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

OK-27295: Add Babylon BTC Web3 Provider #4507

Merged
merged 8 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,17 +65,17 @@
"@cmdcode/crypto-utils": "1.9.5",
"@cmdcode/tapscript": "1.2.9",
"@legendapp/state": "^1.2.9",
"@onekeyfe/cross-inpage-provider-core": "1.1.58",
"@onekeyfe/cross-inpage-provider-errors": "1.1.58",
"@onekeyfe/cross-inpage-provider-injected": "1.1.58",
"@onekeyfe/cross-inpage-provider-types": "1.1.58",
"@onekeyfe/extension-bridge-hosted": "1.1.58",
"@onekeyfe/cross-inpage-provider-core": "1.1.59",
"@onekeyfe/cross-inpage-provider-errors": "1.1.59",
"@onekeyfe/cross-inpage-provider-injected": "1.1.59",
"@onekeyfe/cross-inpage-provider-types": "1.1.59",
"@onekeyfe/extension-bridge-hosted": "1.1.59",
"@onekeyfe/hd-ble-sdk": "0.3.44",
"@onekeyfe/hd-core": "0.3.44",
"@onekeyfe/hd-shared": "0.3.44",
"@onekeyfe/hd-transport": "0.3.44",
"@onekeyfe/hd-web-sdk": "0.3.44",
"@onekeyfe/onekey-cross-webview": "1.1.58",
"@onekeyfe/onekey-cross-webview": "1.1.59",
"@starcoin/starcoin": "2.1.5",
"@web3-react/core": "8.0.35-beta.0",
"@web3-react/empty": "8.0.20-beta.0",
Expand Down
45 changes: 33 additions & 12 deletions packages/engine/src/vaults/impl/btc/provider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as BitcoinJS from 'bitcoinjs-lib';
import { toXOnly } from 'bitcoinjs-lib/src/psbt/bip371';
import { isTaprootInput, toXOnly } from 'bitcoinjs-lib/src/psbt/bip371';

import { memoizee } from '@onekeyhq/shared/src/utils/cacheUtils';

Expand All @@ -25,7 +25,9 @@ function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
export function tweakSigner(
privKey: Buffer,
publicKey: Buffer,
opts: { tweakHash?: Buffer; network?: Network } = {},
opts: { tweakHash?: Buffer; network?: Network; needTweak: boolean } = {
needTweak: true,
},
): BitcoinJS.Signer {
let privateKey: Uint8Array | null = new Uint8Array(privKey.buffer);
if (!privateKey) {
Expand All @@ -43,13 +45,17 @@ export function tweakSigner(
privateKey,
tapTweakHash(toXOnly(publicKey), opts.tweakHash),
);

if (!tweakedPrivateKey) {
throw new Error('Invalid tweaked private key!');
}

return getBitcoinECPair().fromPrivateKey(Buffer.from(tweakedPrivateKey), {
network: opts.network,
});
return getBitcoinECPair().fromPrivateKey(
Buffer.from(opts.needTweak ? tweakedPrivateKey : privateKey),
{
network: opts.network,
},
);
}

export default class Provider extends BaseProvider {
Expand Down Expand Up @@ -78,13 +84,28 @@ export default class Provider extends BaseProvider {
const publicKey = await signer.getPubkey(true);

// P2TR
if (input.tapInternalKey) {
const privateKey = await signer.getPrvkey();
const tweakedSigner = tweakSigner(privateKey, publicKey, {
network: this.network,
});

return tweakedSigner;
if (isTaprootInput(input)) {
let needTweak = true;
// script path spend
if (
input.tapLeafScript &&
input.tapLeafScript?.length > 0 &&
!input.tapMerkleRoot
) {
input.tapLeafScript.forEach((e) => {
if (e.controlBlock && e.script) {
needTweak = false;
}
});
}
if (input.tapInternalKey) {
const privateKey = await signer.getPrvkey();
const tweakedSigner = tweakSigner(privateKey, publicKey, {
network: this.network,
needTweak,
});
return tweakedSigner;
}
}

// For other encoding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@ export class KeyringHd extends KeyringHdBase {
throw new OneKeyInternalError('Software signing requires a password.');
}

const dbAccount = (await this.getDbAccount()) as DBUTXOAccount;
const { transferInfo } = unsignedTx.encodedTx as IEncodedTxBtc;
const signers = await this.getSigners(
password,
(inputsToSign || unsignedTx.inputs).map((input) => input.address),
[
...(inputsToSign || unsignedTx.inputs).map((input) => input.address),
...Object.values(dbAccount.addresses),
],
transferInfo.useCustomAddressesBalance,
);
debugLogger.engine.info('signTransaction', this.networkId, unsignedTx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,9 @@ export default class VaultBtcFork extends VaultBase {
}

decodedTxToLegacy(decodedTx: IDecodedTx): Promise<IDecodedTxLegacy> {
if (!decodedTx.actions || decodedTx.actions.length === 0) {
return Promise.resolve({} as IDecodedTxLegacy);
}
const { type, nativeTransfer, inscriptionInfo } = decodedTx.actions[0];
if (type === IDecodedTxActionType.NATIVE_TRANSFER && nativeTransfer) {
return Promise.resolve({
Expand Down Expand Up @@ -1579,7 +1582,7 @@ export default class VaultBtcFork extends VaultBase {
},
);

private getFeeRate = memoizee(
getFeeRate = memoizee(
async () => {
// getRpcClient
const client = await (await this.getProvider()).blockbook;
Expand Down
93 changes: 90 additions & 3 deletions packages/kit-bg/src/providers/ProviderApiBtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,15 @@ class ProviderApiBtc extends ProviderApiBase {
) {
const { psbtHexs, options } = params;

const { network } = getActiveWalletAccount();
const { network, wallet } = getActiveWalletAccount();
if (wallet?.type === 'hw') {
throw web3Errors.provider.custom({
code: 4003,
message:
'Partially signed bitcoin transactions is not supported on hardware.',
});
}

if (!network) return null;

const psbtNetwork = toPsbtNetwork(network);
Expand Down Expand Up @@ -537,13 +545,14 @@ class ProviderApiBtc extends ProviderApiBase {
psbt,
psbtNetwork,
account,
isBtcWalletProvider: options.isBtcWalletProvider,
});

const resp = (await this.backgroundApi.serviceDapp.openSignAndSendModal(
request,
{
encodedTx: {
inputs: decodedPsbt.inputInfos.map((v) => ({
inputs: (decodedPsbt.inputInfos ?? []).map((v) => ({
...v,
path: '',
value: v.value.toString(),
Expand All @@ -553,7 +562,7 @@ class ProviderApiBtc extends ProviderApiBase {
),
),
})),
outputs: decodedPsbt.outputInfos.map((v) => ({
outputs: (decodedPsbt.outputInfos ?? []).map((v) => ({
...v,
value: v.value.toString(),
inscriptions: v.inscriptions.map((i) =>
Expand Down Expand Up @@ -589,8 +598,86 @@ class ProviderApiBtc extends ProviderApiBase {
});
}

if (options.isBtcWalletProvider) {
return respPsbt.extractTransaction().toHex();
}
return respPsbt.toHex();
}

@providerApiMethod()
public async getNetworkFees() {
const { account, network } = getActiveWalletAccount();
if (!account || !network) return null;
const vault = (await this.backgroundApi.engine.getVault({
networkId: network.id,
accountId: account.id,
})) as VaultBtcFork;

const result = await vault.getFeeRate();
if (result.length !== 3) {
throw new Error('Invalid fee rate');
}
const [hourFee, halfHourFee, fastestFee] = result;
return {
fastestFee,
halfHourFee,
hourFee,
economyFee: hourFee,
minimumFee: hourFee,
};
}

@providerApiMethod()
public async getUtxos(
request: IJsBridgeMessagePayload,
params: {
address: string;
amount: number;
},
) {
const { account, network } = getActiveWalletAccount();
if (!account || !network) return null;
const vault = (await this.backgroundApi.engine.getVault({
networkId: network.id,
accountId: account.id,
})) as VaultBtcFork;
const { utxos } = await vault.collectUTXOsInfo({
checkInscription: true,
});
const confirmedUtxos = utxos.filter(
(v) => v.address === params.address && Number(v?.confirmations ?? 0) > 0,
);
let sum = 0;
let index = 0;
for (const utxo of confirmedUtxos) {
sum += new BigNumber(utxo.value).toNumber();
index += 1;
if (sum > params.amount) {
break;
}
}
if (sum < params.amount) {
return [];
}
const sliced = confirmedUtxos.slice(0, index);
const result = [];
for (const utxo of sliced) {
const txDetails =
await this.backgroundApi.serviceTransaction.getTransactionDetail({
txId: utxo.txid,
networkId: network.id,
});
result.push({
txid: utxo.txid,
vout: utxo.vout,
value: new BigNumber(utxo.value).toNumber(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
scriptPubKey: txDetails?.vout?.[utxo.vout].hex ?? '',
});
}

return result;
}
}

export default ProviderApiBtc;
4 changes: 3 additions & 1 deletion packages/shared/src/engine/engineConsts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,4 +292,6 @@ export const isLightningNetworkByNetworkId = (networkId?: string) =>
networkId === OnekeyNetwork.lightning ||
networkId === OnekeyNetwork.tlightning;
export const isBTCNetwork = (networkId?: string) =>
networkId === OnekeyNetwork.btc || networkId === OnekeyNetwork.tbtc;
networkId === OnekeyNetwork.btc ||
networkId === OnekeyNetwork.tbtc ||
networkId === OnekeyNetwork.sbtc;
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ export type PushTxParams = {
};
export type SignPsbtParams = {
psbtHex: string;
options: { autoFinalized: boolean };
options: { autoFinalized: boolean; isBtcWalletProvider: boolean };
};

export type SignPsbtsParams = {
psbtHexs: string[];
options: { autoFinalized: boolean };
options: { autoFinalized: boolean; isBtcWalletProvider: boolean };
};

export type PushPsbtParams = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ import {
} from '@onekeyhq/engine/src/types/nft';
import type { IDecodedTxAction } from '@onekeyhq/engine/src/vaults/types';
import { IDecodedTxActionType } from '@onekeyhq/engine/src/vaults/types';
import { initBitcoinEcc } from '@onekeyhq/engine/src/vaults/utils/btcForkChain/utils';

import { isBTCNetwork } from '../../engine/engineConsts';

import { NETWORK_TYPES, NetworkTypeEnum } from './ProviderApiBtc.types';

import type { InputToSign, Inscription } from './ProviderApiBtc.types';
import type {
InputToSign,
Inscription,
SignPsbtParams,
} from './ProviderApiBtc.types';

export const OPENAPI_URL_MAINNET = 'https://wallet-api.unisat.io/v5';
export const OPENAPI_URL_TESTNET = 'https://wallet-api-testnet.unisat.io/v5';
Expand Down Expand Up @@ -109,14 +114,32 @@ export function mapInscriptionToNFTBTCAssetModel(inscription: Inscription) {
return asset;
}

function scriptPkToAddress(
scriptPk: string | Buffer,
psbtNetwork: BitcoinJS.networks.Network,
) {
initBitcoinEcc();
try {
const address = BitcoinJS.address.fromOutputScript(
typeof scriptPk === 'string' ? Buffer.from(scriptPk, 'hex') : scriptPk,
psbtNetwork,
);
return address;
} catch (e) {
return '';
}
}

export function getInputsToSignFromPsbt({
psbt,
psbtNetwork,
account,
isBtcWalletProvider,
}: {
account: Account;
psbt: BitcoinJS.Psbt;
psbtNetwork: BitcoinJS.networks.Network;
isBtcWalletProvider: SignPsbtParams['options']['isBtcWalletProvider'];
}) {
const inputsToSign: InputToSign[] = [];
psbt.data.inputs.forEach((v, index) => {
Expand All @@ -134,7 +157,7 @@ export function getInputsToSignFromPsbt({
const isSigned = v.finalScriptSig || v.finalScriptWitness;

if (script && !isSigned) {
const address = BitcoinJS.address.fromOutputScript(script, psbtNetwork);
const address = scriptPkToAddress(script, psbtNetwork);
if (account.address === address) {
inputsToSign.push({
index,
Expand All @@ -147,6 +170,14 @@ export function getInputsToSignFromPsbt({
Buffer.from(account.pubKey as string, 'hex'),
);
}
} else if (isBtcWalletProvider) {
// handle babylon
inputsToSign.push({
index,
publicKey: account.pubKey as string,
address: account.address,
sighashTypes: v.sighashType ? [v.sighashType] : undefined,
});
}
}
});
Expand Down
Loading
Loading