mirror of
https://github.com/BlueWallet/BlueWallet.git
synced 2025-02-22 06:52:41 +01:00
FIX: PSBT with HW wallets flow (closes #1822)
This commit is contained in:
parent
bedf44e6f8
commit
ec30394ed5
13 changed files with 101 additions and 34 deletions
|
@ -41,7 +41,7 @@ class DeeplinkSchemaMatch {
|
|||
event.url = event.url.substring(11);
|
||||
}
|
||||
|
||||
if (DeeplinkSchemaMatch.isPossiblyPSBTFile(event.url)) {
|
||||
if (DeeplinkSchemaMatch.isPossiblySignedPSBTFile(event.url)) {
|
||||
RNFS.readFile(event.url)
|
||||
.then(file => {
|
||||
if (file) {
|
||||
|
@ -203,13 +203,23 @@ class DeeplinkSchemaMatch {
|
|||
}
|
||||
|
||||
static isTXNFile(filePath) {
|
||||
return filePath.toLowerCase().startsWith('file:') && filePath.toLowerCase().endsWith('.txn');
|
||||
return (
|
||||
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
|
||||
filePath.toLowerCase().endsWith('.txn')
|
||||
);
|
||||
}
|
||||
|
||||
static isPossiblySignedPSBTFile(filePath) {
|
||||
return (
|
||||
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
|
||||
filePath.toLowerCase().endsWith('-signed.psbt')
|
||||
);
|
||||
}
|
||||
|
||||
static isPossiblyPSBTFile(filePath) {
|
||||
return (
|
||||
(filePath.toLowerCase().startsWith('file:') || filePath.toLowerCase().startsWith('content:')) &&
|
||||
filePath.toLowerCase().endsWith('-signed.psbt')
|
||||
filePath.toLowerCase().endsWith('.psbt')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Next",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "note to self",
|
||||
"details_scan": "Scan",
|
||||
"details_total_exceeds_balance": "The sending amount exceeds the available balance.",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Next",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "catatan pribadi",
|
||||
"details_scan": "Pindai",
|
||||
"details_total_exceeds_balance": "Jumlah yang dikirim melebihi saldo.",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "次",
|
||||
"details_no_maximum": "選択したウォレットは、最大残高の自動計算に対応していません。このウォレットを選択してもよろしいですか?",
|
||||
"details_no_multiple": "選択したウォレットは、複数の受信者へのビットコインの送信をサポートしていません。このウォレットを選択してもよろしいですか?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "ラベル",
|
||||
"details_scan": "読取り",
|
||||
"details_total_exceeds_balance": "送金額が利用可能残額を超えています。",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Volgende",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "notitie voor mezelf",
|
||||
"details_scan": "Scan",
|
||||
"details_total_exceeds_balance": "Het verzendingsbedrag overschrijdt het beschikbare saldo.",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Ďalej",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "poznámka pre seba",
|
||||
"details_scan": "Skenovať",
|
||||
"details_total_exceeds_balance": "Čiastka, ktorú chcete poslať, presahuje dostupný zostatok.",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Nästa",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "egen notering",
|
||||
"details_scan": "Skanna",
|
||||
"details_total_exceeds_balance": "Beloppet överstiger plånbokens tillgängliga belopp",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "ถัดไป",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "หมายเหตุถึงตัวท่านเอง",
|
||||
"details_scan": "สแกน",
|
||||
"details_total_exceeds_balance": "จำนวนเงินที่จะส่งเกินเงินที่มี.",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Next",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "kendime not",
|
||||
"details_scan": "Tara",
|
||||
"details_total_exceeds_balance": "Gönderme miktarı mevcut bakiyeyi aşıyor.",
|
||||
|
|
|
@ -170,7 +170,7 @@
|
|||
"details_next": "Next",
|
||||
"details_no_maximum": "The selected wallet does not support automatic maximum balance calculation. Are you sure to want to select this wallet?",
|
||||
"details_no_multiple": "The selected wallet does not support sending Bitcoin to multiple recipients. Are you sure to want to select this wallet?",
|
||||
"details_no_signed_tx": "The selected file does not contain a signed transaction that can be imported.",
|
||||
"details_no_signed_tx": "The selected file does not contain a transaction that can be imported.",
|
||||
"details_note_placeholder": "消息",
|
||||
"details_scan": "扫描",
|
||||
"details_total_exceeds_balance": "余额不足",
|
||||
|
|
|
@ -772,32 +772,51 @@ export default class SendDetails extends Component {
|
|||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
|
||||
* so he can scan it and sign it. then we have to scan it back from user (via camera and QR code), and ask
|
||||
* user whether he wants to broadcast it.
|
||||
* alternatively, user can export psbt file, sign it externally and then import it
|
||||
*
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
importTransaction = async () => {
|
||||
if (this.state.fromWallet.type !== WatchOnlyWallet.type) {
|
||||
alert('Error: importing transaction in non-watchonly wallet (this should never happen)');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await DocumentPicker.pick({
|
||||
type: Platform.OS === 'ios' ? ['io.bluewallet.psbt', 'io.bluewallet.psbt.txn'] : [DocumentPicker.types.allFiles],
|
||||
});
|
||||
if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) {
|
||||
|
||||
if (DeeplinkSchemaMatch.isPossiblySignedPSBTFile(res.uri)) {
|
||||
// we assume that transaction is already signed, so all we have to do is get txhex and pass it to next screen
|
||||
// so user can broadcast:
|
||||
const file = await RNFS.readFile(res.uri, 'ascii');
|
||||
const bufferDecoded = Buffer.from(file, 'ascii').toString('base64');
|
||||
if (bufferDecoded) {
|
||||
if (this.state.fromWallet.type === WatchOnlyWallet.type) {
|
||||
// watch-only wallets with enabled HW wallet support have different flow. we have to show PSBT to user as QR code
|
||||
// so he can scan it and sign it. then we have to scan it back from user (via camera and QR code), and ask
|
||||
// user whether he wants to broadcast it.
|
||||
// alternatively, user can export psbt file, sign it externally and then import it
|
||||
this.props.navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: this.state.memo,
|
||||
fromWallet: this.state.fromWallet,
|
||||
psbt: file,
|
||||
});
|
||||
this.setState({ isLoading: false });
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
const psbt = bitcoin.Psbt.fromBase64(file);
|
||||
const txhex = psbt.extractTransaction().toHex();
|
||||
|
||||
this.props.navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: this.state.memo,
|
||||
fromWallet: this.state.fromWallet,
|
||||
txhex,
|
||||
});
|
||||
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
|
||||
} else if (DeeplinkSchemaMatch.isPossiblyPSBTFile(res.uri)) {
|
||||
// looks like transaction is UNsigned, so we construct PSBT object and pass to next screen
|
||||
// so user can do smth with it:
|
||||
const file = await RNFS.readFile(res.uri, 'ascii');
|
||||
const psbt = bitcoin.Psbt.fromBase64(file);
|
||||
this.props.navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: this.state.memo,
|
||||
fromWallet: this.state.fromWallet,
|
||||
psbt,
|
||||
});
|
||||
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
|
||||
} else if (DeeplinkSchemaMatch.isTXNFile(res.uri)) {
|
||||
// plain text file with txhex ready to broadcast
|
||||
const file = await RNFS.readFile(res.uri, 'ascii');
|
||||
this.props.navigation.navigate('PsbtWithHardwareWallet', {
|
||||
memo: this.state.memo,
|
||||
|
@ -805,7 +824,8 @@ export default class SendDetails extends Component {
|
|||
txhex: file,
|
||||
});
|
||||
this.setState({ isLoading: false, isAdvancedTransactionOptionsVisible: false });
|
||||
return;
|
||||
} else {
|
||||
alert('Unrecognized file format');
|
||||
}
|
||||
} catch (err) {
|
||||
if (!DocumentPicker.isCancel(err)) {
|
||||
|
|
|
@ -173,7 +173,7 @@ export default class PsbtWithHardwareWallet extends Component {
|
|||
}
|
||||
if (ret.data.indexOf('+') === -1 && ret.data.indexOf('=') === -1 && ret.data.indexOf('=') === -1) {
|
||||
// this looks like NOT base64, so maybe its transaction's hex
|
||||
this.setState({ txhex: ret.data }, () => this.props.navigation.dangerouslyGetParent().pop());
|
||||
this.setState({ txhex: ret.data });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
@ -201,11 +201,20 @@ export default class PsbtWithHardwareWallet extends Component {
|
|||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (!prevState.psbt && !nextProps.route.params.txhex) {
|
||||
alert('There is no transaction signing in progress');
|
||||
return {
|
||||
...prevState,
|
||||
isLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
const deepLinkPSBT = nextProps.route.params.deepLinkPSBT;
|
||||
const txhex = nextProps.route.params.txhex;
|
||||
if (deepLinkPSBT) {
|
||||
const psbt = bitcoin.Psbt.fromBase64(deepLinkPSBT);
|
||||
try {
|
||||
const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, deepLinkPSBT);
|
||||
const Tx = prevState.fromWallet.combinePsbt(prevState.psbt, psbt);
|
||||
return {
|
||||
...prevState,
|
||||
txhex: Tx.toHex(),
|
||||
|
|
|
@ -220,4 +220,32 @@ describe('unit - DeepLinkSchemaMatch', function () {
|
|||
});
|
||||
assert.strictEqual(encoded, 'bitcoin:1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH?amount=20.3&label=Foobar');
|
||||
});
|
||||
|
||||
it('recognizes files', () => {
|
||||
// txn files:
|
||||
assert.ok(DeeplinkSchemaMatch.isTXNFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex.txn'));
|
||||
assert.ok(!DeeplinkSchemaMatch.isPossiblySignedPSBTFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex.txn'));
|
||||
|
||||
assert.ok(DeeplinkSchemaMatch.isTXNFile('content://com.android.externalstorage.documents/document/081D-1403%3Atxhex.txn'));
|
||||
assert.ok(
|
||||
!DeeplinkSchemaMatch.isPossiblySignedPSBTFile('content://com.android.externalstorage.documents/document/081D-1403%3Atxhex.txn'),
|
||||
);
|
||||
|
||||
// psbt files (signed):
|
||||
assert.ok(
|
||||
DeeplinkSchemaMatch.isPossiblySignedPSBTFile(
|
||||
'content://com.android.externalstorage.documents/document/081D-1403%3Atxhex-signed.psbt',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
DeeplinkSchemaMatch.isPossiblySignedPSBTFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex-signed.psbt'),
|
||||
);
|
||||
|
||||
assert.ok(!DeeplinkSchemaMatch.isTXNFile('content://com.android.externalstorage.documents/document/081D-1403%3Atxhex-signed.psbt'));
|
||||
assert.ok(!DeeplinkSchemaMatch.isTXNFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex-signed.psbt'));
|
||||
|
||||
// psbt files (unsigned):
|
||||
assert.ok(DeeplinkSchemaMatch.isPossiblyPSBTFile('content://com.android.externalstorage.documents/document/081D-1403%3Atxhex.psbt'));
|
||||
assert.ok(DeeplinkSchemaMatch.isPossiblyPSBTFile('file://com.android.externalstorage.documents/document/081D-1403%3Atxhex.psbt'));
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue