Skip to content

Commit 83f0d64

Browse files
committed
multi: Allow trezor ticket purchasing on testnet.
Add purchaseTicketsV3 which purchases tickets using a watching only trezor wallet from a vsp with api v3. Errors if not on testnet. Add separate payVSPFee function that will pay a tickets fee if not already paid, and throws if already paid. Correct purchase ticket button for watching only wallets. It no longer asks for a password. Connect purchasing through trezor when isTrezor is true. Add vsp v3 endpoints "feeaddress" and "payfee" to allowed external requests. Change wallet/control.js to not require an unlocked wallet when signTx is false. Add headers to OPTIONS externalRequest. These are required by CORS when making POST requests.
1 parent 9ee8eec commit 83f0d64

File tree

17 files changed

+513
-31
lines changed

17 files changed

+513
-31
lines changed

app/actions/TrezorActions.js

Lines changed: 398 additions & 5 deletions
Large diffs are not rendered by default.

app/components/SideBar/MenuLinks/hooks.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export function useMenuLinks() {
1919
const isTrezor = useSelector(sel.isTrezor);
2020
const isLedger = useSelector(sel.isLedger);
2121
const lnEnabled = useSelector(sel.lnEnabled);
22+
const isTestNet = useSelector(sel.isTestNet);
2223

2324
const newActiveVoteProposalsCount = useSelector(
2425
sel.newActiveVoteProposalsCount
@@ -54,14 +55,12 @@ export function useMenuLinks() {
5455
if (!lnEnabled) {
5556
links = links.filter((l) => l.key !== LN_KEY);
5657
}
57-
// TODO: Enable ticket purchacing for Trezor.
58+
// TODO: Enable ticket purchacing for Trezor on mainnet.
5859
if (isTrezor || isLedger) {
59-
links = links.filter(
60-
(l) => l.key !== DEX_KEY && l.key !== TICKETS_KEY && l.key !== GOV_KEY
61-
);
60+
links = links.filter((l) => l.key !== DEX_KEY && l.key !== GOV_KEY);
6261
}
63-
if (isLedger) {
64-
links = links.filter((l) => l.key !== PRIV_KEY);
62+
if (isLedger || (isTrezor && !isTestNet)) {
63+
links = links.filter((l) => l.key !== PRIV_KEY && l.key !== TICKETS_KEY);
6564
}
6665
return links.map((link) => ({
6766
...link,
@@ -70,7 +69,7 @@ export function useMenuLinks() {
7069
0
7170
)
7271
}));
73-
}, [notifProps, isTrezor, lnEnabled, isLedger]);
72+
}, [notifProps, isTrezor, lnEnabled, isLedger, isTestNet]);
7473

7574
const [activeTabIndex, setActiveTabIndex] = useState(-1);
7675
const history = useHistory();

app/components/buttons/SendTransactionButton/hooks.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ export function useSendTransactionButton() {
1616
dispatch(ca.signTransactionAttempt(passphrase, rawTx, acct));
1717
};
1818
const onAttemptSignTransactionTrezor = (rawUnsigTx, constructTxResponse) =>
19-
dispatch(tza.signTransactionAttemptTrezor(rawUnsigTx, constructTxResponse));
19+
dispatch(
20+
tza.signTransactionAttemptTrezor(rawUnsigTx, [
21+
constructTxResponse.changeIndex
22+
])
23+
);
2024
const onAttemptSignTransactionLedger = (rawUnsigTx) =>
2125
dispatch(ldgr.signTransactionAttemptLedger(rawUnsigTx));
2226

app/components/views/HomePage/HomePage.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,12 @@ export default () => {
5454
tsDate
5555
} = useHomePage();
5656

57-
// TODO: Enable ticket purchacing for Trezor.
57+
// TODO: Enable ticket purchacing for Trezor on mainnet.
5858
const isTrezor = useSelector(sel.isTrezor);
5959
const isLedger = useSelector(sel.isLedger);
60+
const isTestNet = useSelector(sel.isTestNet);
6061
let recentTickets, tabs;
61-
if (isTrezor || isLedger) {
62+
if ((isTrezor && !isTestNet) || isLedger) {
6263
tabs = [balanceTab, transactionsTab];
6364
} else {
6465
recentTickets = (

app/components/views/TicketsPage/PurchaseTab/PurchaseTabPage.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export function PurchaseTabPage({
7272
isVSPListingEnabled,
7373
onEnableVSPListing,
7474
getRunningIndicator,
75+
isPurchasingTicketsTrezor,
7576
...props
7677
}) {
7778
return (
@@ -115,7 +116,9 @@ export function PurchaseTabPage({
115116
isLoading,
116117
rememberedVspHost,
117118
toggleRememberVspHostCheckBox,
118-
getRunningIndicator
119+
getRunningIndicator,
120+
isPurchasingTicketsTrezor,
121+
isWatchingOnly
119122
}}
120123
/>
121124
)}

app/components/views/TicketsPage/PurchaseTab/PurchaseTicketsForm/PurchaseTicketsForm.jsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ const PurchaseTicketsForm = ({
3636
rememberedVspHost,
3737
toggleRememberVspHostCheckBox,
3838
notMixedAccounts,
39-
getRunningIndicator
39+
getRunningIndicator,
40+
isPurchasingTicketsTrezor
4041
}) => {
4142
const intl = useIntl();
4243
return (
@@ -149,7 +150,10 @@ const PurchaseTicketsForm = ({
149150
</div>
150151
<div className={styles.buttonsArea}>
151152
{isWatchingOnly ? (
152-
<PiUiButton disabled={!isValid} onClick={onPurchaseTickets}>
153+
<PiUiButton
154+
disabled={!isValid}
155+
loading={isPurchasingTicketsTrezor}
156+
onClick={onPurchaseTickets}>
153157
{purchaseLabel()}
154158
</PiUiButton>
155159
) : isLoading ? (

app/components/views/TicketsPage/PurchaseTab/hooks.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useCallback, useMemo } from "react";
33
import { useSettings } from "hooks";
44
import { EXTERNALREQUEST_STAKEPOOL_LISTING } from "constants";
55

6+
import { purchaseTicketsAttempt as trezorPurchseTicketsAttempt } from "actions/TrezorActions.js";
67
import * as vspa from "actions/VSPActions";
78
import * as ca from "actions/ControlActions.js";
89
import * as sel from "selectors";
@@ -21,6 +22,8 @@ export const usePurchaseTab = () => {
2122
const ticketAutoBuyerRunning = useSelector(sel.getTicketAutoBuyerRunning);
2223
const isLoading = useSelector(sel.purchaseTicketsRequestAttempt);
2324
const notMixedAccounts = useSelector(sel.getNotMixedAccounts);
25+
const isTrezor = useSelector(sel.isTrezor);
26+
const isPurchasingTicketsTrezor = useSelector(sel.isPurchasingTicketsTrezor);
2427

2528
const rememberedVspHost = useSelector(sel.getRememberedVspHost);
2629
const visibleAccounts = useSelector(sel.visibleAccounts);
@@ -54,9 +57,16 @@ export const usePurchaseTab = () => {
5457
[dispatch]
5558
);
5659
const purchaseTicketsAttempt = useCallback(
57-
(passphrase, account, numTickets, vsp) =>
58-
dispatch(ca.purchaseTicketsAttempt(passphrase, account, numTickets, vsp)),
59-
[dispatch]
60+
(passphrase, account, numTickets, vsp) => {
61+
if (isTrezor) {
62+
dispatch(trezorPurchseTicketsAttempt(account, numTickets, vsp));
63+
} else {
64+
dispatch(
65+
ca.purchaseTicketsAttempt(passphrase, account, numTickets, vsp)
66+
);
67+
}
68+
},
69+
[dispatch, isTrezor]
6070
);
6171

6272
const setRememberedVspHost = useCallback(
@@ -140,6 +150,7 @@ export const usePurchaseTab = () => {
140150
vsp,
141151
setVSP,
142152
numTicketsToBuy,
143-
setNumTicketsToBuy
153+
setNumTicketsToBuy,
154+
isPurchasingTicketsTrezor
144155
};
145156
};

app/helpers/msgTx.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ export function decodeRawTransaction(rawTx) {
268268
position += 4;
269269
tx.expiry = rawTx.readUInt32LE(position);
270270
position += 4;
271+
tx.prefixOffset = position;
271272
}
272273

273274
if (tx.serType !== SERTYPE_NOWITNESS) {

app/helpers/trezor.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ export const addressPath = (index, branch, account, coinType) => {
2828
};
2929

3030
// walletTxToBtcjsTx is a aux function to convert a tx decoded by the decred wallet (ie,
31-
// returned from wallet.decoreRawTransaction call) into a bitcoinjs-compatible
31+
// returned from wallet.decodeRawTransaction call) into a bitcoinjs-compatible
3232
// transaction (to be used in trezor).
3333
export const walletTxToBtcjsTx = async (
3434
walletService,
3535
chainParams,
3636
tx,
3737
inputTxs,
38-
changeIndex
38+
changeIndexes
3939
) => {
4040
const inputs = tx.inputs.map(async (inp) => {
4141
const addr = inp.outpointAddress;
@@ -81,7 +81,7 @@ export const walletTxToBtcjsTx = async (
8181
const addrValidResp = await wallet.validateAddress(walletService, addr);
8282
if (!addrValidResp.isValid) throw "Not a valid address: " + addr;
8383
let address_n = null;
84-
if (i === changeIndex && addrValidResp.isMine) {
84+
if (changeIndexes.includes(i) && addrValidResp.isMine) {
8585
const addrIndex = addrValidResp.index;
8686
const addrBranch = addrValidResp.isInternal ? 1 : 0;
8787
address_n = addressPath(
@@ -124,7 +124,10 @@ export const walletTxToRefTx = async (walletService, tx) => {
124124
const outputs = tx.outputs.map(async (outp) => {
125125
const addr = outp.decodedScript.address;
126126
const addrValidResp = await wallet.validateAddress(walletService, addr);
127-
if (!addrValidResp.isValid) throw new Error("Not a valid address: " + addr);
127+
// Scripts with zero value can be ignored as they are not a concern when
128+
// spending from an outpoint.
129+
if (outp.value != 0 && !addrValidResp.isValid)
130+
throw new Error("Not a valid address: " + addr);
128131
return {
129132
amount: outp.value,
130133
script_pubkey: rawToHex(outp.script),

app/main_dev/externalRequests.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ export const installSessionHandlers = (mainLogger) => {
121121
`connect-src ${connectSrc}; `;
122122
}
123123

124+
const requestURL = new URL(details.url);
125+
const maybeVSPReqType = `stakepool_${requestURL.protocol}//${requestURL.host}`;
126+
const isVSPRequest = allowedExternalRequests[maybeVSPReqType];
127+
124128
if (isDev && /^http[s]?:\/\//.test(details.url)) {
125129
// In development (when accessing via the HMR server) we need to overwrite
126130
// the origin, otherwise electron fails to contact external servers due
@@ -144,6 +148,12 @@ export const installSessionHandlers = (mainLogger) => {
144148
newHeaders["Access-Control-Allow-Headers"] = "Content-Type";
145149
}
146150

151+
if (isVSPRequest && details.method === "OPTIONS") {
152+
statusLine = "OK";
153+
newHeaders["Access-Control-Allow-Headers"] =
154+
"Content-Type,vsp-client-signature";
155+
}
156+
147157
const globalCfg = getGlobalCfg();
148158
const cfgAllowedVSPs = globalCfg.get(cfgConstants.ALLOWED_VSP_HOSTS, []);
149159
if (cfgAllowedVSPs.some((url) => details.url.includes(url))) {
@@ -204,6 +214,10 @@ export const allowVSPRequests = (stakePoolHost) => {
204214

205215
addAllowedURL(stakePoolHost + "/api/v3/vspinfo");
206216
addAllowedURL(stakePoolHost + "/api/v3/ticketstatus");
217+
addAllowedURL(stakePoolHost + "/api/v3/feeaddress");
218+
addAllowedURL(stakePoolHost + "/api/v3/payfee");
219+
addAllowedURL(stakePoolHost + "/api/ticketstatus");
220+
allowedExternalRequests[reqType] = true;
207221
};
208222

209223
export const reloadAllowedExternalRequests = () => {

0 commit comments

Comments
 (0)