Skip to content

Commit a4e0498

Browse files
ronaldsg20alexjavabraz
authored andcommitted
Adding Native segwit support for xverse
I've updated the XverseService and TxBuilder in order to create the proper transaction according the selected account type: p2wpkh or p2sh
1 parent e119049 commit a4e0498

File tree

2 files changed

+139
-129
lines changed

2 files changed

+139
-129
lines changed

src/common/services/XverseService.ts

Lines changed: 131 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -7,144 +7,150 @@ import {
77
WalletAddress, Tx, SignedTx, BtcAccount, Step,
88
XverseTx,
99
} from '../types';
10+
import { validateAddress } from '../utils';
1011

1112
export default class XverseService extends WalletService {
12-
satsBtcNetwork: BitcoinNetworkType;
13+
satsBtcNetwork: BitcoinNetworkType;
1314

14-
constructor() {
15-
super();
16-
switch (this.network) {
17-
case constants.BTC_NETWORK_MAINNET:
18-
this.satsBtcNetwork = BitcoinNetworkType.Mainnet;
19-
break;
20-
default:
21-
this.satsBtcNetwork = BitcoinNetworkType.Testnet;
22-
break;
23-
}
24-
}
15+
btcAccounts: BtcAccount[] = [];
2516

26-
async getAccountAddresses(): Promise<WalletAddress[]> {
27-
// @ts-expect-error method type not provided
28-
const permissions = await Wallet.request('wallet_getCurrentPermissions', undefined);
29-
if (permissions.status !== 'success') {
30-
// @ts-expect-error method type not provided
31-
await Wallet.request('wallet_requestPermissions', undefined);
32-
}
33-
return new Promise<WalletAddress[]>((resolve, reject) => {
34-
const walletAddresses: WalletAddress[] = [];
35-
const payload = {
36-
purposes: ['payment'] as AddressPurpose[],
37-
message: 'Welcome to the Powpeg app, please select your Bitcoin account to start.',
38-
network: {
39-
type: this.satsBtcNetwork,
40-
},
41-
};
42-
Wallet.request('getAddresses', payload)
43-
.then((response) => {
44-
if (response.status === 'error') {
45-
reject(new Error(response.error.message));
46-
} else {
47-
response.result.addresses
48-
.forEach((addr: { address: string; publicKey: string; }) => {
49-
walletAddresses.push({
50-
address: addr.address,
51-
publicKey: addr.publicKey,
52-
derivationPath: '',
53-
});
54-
});
55-
}
56-
resolve(walletAddresses);
57-
})
58-
.catch(reject);
59-
});
60-
}
17+
constructor() {
18+
super();
19+
switch (this.network) {
20+
case constants.BTC_NETWORK_MAINNET:
21+
this.satsBtcNetwork = BitcoinNetworkType.Mainnet;
22+
break;
23+
default:
24+
this.satsBtcNetwork = BitcoinNetworkType.Testnet;
25+
break;
26+
}
27+
}
28+
29+
async getAccountAddresses(): Promise<WalletAddress[]> {
30+
// @ts-expect-error method type not provided
31+
const permissions = await Wallet.request('wallet_getCurrentPermissions', undefined);
32+
if (permissions.status !== 'success') {
33+
// @ts-expect-error method type not provided
34+
await Wallet.request('wallet_requestPermissions', undefined);
35+
}
36+
return new Promise<WalletAddress[]>((resolve, reject) => {
37+
const walletAddresses: WalletAddress[] = [];
38+
const payload = {
39+
purposes: ['payment'] as AddressPurpose[],
40+
message: 'Welcome to the Powpeg app, please select your Bitcoin account to start.',
41+
network: {
42+
type: this.satsBtcNetwork,
43+
},
44+
};
45+
Wallet.request('getAddresses', payload)
46+
.then((response) => {
47+
if (response.status === 'error') {
48+
reject(new Error(response.error.message));
49+
} else {
50+
response.result.addresses
51+
.forEach((addr: { address: string; publicKey: string; }) => {
52+
walletAddresses.push({
53+
address: addr.address,
54+
publicKey: addr.publicKey,
55+
derivationPath: '',
56+
});
57+
const { addressType } = validateAddress(addr.address);
58+
this.btcAccounts.push(addressType as BtcAccount);
59+
});
60+
}
61+
resolve(walletAddresses);
62+
})
63+
.catch(reject);
64+
});
65+
}
6166

62-
sign(tx: Tx): Promise<SignedTx> {
63-
const xverseTx = tx as XverseTx;
64-
return new Promise<SignedTx>((resolve, reject) => {
65-
const signInputs: Record<string, number[]> = {};
66-
xverseTx.inputs.forEach((input: { address: string; idx: number; }, inputIdx: number) => {
67-
if (signInputs[input.address]) {
68-
signInputs[input.address].push(inputIdx);
67+
sign(tx: Tx): Promise<SignedTx> {
68+
const xverseTx = tx as XverseTx;
69+
return new Promise<SignedTx>((resolve, reject) => {
70+
const signInputs: Record<string, number[]> = {};
71+
xverseTx.inputs.forEach((input: { address: string; idx: number; }, inputIdx: number) => {
72+
if (signInputs[input.address]) {
73+
signInputs[input.address].push(inputIdx);
74+
} else {
75+
signInputs[input.address] = [inputIdx];
76+
}
77+
});
78+
const signPsbtOptions = {
79+
psbt: xverseTx.base64UnsignedPsbt,
80+
signInputs,
81+
broadcast: false,
82+
};
83+
Wallet.request('signPsbt', signPsbtOptions)
84+
.then((response) => {
85+
if (response.status === 'error') {
86+
reject(new Error(response.error.message));
87+
} else {
88+
const signedPsbt = bitcoin.Psbt.fromBase64(response.result.psbt as string);
89+
if (!signedPsbt.validateSignaturesOfAllInputs()) {
90+
reject(new Error('Invalid signature provided'));
6991
} else {
70-
signInputs[input.address] = [inputIdx];
92+
resolve({
93+
signedTx: signedPsbt.finalizeAllInputs().extractTransaction().toHex(),
94+
});
7195
}
72-
});
73-
const signPsbtOptions = {
74-
psbt: xverseTx.base64UnsignedPsbt,
75-
signInputs,
76-
broadcast: false,
77-
};
78-
Wallet.request('signPsbt', signPsbtOptions)
79-
.then((response) => {
80-
if (response.status === 'error') {
81-
reject(new Error(response.error.message));
82-
} else {
83-
const signedPsbt = bitcoin.Psbt.fromBase64(response.result.psbt as string);
84-
if (!signedPsbt.validateSignaturesOfAllInputs()) {
85-
reject(new Error('Invalid signature provided'));
86-
} else {
87-
resolve({
88-
signedTx: signedPsbt.finalizeAllInputs().extractTransaction().toHex(),
89-
});
90-
}
91-
}
92-
})
93-
.catch(() => reject(new Error('Invalid psbt provided')));
94-
});
95-
}
96+
}
97+
})
98+
.catch(() => reject(new Error('Invalid psbt provided')));
99+
});
100+
}
96101

97-
isConnected(): Promise<boolean> {
98-
return Promise.resolve(true);
99-
}
102+
isConnected(): Promise<boolean> {
103+
return Promise.resolve(true);
104+
}
100105

101-
reconnect(): Promise<void> {
102-
return new Promise<void>((resolve, reject) => {
103-
this.getAccountAddresses()
104-
.then(() => resolve())
105-
.catch(reject);
106-
});
107-
}
106+
reconnect(): Promise<void> {
107+
return new Promise<void>((resolve, reject) => {
108+
this.getAccountAddresses()
109+
.then(() => resolve())
110+
.catch(reject);
111+
});
112+
}
108113

109-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
110-
getXpub(accountType: BtcAccount, accountNumber: number): Promise<string> {
111-
throw new Error('Method not supported.');
112-
}
114+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
115+
getXpub(accountType: BtcAccount, accountNumber: number): Promise<string> {
116+
throw new Error('Method not supported.');
117+
}
113118

114-
areEnoughUnusedAddresses(): boolean {
115-
return this.addressesToFetch.segwit.lastIndex >= 1;
116-
}
119+
areEnoughUnusedAddresses(): boolean {
120+
return this.addressesToFetch.segwit.lastIndex >= 1;
121+
}
117122

118-
availableAccounts(): BtcAccount[] {
119-
return [BtcAccount.BITCOIN_SEGWIT_ADDRESS];
120-
}
123+
availableAccounts(): BtcAccount[] {
124+
return this.btcAccounts;
125+
return [BtcAccount.BITCOIN_SEGWIT_ADDRESS, BtcAccount.BITCOIN_NATIVE_SEGWIT_ADDRESS];
126+
}
121127

122-
name(): Record<'formal_name' | 'short_name' | 'long_name', string> {
123-
return constants.WALLET_NAMES.XVERSE;
124-
}
128+
name(): Record<'formal_name' | 'short_name' | 'long_name', string> {
129+
return constants.WALLET_NAMES.XVERSE;
130+
}
125131

126-
confirmationSteps(): Step[] {
127-
return [
128-
{
129-
title: 'Transaction information',
130-
subtitle: '',
131-
outputsToshow: {
132-
opReturn: {
133-
value: false,
134-
amount: true,
135-
},
136-
change: {
137-
address: true,
138-
amount: true,
139-
},
140-
federation: {
141-
address: true,
142-
amount: true,
143-
},
144-
},
145-
fullAmount: false,
146-
fee: true,
132+
confirmationSteps(): Step[] {
133+
return [
134+
{
135+
title: 'Transaction information',
136+
subtitle: '',
137+
outputsToshow: {
138+
opReturn: {
139+
value: false,
140+
amount: true,
141+
},
142+
change: {
143+
address: true,
144+
amount: true,
145+
},
146+
federation: {
147+
address: true,
148+
amount: true,
147149
},
148-
];
149-
}
150+
},
151+
fullAmount: false,
152+
fee: true,
153+
},
154+
];
155+
}
150156
}

src/pegin/middleware/TxBuilder/XverseTxBuilder.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from '@/common/types';
77
import * as bitcoin from 'bitcoinjs-lib';
88
import * as constants from '@/common/store/constants';
9-
import { getP2SHRedeemScript } from '@/common/utils';
9+
import { getP2SHRedeemScript, validateAddress } from '@/common/utils';
1010
import TxBuilder from './TxBuilder';
1111

1212
export default class XverseTxBuilder extends TxBuilder {
@@ -58,17 +58,21 @@ export default class XverseTxBuilder extends TxBuilder {
5858
Promise.all(hexUtxoPromises)
5959
.then((hexUtxos) => {
6060
normalizedInputs.forEach((normalizedInput, idx) => {
61+
const { addressType } = validateAddress(normalizedInput.address);
6162
const utxo = bitcoin.Transaction.fromHex(hexUtxos[idx]);
6263
const pubKey = store.getters[`pegInTx/${constants.PEGIN_TX_GET_ADDRESS_PUBLIC_KEY}`](normalizedInput.address);
63-
psbtExtendedInputs.push({
64+
const extendedInput: PsbtExtendedInput = {
6465
hash: normalizedInput.prev_hash,
6566
index: normalizedInput.prev_index,
6667
witnessUtxo: {
6768
value: utxo.outs[normalizedInput.prev_index].value,
6869
script: utxo.outs[normalizedInput.prev_index].script,
6970
},
70-
redeemScript: getP2SHRedeemScript(pubKey, this.network),
71-
});
71+
};
72+
if (addressType === constants.BITCOIN_SEGWIT_ADDRESS) {
73+
extendedInput.redeemScript = getP2SHRedeemScript(pubKey, this.network);
74+
}
75+
psbtExtendedInputs.push(extendedInput);
7276
});
7377
resolve(psbtExtendedInputs);
7478
})

0 commit comments

Comments
 (0)