Skip to content

Commit

Permalink
utxo work
Browse files Browse the repository at this point in the history
  • Loading branch information
BitHighlander committed Oct 3, 2024
1 parent caab0cf commit 7df95ed
Show file tree
Hide file tree
Showing 15 changed files with 1,082 additions and 447 deletions.
156 changes: 134 additions & 22 deletions chrome-extension/src/background/chains/bitcoinCashHandler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { bip32ToAddressNList } from '@pioneer-platform/pioneer-coins';

const TAG = ' | bitcoinCashHandler | ';
import { JsonRpcProvider } from 'ethers';
import { Chain } from '@coinmasters/types';
import { Chain, DerivationPath } from '@coinmasters/types';
import { AssetValue } from '@pioneer-platform/helpers';
// @ts-ignore
// @ts-ignore
import { ChainToNetworkId, shortListSymbolToCaip, caipToNetworkId } from '@pioneer-platform/pioneer-caip';
// @ts-ignore
import { v4 as uuidv4 } from 'uuid';
import { requestStorage } from '@extension/storage/dist/lib';
//@ts-ignore
import * as coinSelect from 'coinselect';

interface ProviderRpcError extends Error {
code: number;
Expand Down Expand Up @@ -57,49 +62,156 @@ export const handleBitcoinCashRequest = async (
return [balance];
}
case 'transfer': {
//caip
const caip = shortListSymbolToCaip['BCH'];
console.log(tag, 'caip: ', caip);
const networkId = caipToNetworkId(caip);
//verify context is bitcoin
requestInfo.id = uuidv4();
console.log(tag, 'assetContext: ', KEEPKEY_WALLET);
// eslint-disable-next-line no-constant-condition
if (!KEEPKEY_WALLET.assetContext) {
// Set context to the chain, defaults to ETH
await KEEPKEY_WALLET.setAssetContext({ caip });
}
const pubkeys = KEEPKEY_WALLET.pubkeys.filter((e: any) =>
e.networks.includes(ChainToNetworkId[Chain.BitcoinCash]),
);
console.log(tag, 'pubkeys: ', pubkeys);
if (!pubkeys || pubkeys.length === 0) throw Error('Failed to locate pubkeys for chain ' + Chain.BitcoinCash);

//send tx
console.log(tag, 'params[0]: ', params[0]);
const assetString = 'BCH.BCH';
await AssetValue.loadStaticAssets();
console.log(tag, 'params[0].amount.amount: ', params[0].amount.amount);
const assetValue = await AssetValue.fromString(assetString, parseFloat(params[0].amount.amount));

const wallet = await KEEPKEY_WALLET.swapKit.getWallet(Chain.BitcoinCash);
if (!wallet) throw new Error('Failed to init swapkit');
const walletAddress = await wallet.getAddress();
console.log(tag, 'walletAddress: ', walletAddress);

const sendPayload = {
from: params[0].from,
from: walletAddress, // Select preference change address
assetValue,
memo: params[0].memo || '',
recipient: params[0].recipient,
};
console.log(tag, 'sendPayload: ', sendPayload);
// const txHash = await KEEPKEY_WALLET.swapKit.transfer(sendPayload);
// console.log(tag, 'txHash: ', txHash);

const unsignedTx = await wallet.buildTx(sendPayload);
log.info('unsignedTx: ', unsignedTx);
requestInfo.unsignedTx = unsignedTx;
// Require user approval
const result = await requireApproval(networkId, requestInfo, 'bitcoin', method, params[0]);

const buildTx = async function () {
try {
const utxos = [];
for (let i = 0; i < pubkeys.length; i++) {
const pubkey = pubkeys[i];
let utxosResp = await KEEPKEY_WALLET.pioneer.ListUnspent({ network: 'BCH', xpub: pubkey.pubkey });
utxosResp = utxosResp.data;
console.log('utxosResp: ', utxosResp);
utxos.push(...utxosResp);
}
console.log(tag, 'utxos: ', utxos);

//get new change address
let changeAddressIndex = await KEEPKEY_WALLET.pioneer.GetChangeAddress({
network: 'BCH',
xpub: pubkeys[0].pubkey || pubkeys[0].xpub,
});
changeAddressIndex = changeAddressIndex.data.changeIndex;
console.log(tag, 'changeAddressIndex: ', changeAddressIndex);

const path = DerivationPath['BCH'].replace('/0/0', `/1/${changeAddressIndex}`);
console.log(tag, 'path: ', path);
const customAddressInfo = {
coin: 'BitcoinCash',
script_type: 'p2pkh',
address_n: bip32ToAddressNList(path),
};
const address = await wallet.getAddress(customAddressInfo);
console.log('address: ', address);
const changeAddress = {
address: address,
path: path,
index: changeAddressIndex,
addressNList: bip32ToAddressNList(path),
};

for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i];
//@ts-ignore
utxo.value = Number(utxo.value);
}
console.log('utxos: ', utxos);

const amountOut: number = Math.floor(Number(params[0].amount.amount) * 1e8);

console.log(tag, 'amountOut: ', amountOut);
const effectiveFeeRate = 10;
console.log('utxos: ', utxos);
const { inputs, outputs, fee } = coinSelect.default(
utxos,
[{ address: params[0].recipient, value: amountOut }],
effectiveFeeRate,
);
console.log('inputs: ', inputs);
console.log('outputs: ', outputs);
console.log('fee: ', fee);

const unsignedTx = await wallet.buildTx({
inputs,
outputs,
memo: 'test',
changeAddress,
});

//push to front

chrome.runtime.sendMessage({
action: 'utxo_build_tx',
unsignedTx: requestInfo,
});

const storedEvent = await requestStorage.getEventById(requestInfo.id);
console.log(tag, 'storedEvent: ', storedEvent);
storedEvent.utxos = utxos;
storedEvent.changeAddress = changeAddress;
storedEvent.unsignedTx = unsignedTx;
await requestStorage.updateEventById(requestInfo.id, storedEvent);
} catch (e) {
console.error(e);
}
};

buildTx();

// Proceed with requiring approval without waiting for buildTx to resolve
const result = await requireApproval(networkId, requestInfo, 'bitcoincash', method, params[0]);
console.log(tag, 'result:', result);

// signTransaction
const signedTx = await wallet.signTransaction(unsignedTx.psbt, unsignedTx.inputs, unsignedTx.memo);
log.info('signedTx: ', signedTx);
const response = await requestStorage.getEventById(requestInfo.id);
console.log(tag, 'response: ', response);

if (result.success && response.unsignedTx) {
const signedTx = await wallet.signTx(
response.unsignedTx.inputs,
response.unsignedTx.outputs,
response.unsignedTx.memo,
);

response.signedTx = signedTx;
await requestStorage.updateEventById(requestInfo.id, response);

//push signed to user to approve
const txHash = await wallet.broadcastTx(signedTx);

//broadcastTx
const txid = await wallet.broadcastTx(signedTx);
log.info('txid: ', txid);
response.txid = txHash;
await requestStorage.updateEventById(requestInfo.id, response);

return txid;
//push event
chrome.runtime.sendMessage({
action: 'transaction_complete',
txHash: txHash,
});

return txHash;
} else {
throw createProviderRpcError(4200, 'User denied transaction');
}
}
default: {
console.log(tag, `Method ${method} not supported`);
Expand Down
160 changes: 122 additions & 38 deletions chrome-extension/src/background/chains/bitcoinHandler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { requestStorage } from '@extension/storage/dist/lib';

const TAG = ' | bitcoinHandler | ';
import { Chain } from '@coinmasters/types';
import { Chain, DerivationPath } from '@coinmasters/types';
import { AssetValue } from '@pioneer-platform/helpers';
import { ChainToNetworkId, shortListSymbolToCaip, caipToNetworkId } from '@pioneer-platform/pioneer-caip';
//@ts-ignore
import { v4 as uuidv4 } from 'uuid';
import { bip32ToAddressNList } from '@pioneer-platform/pioneer-coins';
//@ts-ignore
import * as coinSelect from 'coinselect';

interface ProviderRpcError extends Error {
code: number;
Expand Down Expand Up @@ -66,68 +69,149 @@ export const handleBitcoinRequest = async (

case 'transfer': {
const caip = shortListSymbolToCaip['BTC'];
console.log(tag, 'caip: ', caip);
const networkId = caipToNetworkId(caip);
requestInfo.id = uuidv4();
console.log(tag, 'assetContext: ', KEEPKEY_WALLET);
// eslint-disable-next-line no-constant-condition
if (!KEEPKEY_WALLET.assetContext) {
// Set context to the chain, defaults to ETH
await KEEPKEY_WALLET.setAssetContext({ caip });
}

const pubkeys = KEEPKEY_WALLET.pubkeys.filter((e: any) => e.networks.includes(ChainToNetworkId[Chain.Bitcoin]));
const accounts = pubkeys.map((pubkey: any) => pubkey.master || pubkey.address);
console.log(tag, 'pubkeys: ', pubkeys);
if (!pubkeys || pubkeys.length === 0) throw Error('Failed to locate pubkeys for chain ' + Chain.Bitcoin);

console.log(tag, 'params[0]: ', params[0]);
const assetString = 'BTC.BTC';
await AssetValue.loadStaticAssets();
console.log(tag, 'params[0].amount.amount: ', params[0].amount.amount);
const assetValue = await AssetValue.fromString(assetString, parseFloat(params[0].amount.amount));

const wallet = await KEEPKEY_WALLET.swapKit.getWallet(Chain.Bitcoin);
if (!wallet) throw new Error('Failed to init swapkit');
const walletAddress = await wallet.getAddress();
console.log(tag, 'walletAddress: ', walletAddress);

const assetValue = await AssetValue.fromString(assetString, parseFloat(params[0].amount.amount) / 100000000);
const sendPayload = {
from: accounts[0], // Select preference change address
from: walletAddress, // Select preference change address
assetValue,
memo: params[0].memo || '',
recipient: params[0].recipient,
};

// Start building the transaction but don't wait for it to finish
const wallet = await KEEPKEY_WALLET.swapKit.getWallet(Chain.Bitcoin);
if (!wallet) throw new Error('Failed to init swapkit');

const pubkeysLocal = await wallet.getPubkeys();
console.log(tag, 'pubkeysLocal: ', pubkeysLocal);

const buildTxPromise = wallet
.buildTx(sendPayload)
.then(async unsignedTx => {
console.log(tag, 'unsignedTx', unsignedTx);
// Update requestInfo with transaction details after buildTx resolves
requestInfo.inputs = unsignedTx.inputs;
requestInfo.outputs = unsignedTx.outputs;
requestInfo.memo = unsignedTx.memo;
const buildTx = async function () {
try {
const utxos = [];
for (let i = 0; i < pubkeys.length; i++) {
const pubkey = pubkeys[i];
let utxosResp = await KEEPKEY_WALLET.pioneer.ListUnspent({ network: 'BTC', xpub: pubkey.pubkey });
utxosResp = utxosResp.data;
console.log('utxosResp: ', utxosResp);
utxos.push(...utxosResp);
}
console.log(tag, 'utxos: ', utxos);

//get new change address
let changeAddressIndex = await KEEPKEY_WALLET.pioneer.GetChangeAddress({
network: 'BTC',
xpub: pubkeys[0].pubkey || pubkeys[0].xpub,
});
changeAddressIndex = changeAddressIndex.data.changeIndex;
console.log(tag, 'changeAddressIndex: ', changeAddressIndex);

const path = DerivationPath['BTC'].replace('/0/0', `/1/${changeAddressIndex}`);
console.log(tag, 'path: ', path);
const customAddressInfo = {
coin: 'Bitcoin',
script_type: 'p2pkh',
address_n: bip32ToAddressNList(path),
};
const address = await wallet.getAddress(customAddressInfo);
console.log('address: ', address);
const changeAddress = {
address: address,
path: path,
index: changeAddressIndex,
addressNList: bip32ToAddressNList(path),
};

for (let i = 0; i < utxos.length; i++) {
const utxo = utxos[i];
//@ts-ignore
utxo.value = Number(utxo.value);
}
console.log('utxos: ', utxos);

const amountOut: number = Math.floor(Number(params[0].amount.amount) * 1e8);

console.log(tag, 'amountOut: ', amountOut);
const effectiveFeeRate = 10;
console.log('utxos: ', utxos);
const { inputs, outputs, fee } = coinSelect.default(
utxos,
[{ address: params[0].recipient, value: amountOut }],
effectiveFeeRate,
);
console.log('inputs: ', inputs);
console.log('outputs: ', outputs);
console.log('fee: ', fee);

const unsignedTx = await wallet.buildTx({
inputs,
outputs,
memo: 'test',
changeAddress,
});
//push to front

chrome.runtime.sendMessage({
action: 'utxo_build_tx',
unsignedTx: unsignedTx,
unsignedTx: requestInfo,
});
const response = await requestStorage.getEventById(requestInfo.id);
response.unsignedTx = unsignedTx;
await requestStorage.updateEventById(requestInfo.id, response);

// Push an event to the front-end that UTXOs are found
// This could be something like: sendUpdateToFrontend('UTXOs found', unsignedTx);
})
.catch(error => {
console.error('Error building the transaction:', error);
// Handle buildTx failure appropriately, such as notifying the user
});

const storedEvent = await requestStorage.getEventById(requestInfo.id);
console.log(tag, 'storedEvent: ', storedEvent);
storedEvent.utxos = utxos;
storedEvent.changeAddress = changeAddress;
storedEvent.unsignedTx = unsignedTx;
await requestStorage.updateEventById(requestInfo.id, storedEvent);
} catch (e) {
console.error(e);
}
};

buildTx();

// Proceed with requiring approval without waiting for buildTx to resolve
const result = await requireApproval(networkId, requestInfo, 'bitcoin', method, params[0]);
console.log(tag, 'result:', result);

const response = await requestStorage.getEventById(requestInfo.id);
console.log(tag, 'response: ', response);

// Wait for the buildTx to complete (if not already completed) before signing
const unsignedTx = await buildTxPromise;
if (result.success && response.unsignedTx) {
const signedTx = await wallet.signTx(
response.unsignedTx.inputs,
response.unsignedTx.outputs,
response.unsignedTx.memo,
);

response.signedTx = signedTx;
await requestStorage.updateEventById(requestInfo.id, response);

const txHash = await wallet.broadcastTx(signedTx);

response.txid = txHash;
await requestStorage.updateEventById(requestInfo.id, response);

//push event
chrome.runtime.sendMessage({
action: 'transaction_complete',
txHash: txHash,
});

if (result.success && unsignedTx) {
const signedTx = await wallet.signTransaction(unsignedTx.psbt, unsignedTx.inputs, unsignedTx.memo);
const txid = await wallet.broadcastTx(signedTx);
return txid;
return txHash;
} else {
throw createProviderRpcError(4200, 'User denied transaction');
}
Expand Down
Loading

0 comments on commit 7df95ed

Please sign in to comment.