@shapeshiftoss/hdwallet-vultisig 1.62.5

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.
Files changed (50) hide show
  1. package/LICENSE.md +21 -0
  2. package/dist/adapter.d.ts +22 -0
  3. package/dist/adapter.d.ts.map +1 -0
  4. package/dist/adapter.js +84 -0
  5. package/dist/adapter.js.map +1 -0
  6. package/dist/bitcoin.d.ts +6 -0
  7. package/dist/bitcoin.d.ts.map +1 -0
  8. package/dist/bitcoin.js +184 -0
  9. package/dist/bitcoin.js.map +1 -0
  10. package/dist/cosmos.d.ts +6 -0
  11. package/dist/cosmos.d.ts.map +1 -0
  12. package/dist/cosmos.js +47 -0
  13. package/dist/cosmos.js.map +1 -0
  14. package/dist/ethereum.d.ts +12 -0
  15. package/dist/ethereum.d.ts.map +1 -0
  16. package/dist/ethereum.js +193 -0
  17. package/dist/ethereum.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +19 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/solana.d.ts +9 -0
  23. package/dist/solana.d.ts.map +1 -0
  24. package/dist/solana.js +55 -0
  25. package/dist/solana.js.map +1 -0
  26. package/dist/thorchain.d.ts +6 -0
  27. package/dist/thorchain.d.ts.map +1 -0
  28. package/dist/thorchain.js +46 -0
  29. package/dist/thorchain.js.map +1 -0
  30. package/dist/types.d.ts +50 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +3 -0
  33. package/dist/types.js.map +1 -0
  34. package/dist/vultisig.d.ts +123 -0
  35. package/dist/vultisig.d.ts.map +1 -0
  36. package/dist/vultisig.js +482 -0
  37. package/dist/vultisig.js.map +1 -0
  38. package/package.json +31 -0
  39. package/src/adapter.ts +72 -0
  40. package/src/bitcoin.ts +171 -0
  41. package/src/cosmos.ts +43 -0
  42. package/src/ethereum.ts +169 -0
  43. package/src/index.ts +2 -0
  44. package/src/solana.ts +31 -0
  45. package/src/thorchain.ts +43 -0
  46. package/src/types.ts +62 -0
  47. package/src/vultisig.test.ts +253 -0
  48. package/src/vultisig.ts +459 -0
  49. package/tsconfig.json +10 -0
  50. package/tsconfig.tsbuildinfo +1 -0
package/src/bitcoin.ts ADDED
@@ -0,0 +1,171 @@
1
+ import * as bitcoin from "@shapeshiftoss/bitcoinjs-lib";
2
+ import * as core from "@shapeshiftoss/hdwallet-core";
3
+ import { BTCInputScriptType } from "@shapeshiftoss/hdwallet-core";
4
+
5
+ import { VultisigUtxoProvider } from "./types";
6
+
7
+ const getNetwork = (coin: string): bitcoin.networks.Network => {
8
+ switch (coin.toLowerCase()) {
9
+ case "bitcoin":
10
+ return bitcoin.networks.bitcoin;
11
+ default:
12
+ throw new Error(`Unsupported coin: ${coin}`);
13
+ }
14
+ };
15
+
16
+ export async function btcGetAddress(provider: VultisigUtxoProvider, msg: core.BTCGetAddress): Promise<string | null> {
17
+ const value = await (async () => {
18
+ switch (msg.coin.toLowerCase()) {
19
+ case "bitcoin": {
20
+ const accounts = await provider.request<"request_accounts">({
21
+ method: "request_accounts",
22
+ params: [],
23
+ });
24
+ return accounts.length > 0 ? accounts[0] : null;
25
+ }
26
+ default:
27
+ throw new Error("Vultisig does not support");
28
+ }
29
+ })();
30
+ if (!value || typeof value !== "string") return null;
31
+
32
+ return value;
33
+ }
34
+
35
+ export const btcGetAccountPaths = (msg: core.BTCGetAccountPaths): Array<core.BTCAccountPath> => {
36
+ const slip44 = core.slip44ByCoin(msg.coin);
37
+ if (slip44 === undefined) return [];
38
+
39
+ const bip84 = core.segwitNativeAccount(msg.coin, slip44, msg.accountIdx);
40
+
41
+ const coinPaths = {
42
+ bitcoin: [bip84],
43
+ } as Partial<Record<string, Array<core.BTCAccountPath>>>;
44
+
45
+ let paths: Array<core.BTCAccountPath> = coinPaths[msg.coin.toLowerCase()] || [];
46
+
47
+ if (msg.scriptType !== undefined) {
48
+ paths = paths.filter((path) => {
49
+ return path.scriptType === msg.scriptType;
50
+ });
51
+ }
52
+
53
+ return paths;
54
+ };
55
+
56
+ async function addInput(psbt: bitcoin.Psbt, input: core.BTCSignTxInput): Promise<void> {
57
+ switch (input.scriptType) {
58
+ case BTCInputScriptType.SpendWitness: {
59
+ psbt.addInput({
60
+ hash: input.txid,
61
+ index: input.vout,
62
+ nonWitnessUtxo: Buffer.from(input.hex, "hex"),
63
+ });
64
+
65
+ break;
66
+ }
67
+ default:
68
+ throw new Error(`Unsupported script type: ${input.scriptType}`);
69
+ }
70
+ }
71
+
72
+ async function addOutput(
73
+ provider: VultisigUtxoProvider,
74
+ psbt: bitcoin.Psbt,
75
+ output: core.BTCSignTxOutput,
76
+ coin: string
77
+ ): Promise<void> {
78
+ if (!output.amount) throw new Error("Invalid output - missing amount.");
79
+
80
+ const address = await (async () => {
81
+ if (output.address) return output.address;
82
+
83
+ if (output.addressNList) {
84
+ const outputAddress = await btcGetAddress(provider, {
85
+ addressNList: output.addressNList,
86
+ coin,
87
+ showDisplay: false,
88
+ });
89
+ if (!outputAddress) throw new Error("Could not get address from wallet");
90
+ return outputAddress;
91
+ }
92
+ })();
93
+
94
+ if (!address) throw new Error("Invalid output - no address");
95
+
96
+ psbt.addOutput({ address, value: BigInt(output.amount) });
97
+ }
98
+
99
+ export async function bitcoinSignTx(
100
+ msg: core.BTCSignTx,
101
+ provider: VultisigUtxoProvider
102
+ ): Promise<core.BTCSignedTx | null> {
103
+ try {
104
+ const network = getNetwork(msg.coin);
105
+ const psbt = new bitcoin.Psbt({ network });
106
+
107
+ psbt.setVersion(msg.version ?? 2);
108
+ if (msg.locktime) {
109
+ psbt.setLocktime(msg.locktime);
110
+ }
111
+
112
+ for (const input of msg.inputs) {
113
+ await addInput(psbt, input);
114
+ }
115
+
116
+ for (const output of msg.outputs) {
117
+ await addOutput(provider, psbt, output, msg.coin);
118
+ }
119
+
120
+ if (msg.opReturnData) {
121
+ const data = Buffer.from(msg.opReturnData, "utf-8");
122
+ const embed = bitcoin.payments.embed({ data: [data] });
123
+ const script = embed.output;
124
+ if (!script) throw new Error("unable to build OP_RETURN script");
125
+ // OP_RETURN_DATA output is always 0 value
126
+ psbt.addOutput({ script, value: BigInt(0) });
127
+ }
128
+
129
+ const inputsToSign = await Promise.all(
130
+ msg.inputs.map(async (input, index) => {
131
+ const address = await btcGetAddress(provider, {
132
+ addressNList: input.addressNList,
133
+ coin: msg.coin,
134
+ showDisplay: false,
135
+ });
136
+
137
+ if (!address) throw new Error("Could not get address from wallet");
138
+
139
+ return {
140
+ address,
141
+ signingIndexes: [index],
142
+ sigHash: bitcoin.Transaction.SIGHASH_ALL,
143
+ };
144
+ })
145
+ );
146
+
147
+ const signedPsbtBuffer = await provider.signPSBT(psbt.toBuffer(), { inputsToSign }, false);
148
+ const signedPsbt = bitcoin.Psbt.fromBuffer(signedPsbtBuffer, { network });
149
+
150
+ signedPsbt.finalizeAllInputs();
151
+
152
+ const tx = signedPsbt.extractTransaction();
153
+
154
+ // If this is a THORChain transaction, validate the vout ordering
155
+ if (msg.vaultAddress && !core.validateVoutOrdering(msg, tx)) {
156
+ throw new Error("Improper vout ordering for BTC Thorchain transaction");
157
+ }
158
+
159
+ const signatures = signedPsbt.data.inputs.map((input) =>
160
+ input.partialSig ? Buffer.from(input.partialSig[0].signature).toString("hex") : ""
161
+ );
162
+
163
+ return {
164
+ signatures,
165
+ serializedTx: tx.toHex(),
166
+ };
167
+ } catch (error) {
168
+ console.error("Error signing with Vultisig:", error);
169
+ return null;
170
+ }
171
+ }
package/src/cosmos.ts ADDED
@@ -0,0 +1,43 @@
1
+ import { StdTx } from "@cosmjs/amino";
2
+ import { SignerData } from "@cosmjs/stargate";
3
+ import {
4
+ CosmosAccountPath,
5
+ CosmosGetAccountPaths,
6
+ CosmosSignedTx,
7
+ CosmosSignTx,
8
+ slip44ByCoin,
9
+ } from "@shapeshiftoss/hdwallet-core";
10
+ import { sign } from "@shapeshiftoss/proto-tx-builder";
11
+
12
+ import { VultisigOfflineProvider } from "./types";
13
+
14
+ const ATOM_CHAIN = "cosmoshub-4";
15
+
16
+ export function cosmosGetAccountPaths(msg: CosmosGetAccountPaths): Array<CosmosAccountPath> {
17
+ return [
18
+ {
19
+ addressNList: [0x80000000 + 44, 0x80000000 + slip44ByCoin("Atom"), 0x80000000 + msg.accountIdx, 0, 0],
20
+ },
21
+ ];
22
+ }
23
+
24
+ export async function cosmosGetAddress(provider: VultisigOfflineProvider): Promise<string | undefined> {
25
+ const offlineSigner = provider.getOfflineSigner(ATOM_CHAIN);
26
+ const accounts = await offlineSigner.getAccounts();
27
+ return accounts[0]?.address;
28
+ }
29
+
30
+ export async function cosmosSignTx(provider: VultisigOfflineProvider, msg: CosmosSignTx): Promise<CosmosSignedTx> {
31
+ const offlineSigner = provider.getOfflineSigner(ATOM_CHAIN);
32
+
33
+ const address = await cosmosGetAddress(provider);
34
+ if (!address) throw new Error("failed to get address");
35
+
36
+ const signerData: SignerData = {
37
+ sequence: Number(msg.sequence),
38
+ accountNumber: Number(msg.account_number),
39
+ chainId: msg.chain_id,
40
+ };
41
+
42
+ return await sign(address, msg.tx as StdTx, offlineSigner, signerData, "cosmos");
43
+ }
@@ -0,0 +1,169 @@
1
+ import * as core from "@shapeshiftoss/hdwallet-core";
2
+ import { ETHSignedMessage } from "@shapeshiftoss/hdwallet-core";
3
+ import { AddEthereumChainParameter } from "@shapeshiftoss/hdwallet-core";
4
+ import { ethErrors, serializeError } from "eth-rpc-errors";
5
+ import { isHexString } from "ethers/lib/utils";
6
+
7
+ import { VultisigEvmProvider } from "./types";
8
+
9
+ export function ethGetAccountPaths(msg: core.ETHGetAccountPath): Array<core.ETHAccountPath> {
10
+ const slip44 = core.slip44ByCoin(msg.coin);
11
+ if (slip44 === undefined) return [];
12
+ return [
13
+ {
14
+ addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0, 0],
15
+ hardenedPath: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx],
16
+ relPath: [0, 0],
17
+ description: "Vultisig",
18
+ },
19
+ ];
20
+ }
21
+
22
+ export async function ethSendTx(
23
+ msg: core.ETHSignTx,
24
+ vultisig: VultisigEvmProvider,
25
+ from: string
26
+ ): Promise<core.ETHTxHash | null> {
27
+ try {
28
+ const utxBase = {
29
+ from: from,
30
+ to: msg.to,
31
+ value: msg.value,
32
+ nonce: msg.nonce,
33
+ chainId: msg.chainId,
34
+ data: msg.data,
35
+ gas: msg.gasLimit,
36
+ };
37
+
38
+ const utx = msg.maxFeePerGas
39
+ ? {
40
+ ...utxBase,
41
+ maxFeePerGas: msg.maxFeePerGas,
42
+ maxPriorityFeePerGas: msg.maxPriorityFeePerGas,
43
+ }
44
+ : { ...utxBase, gasPrice: msg.gasPrice };
45
+
46
+ const signedTx = await vultisig.request?.({
47
+ method: "eth_sendTransaction",
48
+ params: [utx],
49
+ });
50
+
51
+ return { hash: signedTx } as core.ETHTxHash;
52
+ } catch (error) {
53
+ console.error(error);
54
+ return null;
55
+ }
56
+ }
57
+
58
+ export async function ethSignMessage(
59
+ msg: core.ETHSignMessage,
60
+ vultisig: VultisigEvmProvider,
61
+ address: string
62
+ ): Promise<core.ETHSignedMessage | null> {
63
+ try {
64
+ if (!isHexString(msg.message)) throw new Error("data is not an hex string");
65
+ const signedMsg = await vultisig.request?.({
66
+ method: "personal_sign",
67
+ params: [msg.message, address],
68
+ });
69
+
70
+ return {
71
+ address: address,
72
+ signature: signedMsg,
73
+ } as ETHSignedMessage;
74
+ } catch (error) {
75
+ console.error(error);
76
+ return null;
77
+ }
78
+ }
79
+
80
+ export async function ethSignTypedData(
81
+ msg: core.ETHSignTypedData,
82
+ vultisig: VultisigEvmProvider,
83
+ address: string
84
+ ): Promise<core.ETHSignedMessage | null> {
85
+ try {
86
+ const signedMsg = await vultisig.request?.({
87
+ method: "eth_signTypedData_v4",
88
+ params: [address, JSON.stringify(msg.typedData)],
89
+ });
90
+
91
+ return {
92
+ address: address,
93
+ signature: signedMsg,
94
+ } as ETHSignedMessage;
95
+ } catch (error) {
96
+ console.error(error);
97
+ return null;
98
+ }
99
+ }
100
+
101
+ export async function ethGetAddress(vultisig: VultisigEvmProvider): Promise<core.Address | null> {
102
+ if (!(vultisig && vultisig.request)) {
103
+ return null;
104
+ }
105
+ try {
106
+ const ethAccounts = await vultisig.request({
107
+ method: "eth_accounts",
108
+ });
109
+ return ethAccounts[0];
110
+ } catch (error) {
111
+ console.error(error);
112
+ return null;
113
+ }
114
+ }
115
+ export async function ethGetChainId(evmProvider: VultisigEvmProvider): Promise<number | null> {
116
+ try {
117
+ // chainId as hex string
118
+ const chainId: string = (await evmProvider?.request?.({ method: "eth_chainId" })) || "";
119
+ return parseInt(chainId, 16);
120
+ } catch (e) {
121
+ console.error(e);
122
+ return null;
123
+ }
124
+ }
125
+
126
+ export async function ethAddChain(evmProvider: VultisigEvmProvider, params: AddEthereumChainParameter): Promise<void> {
127
+ // at this point, we know that we're in the context of a valid Coinbase provider
128
+ await evmProvider?.request?.({ method: "wallet_addEthereumChain", params: [params] });
129
+ }
130
+
131
+ export async function ethSwitchChain(
132
+ evmProvider: VultisigEvmProvider,
133
+ params: AddEthereumChainParameter
134
+ ): Promise<void> {
135
+ try {
136
+ // at this point, we know that we're in the context of a valid Coinbase provider
137
+ await evmProvider?.request?.({ method: "wallet_switchEthereumChain", params: [{ chainId: params.chainId }] });
138
+ } catch (e: any) {
139
+ const error = serializeError(e);
140
+ if (error.code === -32603) {
141
+ try {
142
+ await ethAddChain(evmProvider, params);
143
+ return;
144
+ } catch (addChainE: any) {
145
+ const addChainError = serializeError(addChainE);
146
+
147
+ if (addChainError.code === 4001) {
148
+ throw ethErrors.provider.userRejectedRequest();
149
+ }
150
+
151
+ throw (addChainError.data as any).originalError as {
152
+ code: number;
153
+ message: string;
154
+ stack: string;
155
+ };
156
+ }
157
+ }
158
+
159
+ if (error.code === 4001) {
160
+ throw ethErrors.provider.userRejectedRequest();
161
+ }
162
+
163
+ throw (error.data as any).originalError as {
164
+ code: number;
165
+ message: string;
166
+ stack: string;
167
+ };
168
+ }
169
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./adapter";
2
+ export * from "./vultisig";
package/src/solana.ts ADDED
@@ -0,0 +1,31 @@
1
+ import * as core from "@shapeshiftoss/hdwallet-core";
2
+ import { PublicKey } from "@solana/web3.js";
3
+
4
+ import { VultisigSolanaProvider } from "./types";
5
+
6
+ export type SolanaAccount = {
7
+ publicKey: PublicKey;
8
+ };
9
+
10
+ export async function solanaSignTx(
11
+ msg: core.SolanaSignTx,
12
+ provider: VultisigSolanaProvider,
13
+ address: string
14
+ ): Promise<core.SolanaSignedTx | null> {
15
+ const transaction = core.solanaBuildTransaction(msg, address);
16
+ const signedTransaction = await provider.signTransaction(transaction);
17
+ return {
18
+ serialized: Buffer.from(signedTransaction.serialize()).toString("base64"),
19
+ signatures: signedTransaction.signatures.map((signature) => Buffer.from(signature).toString("base64")),
20
+ };
21
+ }
22
+
23
+ export async function solanaSendTx(
24
+ msg: core.SolanaSignTx,
25
+ provider: VultisigSolanaProvider,
26
+ address: string
27
+ ): Promise<core.SolanaTxSignature | null> {
28
+ const transaction = core.solanaBuildTransaction(msg, address);
29
+ const { signature } = await provider.signAndSendTransaction(transaction);
30
+ return { signature };
31
+ }
@@ -0,0 +1,43 @@
1
+ import { StdTx } from "@cosmjs/amino";
2
+ import { SignerData } from "@cosmjs/stargate";
3
+ import {
4
+ CosmosAccountPath,
5
+ CosmosGetAccountPaths,
6
+ CosmosSignedTx,
7
+ CosmosSignTx,
8
+ slip44ByCoin,
9
+ } from "@shapeshiftoss/hdwallet-core";
10
+ import { sign } from "@shapeshiftoss/proto-tx-builder";
11
+
12
+ import { VultisigOfflineProvider } from "./types";
13
+
14
+ export function thorchainGetAccountPaths(msg: CosmosGetAccountPaths): Array<CosmosAccountPath> {
15
+ return [
16
+ {
17
+ addressNList: [0x80000000 + 44, 0x80000000 + slip44ByCoin("Thorchain"), 0x80000000 + msg.accountIdx, 0, 0],
18
+ },
19
+ ];
20
+ }
21
+
22
+ export async function thorchainGetAddress(provider: VultisigOfflineProvider): Promise<string | undefined> {
23
+ const offlineSigner = provider.getOfflineSigner("thorchain-1");
24
+ const accounts = await offlineSigner.getAccounts();
25
+ return accounts[0].address;
26
+ }
27
+
28
+ export async function thorchainSignTx(provider: VultisigOfflineProvider, msg: CosmosSignTx): Promise<CosmosSignedTx> {
29
+ const offlineSigner = provider.getOfflineSigner(msg.chain_id);
30
+
31
+ const address = await thorchainGetAddress(provider);
32
+ if (!address) throw new Error("failed to get address");
33
+
34
+ const signerData: SignerData = {
35
+ sequence: Number(msg.sequence),
36
+ accountNumber: Number(msg.account_number),
37
+ chainId: msg.chain_id,
38
+ };
39
+
40
+ const result = await sign(address, msg.tx as StdTx, offlineSigner, signerData, "thor");
41
+
42
+ return result;
43
+ }
package/src/types.ts ADDED
@@ -0,0 +1,62 @@
1
+ import { OfflineSigner } from "@cosmjs/proto-signing";
2
+ import { PublicKey, VersionedTransaction } from "@solana/web3.js";
3
+ import { TransactionSignature } from "@solana/web3.js";
4
+ import { providers } from "ethers";
5
+
6
+ import { SolanaAccount } from "./solana";
7
+
8
+ export type VultisigRequestParams = {
9
+ get_accounts: [];
10
+ request_accounts: [];
11
+ };
12
+
13
+ type VultisigRequestReturn = {
14
+ get_accounts: Promise<string[]>;
15
+ request_accounts: Promise<string[]>;
16
+ };
17
+
18
+ type VultisigRequestMethod = keyof VultisigRequestParams;
19
+
20
+ export type VultisigRequestPayload<M extends VultisigRequestMethod> = {
21
+ method: M;
22
+ params: VultisigRequestParams[M];
23
+ };
24
+
25
+ export type VultisigEvmProvider = providers.ExternalProvider;
26
+
27
+ export type VultisigUtxoProvider = {
28
+ request<M extends VultisigRequestMethod>(payload: VultisigRequestPayload<M>): Promise<VultisigRequestReturn[M]>;
29
+ signPSBT(
30
+ psbt: Uint8Array,
31
+ {
32
+ inputsToSign,
33
+ }: {
34
+ inputsToSign: {
35
+ address: string;
36
+ signingIndexes: number[];
37
+ sigHash?: number;
38
+ }[];
39
+ },
40
+ broadcast: boolean
41
+ ): Promise<Uint8Array>;
42
+ };
43
+
44
+ export type VultisigSolanaProvider = providers.ExternalProvider & {
45
+ publicKey?: PublicKey;
46
+ connect(): Promise<SolanaAccount>;
47
+ signTransaction(transaction: VersionedTransaction): Promise<VersionedTransaction>;
48
+ signAndSendTransaction(transaction: VersionedTransaction): Promise<{ signature: TransactionSignature }>;
49
+ };
50
+
51
+ export type VultisigOfflineProvider = providers.ExternalProvider & {
52
+ getOfflineSigner(chainId: string): OfflineSigner;
53
+ };
54
+
55
+ export type VultisigGetVault = {
56
+ hexChainCode: string;
57
+ isFastVault: boolean;
58
+ name: string;
59
+ publicKeyEcdsa: string;
60
+ publicKeyEddsa: string;
61
+ uid: string;
62
+ };