@cubist-labs/cubesigner-sdk-ethers-v6 0.3.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/README.md ADDED
@@ -0,0 +1,8 @@
1
+ # Ethers.js Signer implementation for CubeSigner TypeScript SDK
2
+
3
+ This package provides a concrete implementation of the `AbstractSigner` abstract class
4
+ from the `ethers` package that offloads all signing tasks to a remote CubeSigner service.
5
+
6
+ For more information, check out the
7
+ [@cubist-labs/cubesigner-sdk](https://www.npmjs.com/package/@cubist-labs/cubesigner-sdk)
8
+ NPM package.
@@ -0,0 +1,87 @@
1
+ import { TypedDataDomain, TypedDataField, ethers } from "ethers";
2
+ import { KeyInfo, EvmSignRequest, MfaRequestInfo, SignerSession } from "@cubist-labs/cubesigner-sdk";
3
+ /** Options for the signer */
4
+ export interface SignerOptions {
5
+ /** Optional provider to use */
6
+ provider?: null | ethers.Provider;
7
+ /**
8
+ * The function to call when MFA information is retrieved. If this callback
9
+ * throws, no transaction is broadcast.
10
+ */
11
+ onMfaPoll?: (arg0: MfaRequestInfo) => void;
12
+ /**
13
+ * The amount of time (in milliseconds) to wait between checks for MFA
14
+ * updates. Default is 1000ms
15
+ */
16
+ mfaPollIntervalMs?: number;
17
+ }
18
+ /**
19
+ * A ethers.js Signer using CubeSigner
20
+ */
21
+ export declare class Signer extends ethers.AbstractSigner {
22
+ #private;
23
+ /**
24
+ * Create new Signer instance
25
+ * @param {KeyInfo | string} address The key or the eth address of the account to use.
26
+ * @param {SignerSession} signerSession The underlying Signer session.
27
+ * @param {SignerOptions} options The options to use for the Signer instance
28
+ */
29
+ constructor(address: KeyInfo | string, signerSession: SignerSession, options?: SignerOptions);
30
+ /** Resolves to the signer address. */
31
+ getAddress(): Promise<string>;
32
+ /**
33
+ * Returns the signer connected to %%provider%%.
34
+ * @param {null | ethers.Provider} provider The optional provider instance to use.
35
+ * @return {Signer} The signer connected to signer.
36
+ */
37
+ connect(provider: null | ethers.Provider): Signer;
38
+ /**
39
+ * Construct a signing request from a transaction. This populates the transaction
40
+ * type to `0x02` (EIP-1559) unless set.
41
+ *
42
+ * @param {ethers.TransactionRequest} tx The transaction
43
+ * @return {EvmSignRequest} The EVM sign request to be sent to CubeSigner
44
+ */
45
+ evmSignRequestFromTx(tx: ethers.TransactionRequest): Promise<EvmSignRequest>;
46
+ /**
47
+ * Sign a transaction. This method will block if the key requires MFA approval.
48
+ * @param {ethers.TransactionRequest} tx The transaction to sign.
49
+ * @return {Promise<string>} Hex-encoded RLP encoding of the transaction and its signature.
50
+ */
51
+ signTransaction(tx: ethers.TransactionRequest): Promise<string>;
52
+ /**
53
+ * Signs arbitrary messages. This uses CubeSigner's EIP-191 signing endpoint.
54
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
55
+ * `"AllowEip191Signing"` policy attached.
56
+ * @param {string | Uint8Array} message The message to sign.
57
+ * @return {Promise<string>} The signature.
58
+ */
59
+ signMessage(message: string | Uint8Array): Promise<string>;
60
+ /**
61
+ * Signs EIP-712 typed data. This uses CubeSigner's EIP-712 signing endpoint.
62
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
63
+ * `"AllowEip712Signing"` policy attached.
64
+ * @param {TypedDataDomain} domain The domain of the typed data.
65
+ * @param {Record<string, Array<TypedDataField>>} types The types of the typed data.
66
+ * @param {Record<string, any>} value The value of the typed data.
67
+ * @return {Promise<string>} The signature.
68
+ */
69
+ signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string>;
70
+ /** @return {KeyInfo} The key corresponding to this address */
71
+ private key;
72
+ /**
73
+ * Initialize the signing a message using MFA approvals. This method populates
74
+ * missing fields. If the signing does not require MFA, this method throws.
75
+ * @param {ethers.TransactionRequest} tx The transaction to send.
76
+ * @return {string} The MFA id associated with the signing request.
77
+ */
78
+ sendTransactionMfaInit(tx: ethers.TransactionRequest): Promise<string>;
79
+ /**
80
+ * Send a transaction from an approved MFA request. The MFA request contains
81
+ * information about the approved signing request, which this method will
82
+ * execute.
83
+ * @param {MfaRequestInfo} mfaInfo The approved MFA request.
84
+ * @return {ethers.TransactionResponse} The result of submitting the transaction
85
+ */
86
+ sendTransactionMfaApproved(mfaInfo: MfaRequestInfo): Promise<ethers.TransactionResponse>;
87
+ }
package/dist/index.js ADDED
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _Signer_instances, _Signer_address, _Signer_key, _Signer_signerSession, _Signer_onMfaPoll, _Signer_mfaPollIntervalMs, _Signer_handleMfa;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.Signer = void 0;
16
+ const ethers_1 = require("ethers");
17
+ const cubesigner_sdk_1 = require("@cubist-labs/cubesigner-sdk");
18
+ /**
19
+ * A ethers.js Signer using CubeSigner
20
+ */
21
+ class Signer extends ethers_1.ethers.AbstractSigner {
22
+ /**
23
+ * Create new Signer instance
24
+ * @param {KeyInfo | string} address The key or the eth address of the account to use.
25
+ * @param {SignerSession} signerSession The underlying Signer session.
26
+ * @param {SignerOptions} options The options to use for the Signer instance
27
+ */
28
+ constructor(address, signerSession, options) {
29
+ super(options?.provider);
30
+ _Signer_instances.add(this);
31
+ /** The address of the account */
32
+ _Signer_address.set(this, void 0);
33
+ /** The key to use for signing */
34
+ _Signer_key.set(this, void 0);
35
+ /** The underlying session */
36
+ _Signer_signerSession.set(this, void 0);
37
+ /**
38
+ * The function to call when MFA information is retrieved. If this callback
39
+ * throws, no transaction is broadcast.
40
+ */
41
+ _Signer_onMfaPoll.set(this, void 0);
42
+ /** The amount of time to wait between checks for MFA updates */
43
+ _Signer_mfaPollIntervalMs.set(this, void 0);
44
+ if (typeof address === "string") {
45
+ __classPrivateFieldSet(this, _Signer_address, address, "f");
46
+ }
47
+ else {
48
+ __classPrivateFieldSet(this, _Signer_address, address.materialId, "f");
49
+ __classPrivateFieldSet(this, _Signer_key, address, "f");
50
+ }
51
+ __classPrivateFieldSet(this, _Signer_signerSession, signerSession, "f");
52
+ __classPrivateFieldSet(this, _Signer_onMfaPoll, options?.onMfaPoll ?? (( /* _mfaInfo: MfaRequestInfo */) => { }), "f"); // eslint-disable-line @typescript-eslint/no-empty-function
53
+ __classPrivateFieldSet(this, _Signer_mfaPollIntervalMs, options?.mfaPollIntervalMs ?? 1000, "f");
54
+ }
55
+ /** Resolves to the signer address. */
56
+ async getAddress() {
57
+ return __classPrivateFieldGet(this, _Signer_address, "f");
58
+ }
59
+ /**
60
+ * Returns the signer connected to %%provider%%.
61
+ * @param {null | ethers.Provider} provider The optional provider instance to use.
62
+ * @return {Signer} The signer connected to signer.
63
+ */
64
+ connect(provider) {
65
+ return new Signer(__classPrivateFieldGet(this, _Signer_address, "f"), __classPrivateFieldGet(this, _Signer_signerSession, "f"), { provider });
66
+ }
67
+ /**
68
+ * Construct a signing request from a transaction. This populates the transaction
69
+ * type to `0x02` (EIP-1559) unless set.
70
+ *
71
+ * @param {ethers.TransactionRequest} tx The transaction
72
+ * @return {EvmSignRequest} The EVM sign request to be sent to CubeSigner
73
+ */
74
+ async evmSignRequestFromTx(tx) {
75
+ // get the chain id from the network or tx
76
+ let chainId = tx.chainId;
77
+ if (chainId === undefined) {
78
+ const network = await this.provider?.getNetwork();
79
+ chainId = network?.chainId?.toString() ?? "1";
80
+ }
81
+ // Convert the transaction into a JSON-RPC transaction
82
+ const rpcTx = this.provider instanceof ethers_1.JsonRpcApiProvider
83
+ ? this.provider.getRpcTransaction(tx)
84
+ : // We can just call the getRpcTransaction with a
85
+ // null receiver since it doesn't actually use it
86
+ // (and really should be declared static).
87
+ ethers_1.JsonRpcApiProvider.prototype.getRpcTransaction.call(null, tx);
88
+ rpcTx.type = (0, ethers_1.toBeHex)(tx.type ?? 0x02, 1); // we expect 0x0[0-2]
89
+ return {
90
+ chain_id: Number(chainId),
91
+ tx: rpcTx,
92
+ };
93
+ }
94
+ /**
95
+ * Sign a transaction. This method will block if the key requires MFA approval.
96
+ * @param {ethers.TransactionRequest} tx The transaction to sign.
97
+ * @return {Promise<string>} Hex-encoded RLP encoding of the transaction and its signature.
98
+ */
99
+ async signTransaction(tx) {
100
+ const req = await this.evmSignRequestFromTx(tx);
101
+ const res = await __classPrivateFieldGet(this, _Signer_signerSession, "f").signEvm(__classPrivateFieldGet(this, _Signer_address, "f"), req);
102
+ const data = await __classPrivateFieldGet(this, _Signer_instances, "m", _Signer_handleMfa).call(this, res);
103
+ return data.rlp_signed_tx;
104
+ }
105
+ /**
106
+ * Signs arbitrary messages. This uses CubeSigner's EIP-191 signing endpoint.
107
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
108
+ * `"AllowEip191Signing"` policy attached.
109
+ * @param {string | Uint8Array} message The message to sign.
110
+ * @return {Promise<string>} The signature.
111
+ */
112
+ async signMessage(message) {
113
+ const key = await this.key();
114
+ const res = await __classPrivateFieldGet(this, _Signer_signerSession, "f").signEip191(key.material_id, {
115
+ data: (0, cubesigner_sdk_1.encodeToHex)(message),
116
+ });
117
+ const data = await __classPrivateFieldGet(this, _Signer_instances, "m", _Signer_handleMfa).call(this, res);
118
+ return data.signature;
119
+ }
120
+ /**
121
+ * Signs EIP-712 typed data. This uses CubeSigner's EIP-712 signing endpoint.
122
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
123
+ * `"AllowEip712Signing"` policy attached.
124
+ * @param {TypedDataDomain} domain The domain of the typed data.
125
+ * @param {Record<string, Array<TypedDataField>>} types The types of the typed data.
126
+ * @param {Record<string, any>} value The value of the typed data.
127
+ * @return {Promise<string>} The signature.
128
+ */
129
+ async signTypedData(domain, types, value) {
130
+ const key = await this.key();
131
+ const res = await __classPrivateFieldGet(this, _Signer_signerSession, "f").signEip712(key.material_id, {
132
+ chain_id: 1,
133
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
134
+ typed_data: {
135
+ domain,
136
+ types,
137
+ primaryType: ethers_1.TypedDataEncoder.getPrimaryType(types),
138
+ message: value,
139
+ },
140
+ });
141
+ const data = await __classPrivateFieldGet(this, _Signer_instances, "m", _Signer_handleMfa).call(this, res);
142
+ return data.signature;
143
+ }
144
+ /** @return {KeyInfo} The key corresponding to this address */
145
+ async key() {
146
+ if (__classPrivateFieldGet(this, _Signer_key, "f") === undefined) {
147
+ const key = (await __classPrivateFieldGet(this, _Signer_signerSession, "f").keys()).find((k) => k.material_id === __classPrivateFieldGet(this, _Signer_address, "f"));
148
+ if (key === undefined) {
149
+ throw new Error(`Cannot access key '${__classPrivateFieldGet(this, _Signer_address, "f")}'`);
150
+ }
151
+ __classPrivateFieldSet(this, _Signer_key, key, "f");
152
+ }
153
+ return __classPrivateFieldGet(this, _Signer_key, "f");
154
+ }
155
+ /**
156
+ * Initialize the signing a message using MFA approvals. This method populates
157
+ * missing fields. If the signing does not require MFA, this method throws.
158
+ * @param {ethers.TransactionRequest} tx The transaction to send.
159
+ * @return {string} The MFA id associated with the signing request.
160
+ */
161
+ async sendTransactionMfaInit(tx) {
162
+ const popTx = await this.populateTransaction(tx);
163
+ const req = await this.evmSignRequestFromTx(popTx);
164
+ const res = await __classPrivateFieldGet(this, _Signer_signerSession, "f").signEvm(__classPrivateFieldGet(this, _Signer_address, "f"), req);
165
+ return res.mfaId();
166
+ }
167
+ /**
168
+ * Send a transaction from an approved MFA request. The MFA request contains
169
+ * information about the approved signing request, which this method will
170
+ * execute.
171
+ * @param {MfaRequestInfo} mfaInfo The approved MFA request.
172
+ * @return {ethers.TransactionResponse} The result of submitting the transaction
173
+ */
174
+ async sendTransactionMfaApproved(mfaInfo) {
175
+ if (!mfaInfo.request.path.includes("/eth1/sign/")) {
176
+ throw new Error(`Expected EVM transaction signing request, got ${mfaInfo.request.path}`);
177
+ }
178
+ if (!mfaInfo.request.path.includes(__classPrivateFieldGet(this, _Signer_address, "f"))) {
179
+ throw new Error(`Expected signing request for ${__classPrivateFieldGet(this, _Signer_address, "f")} but got ${mfaInfo.request.path}`);
180
+ }
181
+ const signedTx = await __classPrivateFieldGet(this, _Signer_signerSession, "f").signEvm(__classPrivateFieldGet(this, _Signer_address, "f"), mfaInfo.request.body, {
182
+ mfaId: mfaInfo.id,
183
+ mfaOrgId: __classPrivateFieldGet(this, _Signer_signerSession, "f").orgId,
184
+ mfaConf: mfaInfo.receipt.confirmation,
185
+ });
186
+ return await this.provider.broadcastTransaction(signedTx.data().rlp_signed_tx);
187
+ }
188
+ }
189
+ exports.Signer = Signer;
190
+ _Signer_address = new WeakMap(), _Signer_key = new WeakMap(), _Signer_signerSession = new WeakMap(), _Signer_onMfaPoll = new WeakMap(), _Signer_mfaPollIntervalMs = new WeakMap(), _Signer_instances = new WeakSet(), _Signer_handleMfa =
191
+ /**
192
+ * If the sign request requires MFA, this method waits for approvals
193
+ * @param {CubeSignerResponse<U>} res The response of a sign request
194
+ * @return {Promise<U>} The sign data after MFA approvals
195
+ */
196
+ async function _Signer_handleMfa(res) {
197
+ while (res.requiresMfa()) {
198
+ await new Promise((resolve) => setTimeout(resolve, __classPrivateFieldGet(this, _Signer_mfaPollIntervalMs, "f")));
199
+ const mfaId = res.mfaId();
200
+ const mfaInfo = await __classPrivateFieldGet(this, _Signer_signerSession, "f").getMfaInfo(mfaId);
201
+ __classPrivateFieldGet(this, _Signer_onMfaPoll, "f").call(this, mfaInfo);
202
+ if (mfaInfo.receipt) {
203
+ res = await res.signWithMfaApproval({
204
+ mfaId,
205
+ mfaOrgId: __classPrivateFieldGet(this, _Signer_signerSession, "f").orgId,
206
+ mfaConf: mfaInfo.receipt.confirmation,
207
+ });
208
+ }
209
+ }
210
+ return res.data();
211
+ };
212
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsbUNBT2dCO0FBQ2hCLGdFQVNxQztBQWtCckM7O0dBRUc7QUFDSCxNQUFhLE1BQU8sU0FBUSxlQUFNLENBQUMsY0FBYztJQW1CL0M7Ozs7O09BS0c7SUFDSCxZQUFZLE9BQXlCLEVBQUUsYUFBNEIsRUFBRSxPQUF1QjtRQUMxRixLQUFLLENBQUMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDOztRQXpCM0IsaUNBQWlDO1FBQ3hCLGtDQUFpQjtRQUUxQixpQ0FBaUM7UUFDakMsOEJBQWU7UUFFZiw2QkFBNkI7UUFDcEIsd0NBQThCO1FBRXZDOzs7V0FHRztRQUNNLG9DQUEyQztRQUVwRCxnRUFBZ0U7UUFDdkQsNENBQTJCO1FBVWxDLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDaEMsdUJBQUEsSUFBSSxtQkFBWSxPQUFPLE1BQUEsQ0FBQztRQUMxQixDQUFDO2FBQU0sQ0FBQztZQUNOLHVCQUFBLElBQUksbUJBQVksT0FBTyxDQUFDLFVBQVUsTUFBQSxDQUFDO1lBQ25DLHVCQUFBLElBQUksZUFBUSxPQUFrQixNQUFBLENBQUM7UUFDakMsQ0FBQztRQUNELHVCQUFBLElBQUkseUJBQWtCLGFBQWEsTUFBQSxDQUFDO1FBQ3BDLHVCQUFBLElBQUkscUJBQWMsT0FBTyxFQUFFLFNBQVMsSUFBSSxDQUFDLEVBQUMsOEJBQThCLEVBQUUsRUFBRSxHQUFFLENBQUMsQ0FBQyxNQUFBLENBQUMsQ0FBQywyREFBMkQ7UUFDN0ksdUJBQUEsSUFBSSw2QkFBc0IsT0FBTyxFQUFFLGlCQUFpQixJQUFJLElBQUksTUFBQSxDQUFDO0lBQy9ELENBQUM7SUFFRCxzQ0FBc0M7SUFDdEMsS0FBSyxDQUFDLFVBQVU7UUFDZCxPQUFPLHVCQUFBLElBQUksdUJBQVMsQ0FBQztJQUN2QixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILE9BQU8sQ0FBQyxRQUFnQztRQUN0QyxPQUFPLElBQUksTUFBTSxDQUFDLHVCQUFBLElBQUksdUJBQVMsRUFBRSx1QkFBQSxJQUFJLDZCQUFlLEVBQUUsRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDO0lBQ3RFLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCxLQUFLLENBQUMsb0JBQW9CLENBQUMsRUFBNkI7UUFDdEQsMENBQTBDO1FBQzFDLElBQUksT0FBTyxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUM7UUFDekIsSUFBSSxPQUFPLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDMUIsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsUUFBUSxFQUFFLFVBQVUsRUFBRSxDQUFDO1lBQ2xELE9BQU8sR0FBRyxPQUFPLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxJQUFJLEdBQUcsQ0FBQztRQUNoRCxDQUFDO1FBRUQsc0RBQXNEO1FBQ3RELE1BQU0sS0FBSyxHQUNULElBQUksQ0FBQyxRQUFRLFlBQVksMkJBQWtCO1lBQ3pDLENBQUMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGlCQUFpQixDQUFDLEVBQUUsQ0FBQztZQUNyQyxDQUFDLENBQUMsZ0RBQWdEO2dCQUNoRCxpREFBaUQ7Z0JBQ2pELDBDQUEwQztnQkFDMUMsMkJBQWtCLENBQUMsU0FBUyxDQUFDLGlCQUFpQixDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDcEUsS0FBSyxDQUFDLElBQUksR0FBRyxJQUFBLGdCQUFPLEVBQUMsRUFBRSxDQUFDLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxxQkFBcUI7UUFFL0QsT0FBdUI7WUFDckIsUUFBUSxFQUFFLE1BQU0sQ0FBQyxPQUFPLENBQUM7WUFDekIsRUFBRSxFQUFFLEtBQUs7U0FDVixDQUFDO0lBQ0osQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsZUFBZSxDQUFDLEVBQTZCO1FBQ2pELE1BQU0sR0FBRyxHQUFHLE1BQU0sSUFBSSxDQUFDLG9CQUFvQixDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ2hELE1BQU0sR0FBRyxHQUFHLE1BQU0sdUJBQUEsSUFBSSw2QkFBZSxDQUFDLE9BQU8sQ0FBQyx1QkFBQSxJQUFJLHVCQUFTLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDbEUsTUFBTSxJQUFJLEdBQUcsTUFBTSx1QkFBQSxJQUFJLDRDQUFXLE1BQWYsSUFBSSxFQUFZLEdBQUcsQ0FBQyxDQUFDO1FBQ3hDLE9BQU8sSUFBSSxDQUFDLGFBQWEsQ0FBQztJQUM1QixDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0gsS0FBSyxDQUFDLFdBQVcsQ0FBQyxPQUE0QjtRQUM1QyxNQUFNLEdBQUcsR0FBRyxNQUFNLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUM3QixNQUFNLEdBQUcsR0FBRyxNQUFNLHVCQUFBLElBQUksNkJBQWUsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBcUI7WUFDbkYsSUFBSSxFQUFFLElBQUEsNEJBQVcsRUFBQyxPQUFPLENBQUM7U0FDM0IsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxJQUFJLEdBQUcsTUFBTSx1QkFBQSxJQUFJLDRDQUFXLE1BQWYsSUFBSSxFQUFZLEdBQUcsQ0FBQyxDQUFDO1FBQ3hDLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQztJQUN4QixDQUFDO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSCxLQUFLLENBQUMsYUFBYSxDQUNqQixNQUF1QixFQUN2QixLQUE0QyxFQUM1QyxLQUEwQjtRQUUxQixNQUFNLEdBQUcsR0FBRyxNQUFNLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUM3QixNQUFNLEdBQUcsR0FBRyxNQUFNLHVCQUFBLElBQUksNkJBQWUsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBcUI7WUFDbkYsUUFBUSxFQUFFLENBQUM7WUFDWCw4REFBOEQ7WUFDOUQsVUFBVSxFQUFPO2dCQUNmLE1BQU07Z0JBQ04sS0FBSztnQkFDTCxXQUFXLEVBQUUseUJBQWdCLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQztnQkFDbkQsT0FBTyxFQUFFLEtBQUs7YUFDZjtTQUNGLENBQUMsQ0FBQztRQUNILE1BQU0sSUFBSSxHQUFHLE1BQU0sdUJBQUEsSUFBSSw0Q0FBVyxNQUFmLElBQUksRUFBWSxHQUFHLENBQUMsQ0FBQztRQUN4QyxPQUFPLElBQUksQ0FBQyxTQUFTLENBQUM7SUFDeEIsQ0FBQztJQUVELDhEQUE4RDtJQUN0RCxLQUFLLENBQUMsR0FBRztRQUNmLElBQUksdUJBQUEsSUFBSSxtQkFBSyxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQzVCLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSx1QkFBQSxJQUFJLDZCQUFlLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxXQUFXLEtBQUssdUJBQUEsSUFBSSx1QkFBUyxDQUFDLENBQUM7WUFDNUYsSUFBSSxHQUFHLEtBQUssU0FBUyxFQUFFLENBQUM7Z0JBQ3RCLE1BQU0sSUFBSSxLQUFLLENBQUMsc0JBQXNCLHVCQUFBLElBQUksdUJBQVMsR0FBRyxDQUFDLENBQUM7WUFDMUQsQ0FBQztZQUNELHVCQUFBLElBQUksZUFBUSxHQUFHLE1BQUEsQ0FBQztRQUNsQixDQUFDO1FBQ0QsT0FBTyx1QkFBQSxJQUFJLG1CQUFLLENBQUM7SUFDbkIsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0gsS0FBSyxDQUFDLHNCQUFzQixDQUFDLEVBQTZCO1FBQ3hELE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLG1CQUFtQixDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ2pELE1BQU0sR0FBRyxHQUFHLE1BQU0sSUFBSSxDQUFDLG9CQUFvQixDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ25ELE1BQU0sR0FBRyxHQUFHLE1BQU0sdUJBQUEsSUFBSSw2QkFBZSxDQUFDLE9BQU8sQ0FBQyx1QkFBQSxJQUFJLHVCQUFTLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDbEUsT0FBTyxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDckIsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNILEtBQUssQ0FBQywwQkFBMEIsQ0FBQyxPQUF1QjtRQUN0RCxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQUM7WUFDbEQsTUFBTSxJQUFJLEtBQUssQ0FBQyxpREFBaUQsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzNGLENBQUM7UUFDRCxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLHVCQUFBLElBQUksdUJBQVMsQ0FBQyxFQUFFLENBQUM7WUFDbEQsTUFBTSxJQUFJLEtBQUssQ0FDYixnQ0FBZ0MsdUJBQUEsSUFBSSx1QkFBUyxZQUFZLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLENBQ2hGLENBQUM7UUFDSixDQUFDO1FBRUQsTUFBTSxRQUFRLEdBQUcsTUFBTSx1QkFBQSxJQUFJLDZCQUFlLENBQUMsT0FBTyxDQUNoRCx1QkFBQSxJQUFJLHVCQUFTLEVBQ2IsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFzQixFQUN0QztZQUNFLEtBQUssRUFBRSxPQUFPLENBQUMsRUFBRTtZQUNqQixRQUFRLEVBQUUsdUJBQUEsSUFBSSw2QkFBZSxDQUFDLEtBQUs7WUFDbkMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxPQUFRLENBQUMsWUFBWTtTQUN2QyxDQUNGLENBQUM7UUFDRixPQUFPLE1BQU0sSUFBSSxDQUFDLFFBQVMsQ0FBQyxvQkFBb0IsQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDbEYsQ0FBQztDQXdCRjtBQXhORCx3QkF3TkM7O0FBdEJDOzs7O0dBSUc7QUFDSCxLQUFLLDRCQUFlLEdBQTBCO0lBQzVDLE9BQU8sR0FBRyxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUM7UUFDekIsTUFBTSxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSx1QkFBQSxJQUFJLGlDQUFtQixDQUFDLENBQUMsQ0FBQztRQUU3RSxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDMUIsTUFBTSxPQUFPLEdBQUcsTUFBTSx1QkFBQSxJQUFJLDZCQUFlLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzVELHVCQUFBLElBQUkseUJBQVcsTUFBZixJQUFJLEVBQVksT0FBTyxDQUFDLENBQUM7UUFDekIsSUFBSSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDcEIsR0FBRyxHQUFHLE1BQU0sR0FBRyxDQUFDLG1CQUFtQixDQUFDO2dCQUNsQyxLQUFLO2dCQUNMLFFBQVEsRUFBRSx1QkFBQSxJQUFJLDZCQUFlLENBQUMsS0FBSztnQkFDbkMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUMsWUFBWTthQUN0QyxDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQztJQUNELE9BQU8sR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDO0FBQ3BCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBKc29uUnBjQXBpUHJvdmlkZXIsXG4gIFR5cGVkRGF0YURvbWFpbixcbiAgVHlwZWREYXRhRmllbGQsXG4gIFR5cGVkRGF0YUVuY29kZXIsXG4gIGV0aGVycyxcbiAgdG9CZUhleCxcbn0gZnJvbSBcImV0aGVyc1wiO1xuaW1wb3J0IHtcbiAgQ3ViZVNpZ25lclJlc3BvbnNlLFxuICBLZXlJbmZvLFxuICBFaXAxOTFTaWduUmVxdWVzdCxcbiAgRWlwNzEyU2lnblJlcXVlc3QsXG4gIEV2bVNpZ25SZXF1ZXN0LFxuICBNZmFSZXF1ZXN0SW5mbyxcbiAgU2lnbmVyU2Vzc2lvbixcbiAgZW5jb2RlVG9IZXgsXG59IGZyb20gXCJAY3ViaXN0LWxhYnMvY3ViZXNpZ25lci1zZGtcIjtcblxuLyoqIE9wdGlvbnMgZm9yIHRoZSBzaWduZXIgKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2lnbmVyT3B0aW9ucyB7XG4gIC8qKiBPcHRpb25hbCBwcm92aWRlciB0byB1c2UgKi9cbiAgcHJvdmlkZXI/OiBudWxsIHwgZXRoZXJzLlByb3ZpZGVyO1xuICAvKipcbiAgICogVGhlIGZ1bmN0aW9uIHRvIGNhbGwgd2hlbiBNRkEgaW5mb3JtYXRpb24gaXMgcmV0cmlldmVkLiBJZiB0aGlzIGNhbGxiYWNrXG4gICAqIHRocm93cywgbm8gdHJhbnNhY3Rpb24gaXMgYnJvYWRjYXN0LlxuICAgKi9cbiAgb25NZmFQb2xsPzogKGFyZzA6IE1mYVJlcXVlc3RJbmZvKSA9PiB2b2lkO1xuICAvKipcbiAgICogVGhlIGFtb3VudCBvZiB0aW1lIChpbiBtaWxsaXNlY29uZHMpIHRvIHdhaXQgYmV0d2VlbiBjaGVja3MgZm9yIE1GQVxuICAgKiB1cGRhdGVzLiBEZWZhdWx0IGlzIDEwMDBtc1xuICAgKi9cbiAgbWZhUG9sbEludGVydmFsTXM/OiBudW1iZXI7XG59XG5cbi8qKlxuICogQSBldGhlcnMuanMgU2lnbmVyIHVzaW5nIEN1YmVTaWduZXJcbiAqL1xuZXhwb3J0IGNsYXNzIFNpZ25lciBleHRlbmRzIGV0aGVycy5BYnN0cmFjdFNpZ25lciB7XG4gIC8qKiBUaGUgYWRkcmVzcyBvZiB0aGUgYWNjb3VudCAqL1xuICByZWFkb25seSAjYWRkcmVzczogc3RyaW5nO1xuXG4gIC8qKiBUaGUga2V5IHRvIHVzZSBmb3Igc2lnbmluZyAqL1xuICAja2V5PzogS2V5SW5mbztcblxuICAvKiogVGhlIHVuZGVybHlpbmcgc2Vzc2lvbiAqL1xuICByZWFkb25seSAjc2lnbmVyU2Vzc2lvbjogU2lnbmVyU2Vzc2lvbjtcblxuICAvKipcbiAgICogVGhlIGZ1bmN0aW9uIHRvIGNhbGwgd2hlbiBNRkEgaW5mb3JtYXRpb24gaXMgcmV0cmlldmVkLiBJZiB0aGlzIGNhbGxiYWNrXG4gICAqIHRocm93cywgbm8gdHJhbnNhY3Rpb24gaXMgYnJvYWRjYXN0LlxuICAgKi9cbiAgcmVhZG9ubHkgI29uTWZhUG9sbDogKGFyZzA6IE1mYVJlcXVlc3RJbmZvKSA9PiB2b2lkO1xuXG4gIC8qKiBUaGUgYW1vdW50IG9mIHRpbWUgdG8gd2FpdCBiZXR3ZWVuIGNoZWNrcyBmb3IgTUZBIHVwZGF0ZXMgKi9cbiAgcmVhZG9ubHkgI21mYVBvbGxJbnRlcnZhbE1zOiBudW1iZXI7XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBuZXcgU2lnbmVyIGluc3RhbmNlXG4gICAqIEBwYXJhbSB7S2V5SW5mbyB8IHN0cmluZ30gYWRkcmVzcyBUaGUga2V5IG9yIHRoZSBldGggYWRkcmVzcyBvZiB0aGUgYWNjb3VudCB0byB1c2UuXG4gICAqIEBwYXJhbSB7U2lnbmVyU2Vzc2lvbn0gc2lnbmVyU2Vzc2lvbiBUaGUgdW5kZXJseWluZyBTaWduZXIgc2Vzc2lvbi5cbiAgICogQHBhcmFtIHtTaWduZXJPcHRpb25zfSBvcHRpb25zIFRoZSBvcHRpb25zIHRvIHVzZSBmb3IgdGhlIFNpZ25lciBpbnN0YW5jZVxuICAgKi9cbiAgY29uc3RydWN0b3IoYWRkcmVzczogS2V5SW5mbyB8IHN0cmluZywgc2lnbmVyU2Vzc2lvbjogU2lnbmVyU2Vzc2lvbiwgb3B0aW9ucz86IFNpZ25lck9wdGlvbnMpIHtcbiAgICBzdXBlcihvcHRpb25zPy5wcm92aWRlcik7XG4gICAgaWYgKHR5cGVvZiBhZGRyZXNzID09PSBcInN0cmluZ1wiKSB7XG4gICAgICB0aGlzLiNhZGRyZXNzID0gYWRkcmVzcztcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy4jYWRkcmVzcyA9IGFkZHJlc3MubWF0ZXJpYWxJZDtcbiAgICAgIHRoaXMuI2tleSA9IGFkZHJlc3MgYXMgS2V5SW5mbztcbiAgICB9XG4gICAgdGhpcy4jc2lnbmVyU2Vzc2lvbiA9IHNpZ25lclNlc3Npb247XG4gICAgdGhpcy4jb25NZmFQb2xsID0gb3B0aW9ucz8ub25NZmFQb2xsID8/ICgoLyogX21mYUluZm86IE1mYVJlcXVlc3RJbmZvICovKSA9PiB7fSk7IC8vIGVzbGludC1kaXNhYmxlLWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWVtcHR5LWZ1bmN0aW9uXG4gICAgdGhpcy4jbWZhUG9sbEludGVydmFsTXMgPSBvcHRpb25zPy5tZmFQb2xsSW50ZXJ2YWxNcyA/PyAxMDAwO1xuICB9XG5cbiAgLyoqIFJlc29sdmVzIHRvIHRoZSBzaWduZXIgYWRkcmVzcy4gKi9cbiAgYXN5bmMgZ2V0QWRkcmVzcygpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIHJldHVybiB0aGlzLiNhZGRyZXNzO1xuICB9XG5cbiAgLyoqXG4gICAqICBSZXR1cm5zIHRoZSBzaWduZXIgY29ubmVjdGVkIHRvICUlcHJvdmlkZXIlJS5cbiAgICogIEBwYXJhbSB7bnVsbCB8IGV0aGVycy5Qcm92aWRlcn0gcHJvdmlkZXIgVGhlIG9wdGlvbmFsIHByb3ZpZGVyIGluc3RhbmNlIHRvIHVzZS5cbiAgICogIEByZXR1cm4ge1NpZ25lcn0gVGhlIHNpZ25lciBjb25uZWN0ZWQgdG8gc2lnbmVyLlxuICAgKi9cbiAgY29ubmVjdChwcm92aWRlcjogbnVsbCB8IGV0aGVycy5Qcm92aWRlcik6IFNpZ25lciB7XG4gICAgcmV0dXJuIG5ldyBTaWduZXIodGhpcy4jYWRkcmVzcywgdGhpcy4jc2lnbmVyU2Vzc2lvbiwgeyBwcm92aWRlciB9KTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3QgYSBzaWduaW5nIHJlcXVlc3QgZnJvbSBhIHRyYW5zYWN0aW9uLiBUaGlzIHBvcHVsYXRlcyB0aGUgdHJhbnNhY3Rpb25cbiAgICogdHlwZSB0byBgMHgwMmAgKEVJUC0xNTU5KSB1bmxlc3Mgc2V0LlxuICAgKlxuICAgKiBAcGFyYW0ge2V0aGVycy5UcmFuc2FjdGlvblJlcXVlc3R9IHR4IFRoZSB0cmFuc2FjdGlvblxuICAgKiBAcmV0dXJuIHtFdm1TaWduUmVxdWVzdH0gVGhlIEVWTSBzaWduIHJlcXVlc3QgdG8gYmUgc2VudCB0byBDdWJlU2lnbmVyXG4gICAqL1xuICBhc3luYyBldm1TaWduUmVxdWVzdEZyb21UeCh0eDogZXRoZXJzLlRyYW5zYWN0aW9uUmVxdWVzdCk6IFByb21pc2U8RXZtU2lnblJlcXVlc3Q+IHtcbiAgICAvLyBnZXQgdGhlIGNoYWluIGlkIGZyb20gdGhlIG5ldHdvcmsgb3IgdHhcbiAgICBsZXQgY2hhaW5JZCA9IHR4LmNoYWluSWQ7XG4gICAgaWYgKGNoYWluSWQgPT09IHVuZGVmaW5lZCkge1xuICAgICAgY29uc3QgbmV0d29yayA9IGF3YWl0IHRoaXMucHJvdmlkZXI/LmdldE5ldHdvcmsoKTtcbiAgICAgIGNoYWluSWQgPSBuZXR3b3JrPy5jaGFpbklkPy50b1N0cmluZygpID8/IFwiMVwiO1xuICAgIH1cblxuICAgIC8vIENvbnZlcnQgdGhlIHRyYW5zYWN0aW9uIGludG8gYSBKU09OLVJQQyB0cmFuc2FjdGlvblxuICAgIGNvbnN0IHJwY1R4ID1cbiAgICAgIHRoaXMucHJvdmlkZXIgaW5zdGFuY2VvZiBKc29uUnBjQXBpUHJvdmlkZXJcbiAgICAgICAgPyB0aGlzLnByb3ZpZGVyLmdldFJwY1RyYW5zYWN0aW9uKHR4KVxuICAgICAgICA6IC8vIFdlIGNhbiBqdXN0IGNhbGwgdGhlIGdldFJwY1RyYW5zYWN0aW9uIHdpdGggYVxuICAgICAgICAgIC8vIG51bGwgcmVjZWl2ZXIgc2luY2UgaXQgZG9lc24ndCBhY3R1YWxseSB1c2UgaXRcbiAgICAgICAgICAvLyAoYW5kIHJlYWxseSBzaG91bGQgYmUgZGVjbGFyZWQgc3RhdGljKS5cbiAgICAgICAgICBKc29uUnBjQXBpUHJvdmlkZXIucHJvdG90eXBlLmdldFJwY1RyYW5zYWN0aW9uLmNhbGwobnVsbCwgdHgpO1xuICAgIHJwY1R4LnR5cGUgPSB0b0JlSGV4KHR4LnR5cGUgPz8gMHgwMiwgMSk7IC8vIHdlIGV4cGVjdCAweDBbMC0yXVxuXG4gICAgcmV0dXJuIDxFdm1TaWduUmVxdWVzdD57XG4gICAgICBjaGFpbl9pZDogTnVtYmVyKGNoYWluSWQpLFxuICAgICAgdHg6IHJwY1R4LFxuICAgIH07XG4gIH1cblxuICAvKipcbiAgICogU2lnbiBhIHRyYW5zYWN0aW9uLiBUaGlzIG1ldGhvZCB3aWxsIGJsb2NrIGlmIHRoZSBrZXkgcmVxdWlyZXMgTUZBIGFwcHJvdmFsLlxuICAgKiBAcGFyYW0ge2V0aGVycy5UcmFuc2FjdGlvblJlcXVlc3R9IHR4IFRoZSB0cmFuc2FjdGlvbiB0byBzaWduLlxuICAgKiBAcmV0dXJuIHtQcm9taXNlPHN0cmluZz59IEhleC1lbmNvZGVkIFJMUCBlbmNvZGluZyBvZiB0aGUgdHJhbnNhY3Rpb24gYW5kIGl0cyBzaWduYXR1cmUuXG4gICAqL1xuICBhc3luYyBzaWduVHJhbnNhY3Rpb24odHg6IGV0aGVycy5UcmFuc2FjdGlvblJlcXVlc3QpOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IHJlcSA9IGF3YWl0IHRoaXMuZXZtU2lnblJlcXVlc3RGcm9tVHgodHgpO1xuICAgIGNvbnN0IHJlcyA9IGF3YWl0IHRoaXMuI3NpZ25lclNlc3Npb24uc2lnbkV2bSh0aGlzLiNhZGRyZXNzLCByZXEpO1xuICAgIGNvbnN0IGRhdGEgPSBhd2FpdCB0aGlzLiNoYW5kbGVNZmEocmVzKTtcbiAgICByZXR1cm4gZGF0YS5ybHBfc2lnbmVkX3R4O1xuICB9XG5cbiAgLyoqXG4gICAqIFNpZ25zIGFyYml0cmFyeSBtZXNzYWdlcy4gVGhpcyB1c2VzIEN1YmVTaWduZXIncyBFSVAtMTkxIHNpZ25pbmcgZW5kcG9pbnQuXG4gICAqIFRoZSBrZXkgKGZvciB0aGlzIHNlc3Npb24pIG11c3QgaGF2ZSB0aGUgYFwiQWxsb3dSYXdCbG9iU2lnbmluZ1wiYCBvclxuICAgKiBgXCJBbGxvd0VpcDE5MVNpZ25pbmdcImAgcG9saWN5IGF0dGFjaGVkLlxuICAgKiBAcGFyYW0ge3N0cmluZyB8IFVpbnQ4QXJyYXl9IG1lc3NhZ2UgVGhlIG1lc3NhZ2UgdG8gc2lnbi5cbiAgICogQHJldHVybiB7UHJvbWlzZTxzdHJpbmc+fSBUaGUgc2lnbmF0dXJlLlxuICAgKi9cbiAgYXN5bmMgc2lnbk1lc3NhZ2UobWVzc2FnZTogc3RyaW5nIHwgVWludDhBcnJheSk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3Qga2V5ID0gYXdhaXQgdGhpcy5rZXkoKTtcbiAgICBjb25zdCByZXMgPSBhd2FpdCB0aGlzLiNzaWduZXJTZXNzaW9uLnNpZ25FaXAxOTEoa2V5Lm1hdGVyaWFsX2lkLCA8RWlwMTkxU2lnblJlcXVlc3Q+e1xuICAgICAgZGF0YTogZW5jb2RlVG9IZXgobWVzc2FnZSksXG4gICAgfSk7XG4gICAgY29uc3QgZGF0YSA9IGF3YWl0IHRoaXMuI2hhbmRsZU1mYShyZXMpO1xuICAgIHJldHVybiBkYXRhLnNpZ25hdHVyZTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTaWducyBFSVAtNzEyIHR5cGVkIGRhdGEuIFRoaXMgdXNlcyBDdWJlU2lnbmVyJ3MgRUlQLTcxMiBzaWduaW5nIGVuZHBvaW50LlxuICAgKiBUaGUga2V5IChmb3IgdGhpcyBzZXNzaW9uKSBtdXN0IGhhdmUgdGhlIGBcIkFsbG93UmF3QmxvYlNpZ25pbmdcImAgb3JcbiAgICogYFwiQWxsb3dFaXA3MTJTaWduaW5nXCJgIHBvbGljeSBhdHRhY2hlZC5cbiAgICogQHBhcmFtIHtUeXBlZERhdGFEb21haW59IGRvbWFpbiBUaGUgZG9tYWluIG9mIHRoZSB0eXBlZCBkYXRhLlxuICAgKiBAcGFyYW0ge1JlY29yZDxzdHJpbmcsIEFycmF5PFR5cGVkRGF0YUZpZWxkPj59IHR5cGVzIFRoZSB0eXBlcyBvZiB0aGUgdHlwZWQgZGF0YS5cbiAgICogQHBhcmFtIHtSZWNvcmQ8c3RyaW5nLCBhbnk+fSB2YWx1ZSBUaGUgdmFsdWUgb2YgdGhlIHR5cGVkIGRhdGEuXG4gICAqIEByZXR1cm4ge1Byb21pc2U8c3RyaW5nPn0gVGhlIHNpZ25hdHVyZS5cbiAgICovXG4gIGFzeW5jIHNpZ25UeXBlZERhdGEoXG4gICAgZG9tYWluOiBUeXBlZERhdGFEb21haW4sXG4gICAgdHlwZXM6IFJlY29yZDxzdHJpbmcsIEFycmF5PFR5cGVkRGF0YUZpZWxkPj4sXG4gICAgdmFsdWU6IFJlY29yZDxzdHJpbmcsIGFueT4sIC8vIGVzbGludC1kaXNhYmxlLWxpbmUgQHR5cGVzY3JpcHQtZXNsaW50L25vLWV4cGxpY2l0LWFueVxuICApOiBQcm9taXNlPHN0cmluZz4ge1xuICAgIGNvbnN0IGtleSA9IGF3YWl0IHRoaXMua2V5KCk7XG4gICAgY29uc3QgcmVzID0gYXdhaXQgdGhpcy4jc2lnbmVyU2Vzc2lvbi5zaWduRWlwNzEyKGtleS5tYXRlcmlhbF9pZCwgPEVpcDcxMlNpZ25SZXF1ZXN0PntcbiAgICAgIGNoYWluX2lkOiAxLFxuICAgICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1leHBsaWNpdC1hbnlcbiAgICAgIHR5cGVkX2RhdGE6IDxhbnk+e1xuICAgICAgICBkb21haW4sXG4gICAgICAgIHR5cGVzLFxuICAgICAgICBwcmltYXJ5VHlwZTogVHlwZWREYXRhRW5jb2Rlci5nZXRQcmltYXJ5VHlwZSh0eXBlcyksXG4gICAgICAgIG1lc3NhZ2U6IHZhbHVlLFxuICAgICAgfSxcbiAgICB9KTtcbiAgICBjb25zdCBkYXRhID0gYXdhaXQgdGhpcy4jaGFuZGxlTWZhKHJlcyk7XG4gICAgcmV0dXJuIGRhdGEuc2lnbmF0dXJlO1xuICB9XG5cbiAgLyoqIEByZXR1cm4ge0tleUluZm99IFRoZSBrZXkgY29ycmVzcG9uZGluZyB0byB0aGlzIGFkZHJlc3MgKi9cbiAgcHJpdmF0ZSBhc3luYyBrZXkoKTogUHJvbWlzZTxLZXlJbmZvPiB7XG4gICAgaWYgKHRoaXMuI2tleSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICBjb25zdCBrZXkgPSAoYXdhaXQgdGhpcy4jc2lnbmVyU2Vzc2lvbi5rZXlzKCkpLmZpbmQoKGspID0+IGsubWF0ZXJpYWxfaWQgPT09IHRoaXMuI2FkZHJlc3MpO1xuICAgICAgaWYgKGtleSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgQ2Fubm90IGFjY2VzcyBrZXkgJyR7dGhpcy4jYWRkcmVzc30nYCk7XG4gICAgICB9XG4gICAgICB0aGlzLiNrZXkgPSBrZXk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLiNrZXk7XG4gIH1cblxuICAvKipcbiAgICogSW5pdGlhbGl6ZSB0aGUgc2lnbmluZyBhIG1lc3NhZ2UgdXNpbmcgTUZBIGFwcHJvdmFscy4gVGhpcyBtZXRob2QgcG9wdWxhdGVzXG4gICAqIG1pc3NpbmcgZmllbGRzLiBJZiB0aGUgc2lnbmluZyBkb2VzIG5vdCByZXF1aXJlIE1GQSwgdGhpcyBtZXRob2QgdGhyb3dzLlxuICAgKiBAcGFyYW0ge2V0aGVycy5UcmFuc2FjdGlvblJlcXVlc3R9IHR4IFRoZSB0cmFuc2FjdGlvbiB0byBzZW5kLlxuICAgKiBAcmV0dXJuIHtzdHJpbmd9IFRoZSBNRkEgaWQgYXNzb2NpYXRlZCB3aXRoIHRoZSBzaWduaW5nIHJlcXVlc3QuXG4gICAqL1xuICBhc3luYyBzZW5kVHJhbnNhY3Rpb25NZmFJbml0KHR4OiBldGhlcnMuVHJhbnNhY3Rpb25SZXF1ZXN0KTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgICBjb25zdCBwb3BUeCA9IGF3YWl0IHRoaXMucG9wdWxhdGVUcmFuc2FjdGlvbih0eCk7XG4gICAgY29uc3QgcmVxID0gYXdhaXQgdGhpcy5ldm1TaWduUmVxdWVzdEZyb21UeChwb3BUeCk7XG4gICAgY29uc3QgcmVzID0gYXdhaXQgdGhpcy4jc2lnbmVyU2Vzc2lvbi5zaWduRXZtKHRoaXMuI2FkZHJlc3MsIHJlcSk7XG4gICAgcmV0dXJuIHJlcy5tZmFJZCgpO1xuICB9XG5cbiAgLyoqXG4gICAqIFNlbmQgYSB0cmFuc2FjdGlvbiBmcm9tIGFuIGFwcHJvdmVkIE1GQSByZXF1ZXN0LiBUaGUgTUZBIHJlcXVlc3QgY29udGFpbnNcbiAgICogaW5mb3JtYXRpb24gYWJvdXQgdGhlIGFwcHJvdmVkIHNpZ25pbmcgcmVxdWVzdCwgd2hpY2ggdGhpcyBtZXRob2Qgd2lsbFxuICAgKiBleGVjdXRlLlxuICAgKiBAcGFyYW0ge01mYVJlcXVlc3RJbmZvfSBtZmFJbmZvIFRoZSBhcHByb3ZlZCBNRkEgcmVxdWVzdC5cbiAgICogQHJldHVybiB7ZXRoZXJzLlRyYW5zYWN0aW9uUmVzcG9uc2V9IFRoZSByZXN1bHQgb2Ygc3VibWl0dGluZyB0aGUgdHJhbnNhY3Rpb25cbiAgICovXG4gIGFzeW5jIHNlbmRUcmFuc2FjdGlvbk1mYUFwcHJvdmVkKG1mYUluZm86IE1mYVJlcXVlc3RJbmZvKTogUHJvbWlzZTxldGhlcnMuVHJhbnNhY3Rpb25SZXNwb25zZT4ge1xuICAgIGlmICghbWZhSW5mby5yZXF1ZXN0LnBhdGguaW5jbHVkZXMoXCIvZXRoMS9zaWduL1wiKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBFeHBlY3RlZCBFVk0gdHJhbnNhY3Rpb24gc2lnbmluZyByZXF1ZXN0LCBnb3QgJHttZmFJbmZvLnJlcXVlc3QucGF0aH1gKTtcbiAgICB9XG4gICAgaWYgKCFtZmFJbmZvLnJlcXVlc3QucGF0aC5pbmNsdWRlcyh0aGlzLiNhZGRyZXNzKSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgRXhwZWN0ZWQgc2lnbmluZyByZXF1ZXN0IGZvciAke3RoaXMuI2FkZHJlc3N9IGJ1dCBnb3QgJHttZmFJbmZvLnJlcXVlc3QucGF0aH1gLFxuICAgICAgKTtcbiAgICB9XG5cbiAgICBjb25zdCBzaWduZWRUeCA9IGF3YWl0IHRoaXMuI3NpZ25lclNlc3Npb24uc2lnbkV2bShcbiAgICAgIHRoaXMuI2FkZHJlc3MsXG4gICAgICBtZmFJbmZvLnJlcXVlc3QuYm9keSBhcyBFdm1TaWduUmVxdWVzdCxcbiAgICAgIHtcbiAgICAgICAgbWZhSWQ6IG1mYUluZm8uaWQsXG4gICAgICAgIG1mYU9yZ0lkOiB0aGlzLiNzaWduZXJTZXNzaW9uLm9yZ0lkLFxuICAgICAgICBtZmFDb25mOiBtZmFJbmZvLnJlY2VpcHQhLmNvbmZpcm1hdGlvbixcbiAgICAgIH0sXG4gICAgKTtcbiAgICByZXR1cm4gYXdhaXQgdGhpcy5wcm92aWRlciEuYnJvYWRjYXN0VHJhbnNhY3Rpb24oc2lnbmVkVHguZGF0YSgpLnJscF9zaWduZWRfdHgpO1xuICB9XG5cbiAgLyoqXG4gICAqIElmIHRoZSBzaWduIHJlcXVlc3QgcmVxdWlyZXMgTUZBLCB0aGlzIG1ldGhvZCB3YWl0cyBmb3IgYXBwcm92YWxzXG4gICAqIEBwYXJhbSB7Q3ViZVNpZ25lclJlc3BvbnNlPFU+fSByZXMgVGhlIHJlc3BvbnNlIG9mIGEgc2lnbiByZXF1ZXN0XG4gICAqIEByZXR1cm4ge1Byb21pc2U8VT59IFRoZSBzaWduIGRhdGEgYWZ0ZXIgTUZBIGFwcHJvdmFsc1xuICAgKi9cbiAgYXN5bmMgI2hhbmRsZU1mYTxVPihyZXM6IEN1YmVTaWduZXJSZXNwb25zZTxVPik6IFByb21pc2U8VT4ge1xuICAgIHdoaWxlIChyZXMucmVxdWlyZXNNZmEoKSkge1xuICAgICAgYXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHNldFRpbWVvdXQocmVzb2x2ZSwgdGhpcy4jbWZhUG9sbEludGVydmFsTXMpKTtcblxuICAgICAgY29uc3QgbWZhSWQgPSByZXMubWZhSWQoKTtcbiAgICAgIGNvbnN0IG1mYUluZm8gPSBhd2FpdCB0aGlzLiNzaWduZXJTZXNzaW9uLmdldE1mYUluZm8obWZhSWQpO1xuICAgICAgdGhpcy4jb25NZmFQb2xsKG1mYUluZm8pO1xuICAgICAgaWYgKG1mYUluZm8ucmVjZWlwdCkge1xuICAgICAgICByZXMgPSBhd2FpdCByZXMuc2lnbldpdGhNZmFBcHByb3ZhbCh7XG4gICAgICAgICAgbWZhSWQsXG4gICAgICAgICAgbWZhT3JnSWQ6IHRoaXMuI3NpZ25lclNlc3Npb24ub3JnSWQsXG4gICAgICAgICAgbWZhQ29uZjogbWZhSW5mby5yZWNlaXB0LmNvbmZpcm1hdGlvbixcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiByZXMuZGF0YSgpO1xuICB9XG59XG4iXX0=
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@cubist-labs/cubesigner-sdk-ethers-v6",
3
+ "version": "0.3.1",
4
+ "description": "Ethers.js Signer implementation",
5
+ "license": "MIT OR Apache-2.0",
6
+ "author": "Cubist, Inc.",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "tsconfig.json",
11
+ "src/**",
12
+ "dist/**",
13
+ "../../NOTICE",
14
+ "../../LICENSE-APACHE",
15
+ "../../LICENSE-MIT"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepack": "tsc",
20
+ "test": "jest --maxWorkers=1"
21
+ },
22
+ "peerDependencies": {
23
+ "@cubist-labs/cubesigner-sdk": "^0.3.1"
24
+ },
25
+ "dependencies": {
26
+ "ethers": "6.7.1"
27
+ },
28
+ "directories": {
29
+ "test": "test"
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,255 @@
1
+ import {
2
+ JsonRpcApiProvider,
3
+ TypedDataDomain,
4
+ TypedDataField,
5
+ TypedDataEncoder,
6
+ ethers,
7
+ toBeHex,
8
+ } from "ethers";
9
+ import {
10
+ CubeSignerResponse,
11
+ KeyInfo,
12
+ Eip191SignRequest,
13
+ Eip712SignRequest,
14
+ EvmSignRequest,
15
+ MfaRequestInfo,
16
+ SignerSession,
17
+ encodeToHex,
18
+ } from "@cubist-labs/cubesigner-sdk";
19
+
20
+ /** Options for the signer */
21
+ export interface SignerOptions {
22
+ /** Optional provider to use */
23
+ provider?: null | ethers.Provider;
24
+ /**
25
+ * The function to call when MFA information is retrieved. If this callback
26
+ * throws, no transaction is broadcast.
27
+ */
28
+ onMfaPoll?: (arg0: MfaRequestInfo) => void;
29
+ /**
30
+ * The amount of time (in milliseconds) to wait between checks for MFA
31
+ * updates. Default is 1000ms
32
+ */
33
+ mfaPollIntervalMs?: number;
34
+ }
35
+
36
+ /**
37
+ * A ethers.js Signer using CubeSigner
38
+ */
39
+ export class Signer extends ethers.AbstractSigner {
40
+ /** The address of the account */
41
+ readonly #address: string;
42
+
43
+ /** The key to use for signing */
44
+ #key?: KeyInfo;
45
+
46
+ /** The underlying session */
47
+ readonly #signerSession: SignerSession;
48
+
49
+ /**
50
+ * The function to call when MFA information is retrieved. If this callback
51
+ * throws, no transaction is broadcast.
52
+ */
53
+ readonly #onMfaPoll: (arg0: MfaRequestInfo) => void;
54
+
55
+ /** The amount of time to wait between checks for MFA updates */
56
+ readonly #mfaPollIntervalMs: number;
57
+
58
+ /**
59
+ * Create new Signer instance
60
+ * @param {KeyInfo | string} address The key or the eth address of the account to use.
61
+ * @param {SignerSession} signerSession The underlying Signer session.
62
+ * @param {SignerOptions} options The options to use for the Signer instance
63
+ */
64
+ constructor(address: KeyInfo | string, signerSession: SignerSession, options?: SignerOptions) {
65
+ super(options?.provider);
66
+ if (typeof address === "string") {
67
+ this.#address = address;
68
+ } else {
69
+ this.#address = address.materialId;
70
+ this.#key = address as KeyInfo;
71
+ }
72
+ this.#signerSession = signerSession;
73
+ this.#onMfaPoll = options?.onMfaPoll ?? ((/* _mfaInfo: MfaRequestInfo */) => {}); // eslint-disable-line @typescript-eslint/no-empty-function
74
+ this.#mfaPollIntervalMs = options?.mfaPollIntervalMs ?? 1000;
75
+ }
76
+
77
+ /** Resolves to the signer address. */
78
+ async getAddress(): Promise<string> {
79
+ return this.#address;
80
+ }
81
+
82
+ /**
83
+ * Returns the signer connected to %%provider%%.
84
+ * @param {null | ethers.Provider} provider The optional provider instance to use.
85
+ * @return {Signer} The signer connected to signer.
86
+ */
87
+ connect(provider: null | ethers.Provider): Signer {
88
+ return new Signer(this.#address, this.#signerSession, { provider });
89
+ }
90
+
91
+ /**
92
+ * Construct a signing request from a transaction. This populates the transaction
93
+ * type to `0x02` (EIP-1559) unless set.
94
+ *
95
+ * @param {ethers.TransactionRequest} tx The transaction
96
+ * @return {EvmSignRequest} The EVM sign request to be sent to CubeSigner
97
+ */
98
+ async evmSignRequestFromTx(tx: ethers.TransactionRequest): Promise<EvmSignRequest> {
99
+ // get the chain id from the network or tx
100
+ let chainId = tx.chainId;
101
+ if (chainId === undefined) {
102
+ const network = await this.provider?.getNetwork();
103
+ chainId = network?.chainId?.toString() ?? "1";
104
+ }
105
+
106
+ // Convert the transaction into a JSON-RPC transaction
107
+ const rpcTx =
108
+ this.provider instanceof JsonRpcApiProvider
109
+ ? this.provider.getRpcTransaction(tx)
110
+ : // We can just call the getRpcTransaction with a
111
+ // null receiver since it doesn't actually use it
112
+ // (and really should be declared static).
113
+ JsonRpcApiProvider.prototype.getRpcTransaction.call(null, tx);
114
+ rpcTx.type = toBeHex(tx.type ?? 0x02, 1); // we expect 0x0[0-2]
115
+
116
+ return <EvmSignRequest>{
117
+ chain_id: Number(chainId),
118
+ tx: rpcTx,
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Sign a transaction. This method will block if the key requires MFA approval.
124
+ * @param {ethers.TransactionRequest} tx The transaction to sign.
125
+ * @return {Promise<string>} Hex-encoded RLP encoding of the transaction and its signature.
126
+ */
127
+ async signTransaction(tx: ethers.TransactionRequest): Promise<string> {
128
+ const req = await this.evmSignRequestFromTx(tx);
129
+ const res = await this.#signerSession.signEvm(this.#address, req);
130
+ const data = await this.#handleMfa(res);
131
+ return data.rlp_signed_tx;
132
+ }
133
+
134
+ /**
135
+ * Signs arbitrary messages. This uses CubeSigner's EIP-191 signing endpoint.
136
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
137
+ * `"AllowEip191Signing"` policy attached.
138
+ * @param {string | Uint8Array} message The message to sign.
139
+ * @return {Promise<string>} The signature.
140
+ */
141
+ async signMessage(message: string | Uint8Array): Promise<string> {
142
+ const key = await this.key();
143
+ const res = await this.#signerSession.signEip191(key.material_id, <Eip191SignRequest>{
144
+ data: encodeToHex(message),
145
+ });
146
+ const data = await this.#handleMfa(res);
147
+ return data.signature;
148
+ }
149
+
150
+ /**
151
+ * Signs EIP-712 typed data. This uses CubeSigner's EIP-712 signing endpoint.
152
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
153
+ * `"AllowEip712Signing"` policy attached.
154
+ * @param {TypedDataDomain} domain The domain of the typed data.
155
+ * @param {Record<string, Array<TypedDataField>>} types The types of the typed data.
156
+ * @param {Record<string, any>} value The value of the typed data.
157
+ * @return {Promise<string>} The signature.
158
+ */
159
+ async signTypedData(
160
+ domain: TypedDataDomain,
161
+ types: Record<string, Array<TypedDataField>>,
162
+ value: Record<string, any>, // eslint-disable-line @typescript-eslint/no-explicit-any
163
+ ): Promise<string> {
164
+ const key = await this.key();
165
+ const res = await this.#signerSession.signEip712(key.material_id, <Eip712SignRequest>{
166
+ chain_id: 1,
167
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
168
+ typed_data: <any>{
169
+ domain,
170
+ types,
171
+ primaryType: TypedDataEncoder.getPrimaryType(types),
172
+ message: value,
173
+ },
174
+ });
175
+ const data = await this.#handleMfa(res);
176
+ return data.signature;
177
+ }
178
+
179
+ /** @return {KeyInfo} The key corresponding to this address */
180
+ private async key(): Promise<KeyInfo> {
181
+ if (this.#key === undefined) {
182
+ const key = (await this.#signerSession.keys()).find((k) => k.material_id === this.#address);
183
+ if (key === undefined) {
184
+ throw new Error(`Cannot access key '${this.#address}'`);
185
+ }
186
+ this.#key = key;
187
+ }
188
+ return this.#key;
189
+ }
190
+
191
+ /**
192
+ * Initialize the signing a message using MFA approvals. This method populates
193
+ * missing fields. If the signing does not require MFA, this method throws.
194
+ * @param {ethers.TransactionRequest} tx The transaction to send.
195
+ * @return {string} The MFA id associated with the signing request.
196
+ */
197
+ async sendTransactionMfaInit(tx: ethers.TransactionRequest): Promise<string> {
198
+ const popTx = await this.populateTransaction(tx);
199
+ const req = await this.evmSignRequestFromTx(popTx);
200
+ const res = await this.#signerSession.signEvm(this.#address, req);
201
+ return res.mfaId();
202
+ }
203
+
204
+ /**
205
+ * Send a transaction from an approved MFA request. The MFA request contains
206
+ * information about the approved signing request, which this method will
207
+ * execute.
208
+ * @param {MfaRequestInfo} mfaInfo The approved MFA request.
209
+ * @return {ethers.TransactionResponse} The result of submitting the transaction
210
+ */
211
+ async sendTransactionMfaApproved(mfaInfo: MfaRequestInfo): Promise<ethers.TransactionResponse> {
212
+ if (!mfaInfo.request.path.includes("/eth1/sign/")) {
213
+ throw new Error(`Expected EVM transaction signing request, got ${mfaInfo.request.path}`);
214
+ }
215
+ if (!mfaInfo.request.path.includes(this.#address)) {
216
+ throw new Error(
217
+ `Expected signing request for ${this.#address} but got ${mfaInfo.request.path}`,
218
+ );
219
+ }
220
+
221
+ const signedTx = await this.#signerSession.signEvm(
222
+ this.#address,
223
+ mfaInfo.request.body as EvmSignRequest,
224
+ {
225
+ mfaId: mfaInfo.id,
226
+ mfaOrgId: this.#signerSession.orgId,
227
+ mfaConf: mfaInfo.receipt!.confirmation,
228
+ },
229
+ );
230
+ return await this.provider!.broadcastTransaction(signedTx.data().rlp_signed_tx);
231
+ }
232
+
233
+ /**
234
+ * If the sign request requires MFA, this method waits for approvals
235
+ * @param {CubeSignerResponse<U>} res The response of a sign request
236
+ * @return {Promise<U>} The sign data after MFA approvals
237
+ */
238
+ async #handleMfa<U>(res: CubeSignerResponse<U>): Promise<U> {
239
+ while (res.requiresMfa()) {
240
+ await new Promise((resolve) => setTimeout(resolve, this.#mfaPollIntervalMs));
241
+
242
+ const mfaId = res.mfaId();
243
+ const mfaInfo = await this.#signerSession.getMfaInfo(mfaId);
244
+ this.#onMfaPoll(mfaInfo);
245
+ if (mfaInfo.receipt) {
246
+ res = await res.signWithMfaApproval({
247
+ mfaId,
248
+ mfaOrgId: this.#signerSession.orgId,
249
+ mfaConf: mfaInfo.receipt.confirmation,
250
+ });
251
+ }
252
+ }
253
+ return res.data();
254
+ }
255
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "Node16",
5
+ "moduleResolution": "node16",
6
+ "outDir": "./dist"
7
+ },
8
+ "typedocOptions": {
9
+ "out": "./docs",
10
+ "entryPoints": ["src/index.ts"]
11
+ },
12
+ "exclude": ["node_modules", "dist"],
13
+ "include": ["src/**/*.ts"]
14
+ }