@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 +8 -0
- package/dist/index.d.ts +87 -0
- package/dist/index.js +212 -0
- package/package.json +31 -0
- package/src/index.ts +255 -0
- package/tsconfig.json +14 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsbUNBT2dCO0FBQ2hCLGdFQVNxQztBQWtCckM7O0dBRUc7QUFDSCxNQUFhLE1BQU8sU0FBUSxlQUFNLENBQUMsY0FBYztJQW1CL0M7Ozs7O09BS0c7SUFDSCxZQUFZLE9BQXlCLEVBQUUsYUFBNEIsRUFBRSxPQUF1QjtRQUMxRixLQUFLLENBQUMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDOztRQXpCM0IsaUNBQWlDO1FBQ3hCLGtDQUFpQjtRQUUxQixpQ0FBaUM7UUFDakMsOEJBQWU7UUFFZiw2QkFBNkI7UUFDcEIsd0NBQThCO1FBRXZDOzs7V0FHRztRQUNNLG9DQUEyQztRQUVwRCxnRUFBZ0U7UUFDdkQsNENBQTJCO1FBVWxDLElBQUksT0FBTyxPQUFPLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDaEMsdUJBQUEsSUFBSSxtQkFBWSxPQUFPLE1BQUEsQ0FBQztRQUMxQixDQUFDO2FBQU0sQ0FBQztZQUNOLHVCQUFBLElBQUksbUJBQVksT0FBTyxDQUFDLFVBQVUsTUFBQSxDQUFDO1lBQ25DLHVCQUFBLElBQUksZUFBUSxPQUFrQixNQUFBLENBQUM7UUFDakMsQ0FBQztRQUNELHVCQUFBLElBQUkseUJBQWtCLGFBQWEsTUFBQSxDQUFDO1FBQ3BDLHVCQUFBLElBQUkscUJBQWMsT0FBTyxFQUFFLFNBQVMsSUFBSSxDQUFDLEVBQUMsOEJBQThCLEVBQUUsRUFBRSxHQUFFLENBQUMsQ0FBQyxNQUFBLENBQUMsQ0FBQywyREFBMkQ7UUFDN0ksdUJBQUEsSUFBSSw2QkFBc0IsT0FBTyxFQUFFLGlCQUFpQixJQUFJLElBQUksTUFBQSxDQUFDO0lBQy9ELENBQUM7SUFFRCxzQ0FBc0M7SUFDdEMsS0FBSyxDQUFDLFVBQVU7UUFDZCxPQUFPLHVCQUFBLElBQUksdUJBQVMsQ0FBQztJQUN2QixDQUFD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Q0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSx1QkFBQSxJQUFJLGlDQUFtQixDQUFDLENBQUMsQ0FBQztRQUU3RSxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDMUIsTUFBTSxPQUFPLEdBQUcsTUFBTSx1QkFBQSxJQUFJLDZCQUFlLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzVELHVCQUFBLElBQUkseUJBQVcsTUFBZixJQUFJLEVBQVksT0FBTyxDQUFDLENBQUM7UUFDekIsSUFBSSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDcEIsR0FBRyxHQUFHLE1BQU0sR0FBRyxDQUFDLG1CQUFtQixDQUFDO2dCQUNsQyxLQUFLO2dCQUNMLFFBQVEsRUFBRSx1QkFBQSxJQUFJLDZCQUFlLENBQUMsS0FBSztnQkFDbkMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxPQUFPLENBQUMsWUFBWTthQUN0QyxDQUFDLENBQUM7UUFDTCxDQUFDO0lBQ0gsQ0FBQztJQUNELE9BQU8sR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDO0FBQ3BCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBKc29uUnBjQXBpUHJvdmlkZXIsXG4gIFR5cGVkRGF0YURvbWFpbixcbiAgVHlwZWREYXRhRmllbGQsXG4gIFR5cGVkRGF0YUVuY29kZXIsXG4gIGV0aGVycyxcbiAgdG9CZUhleCxcbn0gZnJvbSBcImV0aGVyc1wiO1xuaW1wb3J0IHtcbiAgQ3ViZVNpZ25lclJlc3BvbnNlLFxuICBLZXlJbmZvLFxuICBFaXAxOTFTaWduUmVxdWVzdCxcbiAgRWlwNzEyU2lnblJlcXVlc3QsXG4gIEV2bVNpZ25SZXF1ZXN0LFxuICBNZmFSZXF1ZXN0SW5mbyxcbiAgU2lnbmVyU2Vzc2lvbixcbiAgZW5jb2RlVG9IZXgsXG59IGZyb20gXCJAY3ViaXN0LWxhYnMvY3ViZXNpZ25lci1zZGtcIjtcblxuLyoqIE9wdGlvbnMgZm9yIHRoZSBzaWduZXIgKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2lnbmVyT3B0aW9ucyB7XG4gIC8qKiBPcHRpb25hbCBwcm92aWRlciB0byB1c2UgKi9cbiAgcHJvdmlkZXI/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
|
+
}
|