@cubist-labs/cubesigner-sdk-viem 0.4.183-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # CubeSigner Plugin for Viem
2
+
3
+ This package exposes a single `CubeSignerSource` class which implements
4
+ a custom source in Viem, backed by CubeSigner.
5
+
6
+ ## Simple example usage
7
+
8
+ ```typescript
9
+ import { type CustomSource, createWalletClient, http, publicActions } from "viem";
10
+ import { CubeSignerClient } from "@cubist-labs/cubesigner-sdk";
11
+ import { CubeSignerSource } from "@cubist-labs/cubesigner-sdk-viem";
12
+ import { sepolia } from "viem/chains";
13
+ import { toAccount } from "viem/accounts";
14
+
15
+ const client = await CubeSignerClient.create(...); // must have permissions to sign with `key`
16
+ const key = ...; // A CubeSigner key object or a string of an address in your org
17
+ const chain = sepolia; // use an object from "viem/chains"
18
+
19
+ const account = toAccount(new CubeSignerSource(key, client) as CustomSource);
20
+
21
+ // Create a WalletClient to perform actions
22
+ const walletClient = createWalletClient({
23
+ account,
24
+ chain,
25
+ transport: http(), // uses Viem's default RPC provider
26
+ }).extend(publicActions);
27
+
28
+ // Sign transaction as usual:
29
+ const tx = await walletClient.prepareTransactionRequest({
30
+ to: "0x96be1e4c198ecb1a55e769f653b1934950294f19",
31
+ value: 0n,
32
+ });
33
+
34
+ const signature = await walletClient.signTransaction(tx);
35
+ ...
36
+ ```
37
+
38
+ Check out our [Viem example](../../examples/viem/src/index.ts) or the unit tests
39
+ in the `test` folder for more examples.
40
+
41
+ The [@cubist-labs/cubesigner-sdk](https://www.npmjs.com/package/@cubist-labs/cubesigner-sdk) contains more details on how to create signer sessions.
42
+
43
+ ## Supported Transactions
44
+
45
+ Please note that CubeSigner only supports legacy or EIP-1559 EVM
46
+ transactions with an explicitly defined `type` field. It's recommended to use
47
+ `prepareTransactionRequest` before signing to fill this field.
48
+
49
+ Transactions must take place on a specific chain---either use a client which
50
+ holds a specific chain, or ensure your transaction has the `chainId` field
51
+ defined.
@@ -0,0 +1,55 @@
1
+ import { type Address, type CustomSource } from "viem";
2
+ import { type CubeSignerClient, type EvmSignerOptions, type Key } from "@cubist-labs/cubesigner-sdk";
3
+ /**
4
+ * A class to wrap a CubeSigner key and client into a Viem {@link CustomSource}.
5
+ * Use Viem's `toAccount` to convert this to a Viem Account.
6
+ */
7
+ export declare class CubeSignerSource implements CustomSource {
8
+ #private;
9
+ /** The address the wallet is associated with. */
10
+ readonly address: Address;
11
+ /**
12
+ * Construct a Viem {@link CustomSource} around a CubeSigner key and client.
13
+ * Use Viem's `toAccount` to convert this to a Viem Account.
14
+ *
15
+ * @param address The EVM address this wallet is associated with
16
+ * @param client The session used for signing. Must have necessary scopes.
17
+ * @param options MFA options for the client to respect
18
+ * @throws {Error} if the address is not a valid EVM address
19
+ */
20
+ constructor(address: Key | string, client: CubeSignerClient, options?: EvmSignerOptions);
21
+ /**
22
+ * Signs arbitrary messages. This uses CubeSigner's EIP-191 signing endpoint.
23
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
24
+ * `"AllowEip191Signing"` policy attached.
25
+ *
26
+ * @param root Object around the message
27
+ * @param root.message The message to sign
28
+ * @returns The signature
29
+ */
30
+ signMessage: CustomSource["signMessage"];
31
+ /**
32
+ * Signs EIP-712 typed data. This uses CubeSigner's EIP-712 signing endpoint.
33
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
34
+ * `"AllowEip712Signing"` policy attached.
35
+ * `chainId` must be specified within the `domain`.
36
+ *
37
+ * @param parameters Typed data
38
+ * @returns signature for the typed data
39
+ */
40
+ signTypedData: CustomSource["signTypedData"];
41
+ /**
42
+ * It is recommended to use `prepareTransactionRequest` on your request
43
+ * before calling this function.
44
+ *
45
+ * Sign a transaction. This method will block if the key requires MFA approval.
46
+ * `type` and `chainId` must be defined. Only supports type "legacy" or "eip1559".
47
+ *
48
+ * @param transaction The transaction to sign
49
+ * @param options Contains an optional custom serializer
50
+ * @returns Signed transaction
51
+ * @throws {Error} if transaction.type isn't "legacy" or "eip1559", or if "chainId" isn't specified
52
+ */
53
+ signTransaction: CustomSource["signTransaction"];
54
+ }
55
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,YAAY,EAUlB,MAAM,MAAM,CAAC;AACd,OAAO,EACL,KAAK,gBAAgB,EAIrB,KAAK,gBAAgB,EACrB,KAAK,GAAG,EACT,MAAM,6BAA6B,CAAC;AAErC;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,YAAY;;IAInD,iDAAiD;IACjD,SAAgB,OAAO,EAAE,OAAO,CAAC;IAEjC;;;;;;;;OAQG;gBACS,OAAO,EAAE,GAAG,GAAG,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,gBAAgB;IAYvF;;;;;;;;OAQG;IACI,WAAW,EAAE,YAAY,CAAC,aAAa,CAAC,CAa7C;IAEF;;;;;;;;OAQG;IACI,aAAa,EAAE,YAAY,CAAC,eAAe,CAAC,CAYjD;IAEF;;;;;;;;;;;OAWG;IACI,eAAe,EAAE,YAAY,CAAC,iBAAiB,CAAC,CAsBrD;CACH"}
package/dist/index.js ADDED
@@ -0,0 +1,129 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ 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");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ 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");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _CubeSignerSource_signer;
13
+ import { bytesToHex, formatTransactionRequest, getAddress, isHex, parseTransaction, serializeTransaction, stringToHex, } from "viem";
14
+ import { EvmSigner, } from "@cubist-labs/cubesigner-sdk";
15
+ /**
16
+ * A class to wrap a CubeSigner key and client into a Viem {@link CustomSource}.
17
+ * Use Viem's `toAccount` to convert this to a Viem Account.
18
+ */
19
+ export class CubeSignerSource {
20
+ /**
21
+ * Construct a Viem {@link CustomSource} around a CubeSigner key and client.
22
+ * Use Viem's `toAccount` to convert this to a Viem Account.
23
+ *
24
+ * @param address The EVM address this wallet is associated with
25
+ * @param client The session used for signing. Must have necessary scopes.
26
+ * @param options MFA options for the client to respect
27
+ * @throws {Error} if the address is not a valid EVM address
28
+ */
29
+ constructor(address, client, options) {
30
+ /** The internal CubeSigner signer used. */
31
+ _CubeSignerSource_signer.set(this, void 0);
32
+ /**
33
+ * Signs arbitrary messages. This uses CubeSigner's EIP-191 signing endpoint.
34
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
35
+ * `"AllowEip191Signing"` policy attached.
36
+ *
37
+ * @param root Object around the message
38
+ * @param root.message The message to sign
39
+ * @returns The signature
40
+ */
41
+ this.signMessage = async ({ message }) => {
42
+ let hex;
43
+ if (typeof message === "string") {
44
+ hex = stringToHex(message);
45
+ }
46
+ else if (isHex(message.raw)) {
47
+ hex = message.raw;
48
+ }
49
+ else {
50
+ hex = bytesToHex(message.raw);
51
+ }
52
+ const signature = await __classPrivateFieldGet(this, _CubeSignerSource_signer, "f").signEip191({ data: hex });
53
+ return ensureHex(signature);
54
+ };
55
+ /**
56
+ * Signs EIP-712 typed data. This uses CubeSigner's EIP-712 signing endpoint.
57
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
58
+ * `"AllowEip712Signing"` policy attached.
59
+ * `chainId` must be specified within the `domain`.
60
+ *
61
+ * @param parameters Typed data
62
+ * @returns signature for the typed data
63
+ */
64
+ this.signTypedData = async (parameters) => {
65
+ assert(parameters.domain, "`domain` must be defined");
66
+ const castedParameters = parameters;
67
+ assert(castedParameters.domain.chainId, "`domain.chainId` must be defined");
68
+ const signature = await __classPrivateFieldGet(this, _CubeSignerSource_signer, "f").signEip712({
69
+ chain_id: Number(castedParameters.domain.chainId),
70
+ typed_data: castedParameters,
71
+ });
72
+ return ensureHex(signature);
73
+ };
74
+ /**
75
+ * It is recommended to use `prepareTransactionRequest` on your request
76
+ * before calling this function.
77
+ *
78
+ * Sign a transaction. This method will block if the key requires MFA approval.
79
+ * `type` and `chainId` must be defined. Only supports type "legacy" or "eip1559".
80
+ *
81
+ * @param transaction The transaction to sign
82
+ * @param options Contains an optional custom serializer
83
+ * @returns Signed transaction
84
+ * @throws {Error} if transaction.type isn't "legacy" or "eip1559", or if "chainId" isn't specified
85
+ */
86
+ this.signTransaction = async (transaction, options) => {
87
+ assert(transaction.type === "legacy" || transaction.type === "eip1559", `Unsupported transaction type '${transaction.type}', CubeSigner only supports type 'legacy' or 'eip1559'")}'`);
88
+ assert(transaction.chainId, "`chainId` must be defined");
89
+ const formatted = formatTransactionRequest(transaction);
90
+ const rlpSignedTransaction = await __classPrivateFieldGet(this, _CubeSignerSource_signer, "f").signTransaction({
91
+ chain_id: transaction.chainId,
92
+ tx: formatted,
93
+ });
94
+ // CubeSigner returns an RLP-encoded transaction. Since Viem allows users to pass a custom serializer, we will
95
+ // now unserialize and reserialize with Viem's serializer.
96
+ const { r, s, v, yParity, ...parsedTransaction } = parseTransaction(ensureHex(rlpSignedTransaction));
97
+ const serializer = options?.serializer ?? serializeTransaction;
98
+ return await serializer(parsedTransaction, { r, s, v, yParity });
99
+ };
100
+ __classPrivateFieldSet(this, _CubeSignerSource_signer, new EvmSigner(address, client, options), "f");
101
+ // NOTE: `getAddress` will checksum the address and throw if it's an invalid EVM address.
102
+ this.address = getAddress(__classPrivateFieldGet(this, _CubeSignerSource_signer, "f").address);
103
+ // Scope these functions to properly resolve `this`
104
+ this.signMessage = this.signMessage.bind(this);
105
+ this.signTransaction = this.signTransaction.bind(this);
106
+ this.signTypedData = this.signTypedData.bind(this);
107
+ }
108
+ }
109
+ _CubeSignerSource_signer = new WeakMap();
110
+ /**
111
+ * @param input A hex string
112
+ * @returns the input, type narrowed to Hex
113
+ * @throws {Error} if input is not Hex
114
+ */
115
+ function ensureHex(input) {
116
+ assert(isHex(input), `${input} is not hex`);
117
+ return input;
118
+ }
119
+ /**
120
+ * @param value A value that is expected to be truthy
121
+ * @param message The error message if the value is falsy
122
+ * @throws {Error} if value is not truthy
123
+ */
124
+ function assert(value, message) {
125
+ if (!value) {
126
+ throw new Error(message);
127
+ }
128
+ }
129
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUtMLFVBQVUsRUFDVix3QkFBd0IsRUFDeEIsVUFBVSxFQUNWLEtBQUssRUFDTCxnQkFBZ0IsRUFDaEIsb0JBQW9CLEVBQ3BCLFdBQVcsR0FDWixNQUFNLE1BQU0sQ0FBQztBQUNkLE9BQU8sRUFJTCxTQUFTLEdBR1YsTUFBTSw2QkFBNkIsQ0FBQztBQUVyQzs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sZ0JBQWdCO0lBTzNCOzs7Ozs7OztPQVFHO0lBQ0gsWUFBWSxPQUFxQixFQUFFLE1BQXdCLEVBQUUsT0FBMEI7UUFmdkYsMkNBQTJDO1FBQ2xDLDJDQUFtQjtRQTBCNUI7Ozs7Ozs7O1dBUUc7UUFDSSxnQkFBVyxHQUFnQyxLQUFLLEVBQUUsRUFBRSxPQUFPLEVBQUUsRUFBRSxFQUFFO1lBQ3RFLElBQUksR0FBRyxDQUFDO1lBQ1IsSUFBSSxPQUFPLE9BQU8sS0FBSyxRQUFRLEVBQUUsQ0FBQztnQkFDaEMsR0FBRyxHQUFHLFdBQVcsQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUM3QixDQUFDO2lCQUFNLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUM5QixHQUFHLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQztZQUNwQixDQUFDO2lCQUFNLENBQUM7Z0JBQ04sR0FBRyxHQUFHLFVBQVUsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDaEMsQ0FBQztZQUVELE1BQU0sU0FBUyxHQUFHLE1BQU0sdUJBQUEsSUFBSSxnQ0FBUSxDQUFDLFVBQVUsQ0FBQyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDO1lBRS9ELE9BQU8sU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQzlCLENBQUMsQ0FBQztRQUVGOzs7Ozs7OztXQVFHO1FB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QUM7QUFDSCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgdHlwZSBBZGRyZXNzLFxuICB0eXBlIEN1c3RvbVNvdXJjZSxcbiAgdHlwZSBIZXgsXG4gIHR5cGUgU2lnbmF0dXJlLFxuICBieXRlc1RvSGV4LFxuICBmb3JtYXRUcmFuc2FjdGlvblJlcXVlc3QsXG4gIGdldEFkZHJlc3MsXG4gIGlzSGV4LFxuICBwYXJzZVRyYW5zYWN0aW9uLFxuICBzZXJpYWxpemVUcmFuc2FjdGlvbixcbiAgc3RyaW5nVG9IZXgsXG59IGZyb20gXCJ2aWVtXCI7XG5pbXBvcnQge1xuICB0eXBlIEN1YmVTaWduZXJDbGllbnQsXG4gIHR5cGUgRWlwNzEyU2lnblJlcXVlc3QsXG4gIHR5cGUgRXZtU2lnblJlcXVlc3QsXG4gIEV2bVNpZ25lcixcbiAgdHlwZSBFdm1TaWduZXJPcHRpb25zLFxuICB0eXBlIEtleSxcbn0gZnJvbSBcIkBjdWJpc3QtbGFicy9jdWJlc2lnbmVyLXNka1wiO1xuXG4vKipcbiAqIEEgY2xhc3MgdG8gd3JhcCBhIEN1YmVTaWduZXIga2V5IGFuZCBjbGllbnQgaW50byBhIFZpZW0ge0BsaW5rIEN1c3RvbVNvdXJjZX0uXG4gKiBVc2UgVmllbSdzIGB0b0FjY291bnRgIHRvIGNvbnZlcnQgdGhpcyB0byBhIFZpZW0gQWNjb3VudC5cbiAqL1xuZXhwb3J0IGNsYXNzIEN1YmVTaWduZXJTb3VyY2UgaW1wbGVtZW50cyBDdXN0b21Tb3VyY2Uge1xuICAvKiogVGhlIGludGVybmFsIEN1YmVTaWduZXIgc2lnbmVyIHVzZWQuICovXG4gIHJlYWRvbmx5ICNzaWduZXI6IEV2bVNpZ25lcjtcblxuICAvKiogVGhlIGFkZHJlc3MgdGhlIHdhbGxldCBpcyBhc3NvY2lhdGVkIHdpdGguICovXG4gIHB1YmxpYyByZWFkb25seSBhZGRyZXNzOiBBZGRyZXNzO1xuXG4gIC8qKlxuICAgKiBDb25zdHJ1Y3QgYSBWaWVtIHtAbGluayBDdXN0b21Tb3VyY2V9IGFyb3VuZCBhIEN1YmVTaWduZXIga2V5IGFuZCBjbGllbnQuXG4gICAqIFVzZSBWaWVtJ3MgYHRvQWNjb3VudGAgdG8gY29udmVydCB0aGlzIHRvIGEgVmllbSBBY2NvdW50LlxuICAgKlxuICAgKiBAcGFyYW0gYWRkcmVzcyBUaGUgRVZNIGFkZHJlc3MgdGhpcyB3YWxsZXQgaXMgYXNzb2NpYXRlZCB3aXRoXG4gICAqIEBwYXJhbSBjbGllbnQgVGhlIHNlc3Npb24gdXNlZCBmb3Igc2lnbmluZy4gTXVzdCBoYXZlIG5lY2Vzc2FyeSBzY29wZXMuXG4gICAqIEBwYXJhbSBvcHRpb25zIE1GQSBvcHRpb25zIGZvciB0aGUgY2xpZW50IHRvIHJlc3BlY3RcbiAgICogQHRocm93cyB7RXJyb3J9IGlmIHRoZSBhZGRyZXNzIGlzIG5vdCBhIHZhbGlkIEVWTSBhZGRyZXNzXG4gICAqL1xuICBjb25zdHJ1Y3RvcihhZGRyZXNzOiBLZXkgfCBzdHJpbmcsIGNsaWVudDogQ3ViZVNpZ25lckNsaWVudCwgb3B0aW9ucz86IEV2bVNpZ25lck9wdGlvbnMpIHtcbiAgICB0aGlzLiNzaWduZXIgPSBuZXcgRXZtU2lnbmVyKGFkZHJlc3MsIGNsaWVudCwgb3B0aW9ucyk7XG5cbiAgICAvLyBOT1RFOiBgZ2V0QWRkcmVzc2Agd2lsbCBjaGVja3N1bSB0aGUgYWRkcmVzcyBhbmQgdGhyb3cgaWYgaXQncyBhbiBpbnZhbGlkIEVWTSBhZGRyZXNzLlxuICAgIHRoaXMuYWRkcmVzcyA9IGdldEFkZHJlc3ModGhpcy4jc2lnbmVyLmFkZHJlc3MpO1xuXG4gICAgLy8gU2NvcGUgdGhlc2UgZnVuY3Rpb25zIHRvIHByb3Blcmx5IHJlc29sdmUgYHRoaXNgXG4gICAgdGhpcy5zaWduTWVzc2FnZSA9IHRoaXMuc2lnbk1lc3NhZ2UuYmluZCh0aGlzKTtcbiAgICB0aGlzLnNpZ25UcmFuc2FjdGlvbiA9IHRoaXMuc2lnblRyYW5zYWN0aW9uLmJpbmQodGhpcyk7XG4gICAgdGhpcy5zaWduVHlwZWREYXRhID0gdGhpcy5zaWduVHlwZWREYXRhLmJpbmQodGhpcyk7XG4gIH1cblxuICAvKipcbiAgICogU2lnbnMgYXJiaXRyYXJ5IG1lc3NhZ2VzLiBUaGlzIHVzZXMgQ3ViZVNpZ25lcidzIEVJUC0xOTEgc2lnbmluZyBlbmRwb2ludC5cbiAgICogVGhlIGtleSAoZm9yIHRoaXMgc2Vzc2lvbikgbXVzdCBoYXZlIHRoZSBgXCJBbGxvd1Jhd0Jsb2JTaWduaW5nXCJgIG9yXG4gICAqIGBcIkFsbG93RWlwMTkxU2lnbmluZ1wiYCBwb2xpY3kgYXR0YWNoZWQuXG4gICAqXG4gICAqIEBwYXJhbSByb290IE9iamVjdCBhcm91bmQgdGhlIG1lc3NhZ2VcbiAgICogQHBhcmFtIHJvb3QubWVzc2FnZSBUaGUgbWVzc2FnZSB0byBzaWduXG4gICAqIEByZXR1cm5zIFRoZSBzaWduYXR1cmVcbiAgICovXG4gIHB1YmxpYyBzaWduTWVzc2FnZTogQ3VzdG9tU291cmNlW1wic2lnbk1lc3NhZ2VcIl0gPSBhc3luYyAoeyBtZXNzYWdlIH0pID0+IHtcbiAgICBsZXQgaGV4O1xuICAgIGlmICh0eXBlb2YgbWVzc2FnZSA9PT0gXCJzdHJpbmdcIikge1xuICAgICAgaGV4ID0gc3RyaW5nVG9IZXgobWVzc2FnZSk7XG4gICAgfSBlbHNlIGlmIChpc0hleChtZXNzYWdlLnJhdykpIHtcbiAgICAgIGhleCA9IG1lc3NhZ2UucmF3O1xuICAgIH0gZWxzZSB7XG4gICAgICBoZXggPSBieXRlc1RvSGV4KG1lc3NhZ2UucmF3KTtcbiAgICB9XG5cbiAgICBjb25zdCBzaWduYXR1cmUgPSBhd2FpdCB0aGlzLiNzaWduZXIuc2lnbkVpcDE5MSh7IGRhdGE6IGhleCB9KTtcblxuICAgIHJldHVybiBlbnN1cmVIZXgoc2lnbmF0dXJlKTtcbiAgfTtcblxuICAvKipcbiAgICogU2lnbnMgRUlQLTcxMiB0eXBlZCBkYXRhLiBUaGlzIHVzZXMgQ3ViZVNpZ25lcidzIEVJUC03MTIgc2lnbmluZyBlbmRwb2ludC5cbiAgICogVGhlIGtleSAoZm9yIHRoaXMgc2Vzc2lvbikgbXVzdCBoYXZlIHRoZSBgXCJBbGxvd1Jhd0Jsb2JTaWduaW5nXCJgIG9yXG4gICAqIGBcIkFsbG93RWlwNzEyU2lnbmluZ1wiYCBwb2xpY3kgYXR0YWNoZWQuXG4gICAqIGBjaGFpbklkYCBtdXN0IGJlIHNwZWNpZmllZCB3aXRoaW4gdGhlIGBkb21haW5gLlxuICAgKlxuICAgKiBAcGFyYW0gcGFyYW1ldGVycyBUeXBlZCBkYXRhXG4gICAqIEByZXR1cm5zIHNpZ25hdHVyZSBmb3IgdGhlIHR5cGVkIGRhdGFcbiAgICovXG4gIHB1YmxpYyBzaWduVHlwZWREYXRhOiBDdXN0b21Tb3VyY2VbXCJzaWduVHlwZWREYXRhXCJdID0gYXN5bmMgKHBhcmFtZXRlcnMpID0+IHtcbiAgICBhc3NlcnQocGFyYW1ldGVycy5kb21haW4sIFwiYGRvbWFpbmAgbXVzdCBiZSBkZWZpbmVkXCIpO1xuXG4gICAgY29uc3QgY2FzdGVkUGFyYW1ldGVycyA9IHBhcmFtZXRlcnMgYXMgRWlwNzEyU2lnblJlcXVlc3RbXCJ0eXBlZF9kYXRhXCJdO1xuICAgIGFzc2VydChjYXN0ZWRQYXJhbWV0ZXJzLmRvbWFpbi5jaGFpbklkLCBcImBkb21haW4uY2hhaW5JZGAgbXVzdCBiZSBkZWZpbmVkXCIpO1xuXG4gICAgY29uc3Qgc2lnbmF0dXJlID0gYXdhaXQgdGhpcy4jc2lnbmVyLnNpZ25FaXA3MTIoe1xuICAgICAgY2hhaW5faWQ6IE51bWJlcihjYXN0ZWRQYXJhbWV0ZXJzLmRvbWFpbi5jaGFpbklkKSxcbiAgICAgIHR5cGVkX2RhdGE6IGNhc3RlZFBhcmFtZXRlcnMsXG4gICAgfSk7XG5cbiAgICByZXR1cm4gZW5zdXJlSGV4KHNpZ25hdHVyZSk7XG4gIH07XG5cbiAgLyoqXG4gICAqIEl0IGlzIHJlY29tbWVuZGVkIHRvIHVzZSBgcHJlcGFyZVRyYW5zYWN0aW9uUmVxdWVzdGAgb24geW91ciByZXF1ZXN0XG4gICAqIGJlZm9yZSBjYWxsaW5nIHRoaXMgZnVuY3Rpb24uXG4gICAqXG4gICAqIFNpZ24gYSB0cmFuc2FjdGlvbi4gVGhpcyBtZXRob2Qgd2lsbCBibG9jayBpZiB0aGUga2V5IHJlcXVpcmVzIE1GQSBhcHByb3ZhbC5cbiAgICogYHR5cGVgIGFuZCBgY2hhaW5JZGAgbXVzdCBiZSBkZWZpbmVkLiBPbmx5IHN1cHBvcnRzIHR5cGUgXCJsZWdhY3lcIiBvciBcImVpcDE1NTlcIi5cbiAgICpcbiAgICogQHBhcmFtIHRyYW5zYWN0aW9uIFRoZSB0cmFuc2FjdGlvbiB0byBzaWduXG4gICAqIEBwYXJhbSBvcHRpb25zIENvbnRhaW5zIGFuIG9wdGlvbmFsIGN1c3RvbSBzZXJpYWxpemVyXG4gICAqIEByZXR1cm5zIFNpZ25lZCB0cmFuc2FjdGlvblxuICAgKiBAdGhyb3dzIHtFcnJvcn0gaWYgdHJhbnNhY3Rpb24udHlwZSBpc24ndCBcImxlZ2FjeVwiIG9yIFwiZWlwMTU1OVwiLCBvciBpZiBcImNoYWluSWRcIiBpc24ndCBzcGVjaWZpZWRcbiAgICovXG4gIHB1YmxpYyBzaWduVHJhbnNhY3Rpb246IEN1c3RvbVNvdXJjZVtcInNpZ25UcmFuc2FjdGlvblwiXSA9IGFzeW5jICh0cmFuc2FjdGlvbiwgb3B0aW9ucykgPT4ge1xuICAgIGFzc2VydChcbiAgICAgIHRyYW5zYWN0aW9uLnR5cGUgPT09IFwibGVnYWN5XCIgfHwgdHJhbnNhY3Rpb24udHlwZSA9PT0gXCJlaXAxNTU5XCIsXG4gICAgICBgVW5zdXBwb3J0ZWQgdHJhbnNhY3Rpb24gdHlwZSAnJHt0cmFuc2FjdGlvbi50eXBlfScsIEN1YmVTaWduZXIgb25seSBzdXBwb3J0cyB0eXBlICdsZWdhY3knIG9yICdlaXAxNTU5J1wiKX0nYCxcbiAgICApO1xuXG4gICAgYXNzZXJ0KHRyYW5zYWN0aW9uLmNoYWluSWQsIFwiYGNoYWluSWRgIG11c3QgYmUgZGVmaW5lZFwiKTtcblxuICAgIGNvbnN0IGZvcm1hdHRlZCA9IGZvcm1hdFRyYW5zYWN0aW9uUmVxdWVzdCh0cmFuc2FjdGlvbik7XG4gICAgY29uc3QgcmxwU2lnbmVkVHJhbnNhY3Rpb24gPSBhd2FpdCB0aGlzLiNzaWduZXIuc2lnblRyYW5zYWN0aW9uKHtcbiAgICAgIGNoYWluX2lkOiB0cmFuc2FjdGlvbi5jaGFpbklkLFxuICAgICAgdHg6IGZvcm1hdHRlZCBhcyBFdm1TaWduUmVxdWVzdFtcInR4XCJdLFxuICAgIH0pO1xuXG4gICAgLy8gQ3ViZVNpZ25lciByZXR1cm5zIGFuIFJMUC1lbmNvZGVkIHRyYW5zYWN0aW9uLiBTaW5jZSBWaWVtIGFsbG93cyB1c2VycyB0byBwYXNzIGEgY3VzdG9tIHNlcmlhbGl6ZXIsIHdlIHdpbGxcbiAgICAvLyBub3cgdW5zZXJpYWxpemUgYW5kIHJlc2VyaWFsaXplIHdpdGggVmllbSdzIHNlcmlhbGl6ZXIuXG4gICAgY29uc3QgeyByLCBzLCB2LCB5UGFyaXR5LCAuLi5wYXJzZWRUcmFuc2FjdGlvbiB9ID0gcGFyc2VUcmFuc2FjdGlvbihcbiAgICAgIGVuc3VyZUhleChybHBTaWduZWRUcmFuc2FjdGlvbiksXG4gICAgKTtcbiAgICBjb25zdCBzZXJpYWxpemVyID0gb3B0aW9ucz8uc2VyaWFsaXplciA/PyBzZXJpYWxpemVUcmFuc2FjdGlvbjtcblxuICAgIHJldHVybiBhd2FpdCBzZXJpYWxpemVyKHBhcnNlZFRyYW5zYWN0aW9uLCB7IHIsIHMsIHYsIHlQYXJpdHkgfSBhcyBTaWduYXR1cmUpO1xuICB9O1xufVxuXG4vKipcbiAqIEBwYXJhbSBpbnB1dCBBIGhleCBzdHJpbmdcbiAqIEByZXR1cm5zIHRoZSBpbnB1dCwgdHlwZSBuYXJyb3dlZCB0byBIZXhcbiAqIEB0aHJvd3Mge0Vycm9yfSBpZiBpbnB1dCBpcyBub3QgSGV4XG4gKi9cbmZ1bmN0aW9uIGVuc3VyZUhleChpbnB1dDogc3RyaW5nKTogSGV4IHtcbiAgYXNzZXJ0KGlzSGV4KGlucHV0KSwgYCR7aW5wdXR9IGlzIG5vdCBoZXhgKTtcbiAgcmV0dXJuIGlucHV0O1xufVxuXG4vKipcbiAqIEBwYXJhbSB2YWx1ZSBBIHZhbHVlIHRoYXQgaXMgZXhwZWN0ZWQgdG8gYmUgdHJ1dGh5XG4gKiBAcGFyYW0gbWVzc2FnZSBUaGUgZXJyb3IgbWVzc2FnZSBpZiB0aGUgdmFsdWUgaXMgZmFsc3lcbiAqIEB0aHJvd3Mge0Vycm9yfSBpZiB2YWx1ZSBpcyBub3QgdHJ1dGh5XG4gKi9cbmZ1bmN0aW9uIGFzc2VydCh2YWx1ZTogdW5rbm93biwgbWVzc2FnZTogc3RyaW5nKTogYXNzZXJ0cyB2YWx1ZSB7XG4gIGlmICghdmFsdWUpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IobWVzc2FnZSk7XG4gIH1cbn1cbiJdfQ==
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@cubist-labs/cubesigner-sdk-viem",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "git+https://github.com/cubist-labs/CubeSigner-TypeScript-SDK.git",
6
+ "directory": "packages/viem"
7
+ },
8
+ "version": "0.4.183-0",
9
+ "type": "module",
10
+ "description": "Viem CustomSource implementation",
11
+ "license": "MIT OR Apache-2.0",
12
+ "author": "Cubist, Inc.",
13
+ "main": "dist/index.js",
14
+ "types": "dist/index.d.ts",
15
+ "files": [
16
+ "tsconfig.json",
17
+ "src/**",
18
+ "dist/**",
19
+ "../../NOTICE",
20
+ "../../LICENSE-APACHE",
21
+ "../../LICENSE-MIT"
22
+ ],
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "prepack": "tsc",
26
+ "test": "jest --maxWorkers=1"
27
+ },
28
+ "directories": {
29
+ "test": "test"
30
+ },
31
+ "peerDependencies": {
32
+ "@cubist-labs/cubesigner-sdk": "^0.4.183-0",
33
+ "viem": "^2.38.2"
34
+ },
35
+ "devDependencies": {
36
+ "@cubist-labs/cubesigner-sdk-fs-storage": "^0.4.183-0"
37
+ }
38
+ }
package/src/index.ts ADDED
@@ -0,0 +1,158 @@
1
+ import {
2
+ type Address,
3
+ type CustomSource,
4
+ type Hex,
5
+ type Signature,
6
+ bytesToHex,
7
+ formatTransactionRequest,
8
+ getAddress,
9
+ isHex,
10
+ parseTransaction,
11
+ serializeTransaction,
12
+ stringToHex,
13
+ } from "viem";
14
+ import {
15
+ type CubeSignerClient,
16
+ type Eip712SignRequest,
17
+ type EvmSignRequest,
18
+ EvmSigner,
19
+ type EvmSignerOptions,
20
+ type Key,
21
+ } from "@cubist-labs/cubesigner-sdk";
22
+
23
+ /**
24
+ * A class to wrap a CubeSigner key and client into a Viem {@link CustomSource}.
25
+ * Use Viem's `toAccount` to convert this to a Viem Account.
26
+ */
27
+ export class CubeSignerSource implements CustomSource {
28
+ /** The internal CubeSigner signer used. */
29
+ readonly #signer: EvmSigner;
30
+
31
+ /** The address the wallet is associated with. */
32
+ public readonly address: Address;
33
+
34
+ /**
35
+ * Construct a Viem {@link CustomSource} around a CubeSigner key and client.
36
+ * Use Viem's `toAccount` to convert this to a Viem Account.
37
+ *
38
+ * @param address The EVM address this wallet is associated with
39
+ * @param client The session used for signing. Must have necessary scopes.
40
+ * @param options MFA options for the client to respect
41
+ * @throws {Error} if the address is not a valid EVM address
42
+ */
43
+ constructor(address: Key | string, client: CubeSignerClient, options?: EvmSignerOptions) {
44
+ this.#signer = new EvmSigner(address, client, options);
45
+
46
+ // NOTE: `getAddress` will checksum the address and throw if it's an invalid EVM address.
47
+ this.address = getAddress(this.#signer.address);
48
+
49
+ // Scope these functions to properly resolve `this`
50
+ this.signMessage = this.signMessage.bind(this);
51
+ this.signTransaction = this.signTransaction.bind(this);
52
+ this.signTypedData = this.signTypedData.bind(this);
53
+ }
54
+
55
+ /**
56
+ * Signs arbitrary messages. This uses CubeSigner's EIP-191 signing endpoint.
57
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
58
+ * `"AllowEip191Signing"` policy attached.
59
+ *
60
+ * @param root Object around the message
61
+ * @param root.message The message to sign
62
+ * @returns The signature
63
+ */
64
+ public signMessage: CustomSource["signMessage"] = async ({ message }) => {
65
+ let hex;
66
+ if (typeof message === "string") {
67
+ hex = stringToHex(message);
68
+ } else if (isHex(message.raw)) {
69
+ hex = message.raw;
70
+ } else {
71
+ hex = bytesToHex(message.raw);
72
+ }
73
+
74
+ const signature = await this.#signer.signEip191({ data: hex });
75
+
76
+ return ensureHex(signature);
77
+ };
78
+
79
+ /**
80
+ * Signs EIP-712 typed data. This uses CubeSigner's EIP-712 signing endpoint.
81
+ * The key (for this session) must have the `"AllowRawBlobSigning"` or
82
+ * `"AllowEip712Signing"` policy attached.
83
+ * `chainId` must be specified within the `domain`.
84
+ *
85
+ * @param parameters Typed data
86
+ * @returns signature for the typed data
87
+ */
88
+ public signTypedData: CustomSource["signTypedData"] = async (parameters) => {
89
+ assert(parameters.domain, "`domain` must be defined");
90
+
91
+ const castedParameters = parameters as Eip712SignRequest["typed_data"];
92
+ assert(castedParameters.domain.chainId, "`domain.chainId` must be defined");
93
+
94
+ const signature = await this.#signer.signEip712({
95
+ chain_id: Number(castedParameters.domain.chainId),
96
+ typed_data: castedParameters,
97
+ });
98
+
99
+ return ensureHex(signature);
100
+ };
101
+
102
+ /**
103
+ * It is recommended to use `prepareTransactionRequest` on your request
104
+ * before calling this function.
105
+ *
106
+ * Sign a transaction. This method will block if the key requires MFA approval.
107
+ * `type` and `chainId` must be defined. Only supports type "legacy" or "eip1559".
108
+ *
109
+ * @param transaction The transaction to sign
110
+ * @param options Contains an optional custom serializer
111
+ * @returns Signed transaction
112
+ * @throws {Error} if transaction.type isn't "legacy" or "eip1559", or if "chainId" isn't specified
113
+ */
114
+ public signTransaction: CustomSource["signTransaction"] = async (transaction, options) => {
115
+ assert(
116
+ transaction.type === "legacy" || transaction.type === "eip1559",
117
+ `Unsupported transaction type '${transaction.type}', CubeSigner only supports type 'legacy' or 'eip1559'")}'`,
118
+ );
119
+
120
+ assert(transaction.chainId, "`chainId` must be defined");
121
+
122
+ const formatted = formatTransactionRequest(transaction);
123
+ const rlpSignedTransaction = await this.#signer.signTransaction({
124
+ chain_id: transaction.chainId,
125
+ tx: formatted as EvmSignRequest["tx"],
126
+ });
127
+
128
+ // CubeSigner returns an RLP-encoded transaction. Since Viem allows users to pass a custom serializer, we will
129
+ // now unserialize and reserialize with Viem's serializer.
130
+ const { r, s, v, yParity, ...parsedTransaction } = parseTransaction(
131
+ ensureHex(rlpSignedTransaction),
132
+ );
133
+ const serializer = options?.serializer ?? serializeTransaction;
134
+
135
+ return await serializer(parsedTransaction, { r, s, v, yParity } as Signature);
136
+ };
137
+ }
138
+
139
+ /**
140
+ * @param input A hex string
141
+ * @returns the input, type narrowed to Hex
142
+ * @throws {Error} if input is not Hex
143
+ */
144
+ function ensureHex(input: string): Hex {
145
+ assert(isHex(input), `${input} is not hex`);
146
+ return input;
147
+ }
148
+
149
+ /**
150
+ * @param value A value that is expected to be truthy
151
+ * @param message The error message if the value is falsy
152
+ * @throws {Error} if value is not truthy
153
+ */
154
+ function assert(value: unknown, message: string): asserts value {
155
+ if (!value) {
156
+ throw new Error(message);
157
+ }
158
+ }