Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: HW wallet support for BTC, LTC and DOGE #504

Merged
merged 15 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions packages/extension/src/libs/utils/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EnkryptAccount, NetworkNames, SignerType } from "@enkryptcom/types";
import PublicKeyRing from "../keyring/public-keyring";
import { getNetworkByName } from "./networks";
import { ProviderName } from "@/types/provider";

const getOtherSigners = (signers: SignerType[]): SignerType[] => {
const otherSigners: SignerType[] = [];
Expand All @@ -22,16 +23,13 @@ export const getAccountsByNetworkName = async (
const accounts = await keyring.getAccounts(network.signer);

const filtered = accounts.filter((account) => {
if (account.isHardware && account.HWOptions !== undefined) {
// Polkadot and Kusama ledger apps only work for those networks
if (
account.HWOptions.networkName === NetworkNames.Kusama ||
account.HWOptions.networkName === NetworkNames.Polkadot
) {
return account.HWOptions.networkName === networkName;
}
if (
account.isHardware &&
account.HWOptions !== undefined &&
network.provider !== ProviderName.ethereum
) {
return account.HWOptions.networkName === networkName;
}

return true;
});
return filtered.map((f) => {
Expand Down
9 changes: 9 additions & 0 deletions packages/extension/src/providers/bitcoin/libs/api-ss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ class API implements ProviderAPIInterface {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
async init(): Promise<void> {}
async getRawTransaction(hash: string): Promise<string | null> {
return fetch(`${this.node}/api/v1/tx/${hash}/raw`)
.then((res) => res.json())
.then((tx: { hex: string; error: unknown }) => {
if ((tx as any).error) return null;
if (!tx.hex) return null;
return `0x${tx.hex}`;
});
}
async getTransactionStatus(hash: string): Promise<BTCRawInfo | null> {
return fetch(`${this.node}/api/v1/tx/${hash}`)
.then((res) => res.json())
Expand Down
9 changes: 9 additions & 0 deletions packages/extension/src/providers/bitcoin/libs/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ class API implements ProviderAPIInterface {
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
async init(): Promise<void> {}
async getRawTransaction(hash: string): Promise<string | null> {
return fetch(`${this.node}transaction/${hash}/raw`)
.then((res) => res.json())
.then((tx: { result: string; error: unknown }) => {
if ((tx as any).error) return null;
if (!tx.result) return null;
return `0x${tx.result}`;
});
}
async getTransactionStatus(hash: string): Promise<BTCRawInfo | null> {
return fetch(`${this.node}transaction/${hash}`)
.then((res) => res.json())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ import { DEFAULT_BTC_NETWORK, getNetworkByName } from "@/libs/utils/networks";
import { WindowPromiseHandler } from "@/libs/window-promise";
import { BitcoinNetwork } from "../types/bitcoin-network";
import { ProviderRequestOptions } from "@/types/provider";
import PublicKeyRing from "@/libs/keyring/public-keyring";
import { fromBase } from "@enkryptcom/utils";
import { getError } from "@/libs/error";
import { ErrorCodes } from "@/providers/ethereum/types";
import { getAccountsByNetworkName } from "@/libs/utils/accounts";
import AccountState from "../libs/accounts-state";

const windowPromise = WindowPromiseHandler(1);
Expand Down Expand Up @@ -109,9 +109,8 @@ onBeforeMount(async () => {
network.value = (await getNetworkByName(
Request.value.params![0]
)) as BitcoinNetwork;
const keyring = new PublicKeyRing();
Options.value = options;
const accounts = await keyring.getAccounts(network.value.signer);
const accounts = await getAccountsByNetworkName(network.value.name);
displayAddress.value = network.value.displayAddress(accounts[0].address);
identicon.value = network.value.identicon(accounts[0].address);
accountHeaderData.value = {
Expand Down
91 changes: 58 additions & 33 deletions packages/extension/src/providers/bitcoin/ui/libs/signer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { InternalMethods, InternalOnMessageResponse } from "@/types/messenger";
import { SignerTransactionOptions, SignerMessageOptions } from "../types";
import sendUsingInternalMessengers from "@/libs/messenger/internal-messenger";
import { hexToBuffer, bufferToHex } from "@enkryptcom/utils";
import { Psbt } from "bitcoinjs-lib";
import { Psbt, Transaction } from "bitcoinjs-lib";
import { BitcoinNetwork, PaymentType } from "../../types/bitcoin-network";
import { EnkryptAccount } from "@enkryptcom/types";
import { EnkryptAccount, HWwalletType } from "@enkryptcom/types";
import { signMessageOfBIP322Simple } from "../../libs/bip322-message-sign";
import { magicHash, toCompact } from "../../libs/sign-message-utils";
import HWwallet from "@enkryptcom/hw-wallets";
import type BitcoinAPI from "@/providers/bitcoin/libs/api";

const PSBTSigner = (account: EnkryptAccount, network: BitcoinNetwork) => {
return {
Expand All @@ -29,44 +31,67 @@ const PSBTSigner = (account: EnkryptAccount, network: BitcoinNetwork) => {
};
};

const TransactionSigner = (
const TransactionSigner = async (
options: SignerTransactionOptions
): Promise<Psbt> => {
): Promise<Transaction> => {
const { account, network, payload } = options;
if (account.isHardware) {
throw new Error("btc-hardware not implemented");
} else {
const tx = new Psbt({
network: network.networkInfo,
maximumFeeRate: network.networkInfo.maxFeeRate,
});
payload.inputs
.map((u) => {
const res: {
hash: string;
index: number;
witnessUtxo?: { script: Buffer; value: number };
nonWitnessUtxo?: Buffer;
} = {
hash: u.hash,
index: u.index,
const tx = new Psbt({
network: network.networkInfo,
maximumFeeRate: network.networkInfo.maxFeeRate,
});
payload.inputs
.map((u) => {
const res: {
hash: string;
index: number;
witnessUtxo?: { script: Buffer; value: number };
nonWitnessUtxo?: Buffer;
} = {
hash: u.hash,
index: u.index,
};
if (network.networkInfo.paymentType === PaymentType.P2WPKH) {
res.witnessUtxo = {
script: Buffer.from(u.witnessUtxo.script, "hex"),
value: u.witnessUtxo.value,
};
if (network.networkInfo.paymentType === PaymentType.P2WPKH) {
res.witnessUtxo = {
script: Buffer.from(u.witnessUtxo.script, "hex"),
value: u.witnessUtxo.value,
};
} else if (network.networkInfo.paymentType === PaymentType.P2PKH) {
res.nonWitnessUtxo = Buffer.from(u.raw, "hex");
}
return res;
} else if (network.networkInfo.paymentType === PaymentType.P2PKH) {
res.nonWitnessUtxo = Buffer.from(u.raw, "hex");
}
return res;
})
.forEach((input) => tx.addInput(input));
payload.outputs.forEach((output) => tx.addOutput(output));
if (account.isHardware) {
const hwwallets = new HWwallet();
const api = (await network.api()) as BitcoinAPI;
const txPromises = payload.inputs.map((u) => api.getRawTransaction(u.hash));
const rawTxs = await Promise.all(txPromises);
for (const t of rawTxs) {
if (t === null) throw new Error("bitcoin-signer: Invalid tx hash");
}
return hwwallets
.signTransaction({
transaction: {
rawTxs: rawTxs as string[],
psbtTx: tx,
},
networkName: network.name,
pathIndex: account.pathIndex.toString(),
pathType: {
basePath: account.basePath,
path: account.HWOptions!.pathTemplate,
},
wallet: account.walletType as unknown as HWwalletType,
})
.forEach((input) => tx.addInput(input));
payload.outputs.forEach((output) => tx.addOutput(output));
.then((strTx) => {
return Transaction.fromHex(strTx);
});
} else {
const signer = PSBTSigner(account, network);
return tx.signAllInputsAsync(signer).then(() => {
tx.finalizeAllInputs();
return tx;
return tx.extractTransaction();
});
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,8 +493,8 @@ const sendAction = async () => {
await Browser.windows.create({
url: Browser.runtime.getURL(
getUiPath(
`eth-hw-verify?id=${routedRoute.query.id}&txData=${routedRoute.query.txData}`,
ProviderName.ethereum
`btc-hw-verify?id=${routedRoute.query.id}&txData=${routedRoute.query.txData}`,
ProviderName.bitcoin
)
),
type: "popup",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ const sendAction = async () => {
})
.then((signedTx) => {
api
.broadcastTx(signedTx.extractTransaction().toHex())
.broadcastTx(signedTx.toHex())
.then(() => {
trackSendEvents(SendEventType.SendComplete, {
network: network.value.name,
Expand All @@ -172,7 +172,7 @@ const sendAction = async () => {
[
{
...txActivity,
...{ transactionHash: signedTx.extractTransaction().getId() },
...{ transactionHash: signedTx.getId() },
},
],
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ const pasteFromClipboard = () => {
position: relative;
margin: auto;
width: 100%;
max-height: 380px;
max-height: 330px;
}

h3 {
Expand Down
Loading
Loading