Skip to content

Commit

Permalink
Cleanup related to merge of PR #436
Browse files Browse the repository at this point in the history
-move /api/xyzpub/:xyzpub to /api/xyzpub/txids/:xyzpub
-move /api/util/xyzpub/:xyzpub -> /api/xyzpub/:xyzpub
-rename xyzpubAddressApi -> xyzpubApi and move more functionality from apiRouter there
-searchXpubTxids: return more details about the search, then in the apiRouter just use what's needed
-fix in searchXpubTxids: reset gapCount value when tx is found
-update api docs
-update changelogs
  • Loading branch information
janoside committed May 18, 2022
1 parent 4acb76b commit e506070
Show file tree
Hide file tree
Showing 5 changed files with 406 additions and 283 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ This changelog specifically tracks changes to the Public API available at `/api`
##### v1.2.0
###### Unreleased

* Added /api/xyzpub/txids/:xyzpub
* Added /api/xyzpub/addresses/:xyzpub
* /api/tx/:txid
* Added result.vin[i].scriptSig.address
* Added result.vin[i].scriptSig.type
* Added result.fee, including result.fee.amount and result.fee.unit
* Changed path: /api/util/xyzpub/:xyzpub -> /api/xyzpub/:xyzpub (auto-redirect included)



Expand Down
108 changes: 0 additions & 108 deletions app/api/xyzpubAddressApi.js

This file was deleted.

232 changes: 232 additions & 0 deletions app/api/xyzpubApi.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
"use strict";

const config = require("./../config.js");
const coins = require("../coins.js");
const utils = require("../utils.js");

const coinConfig = coins[config.coin];

const coreApi = require("./coreApi.js");
const addressApi = require("./addressApi.js");



function getKeyDetails(extendedPubkey) {
let keyDetails = {
keyType: extendedPubkey.substring(0, 4),
relatedKeys: []
};

// if xpub/ypub/zpub convert to address under path m/0/0
if (extendedPubkey.match(/^(xpub|tpub).*$/)) {
keyDetails.outputType = "P2PKH";
keyDetails.outputTypeDesc = "Pay to Public Key Hash";
keyDetails.bip32Path = "m/44'/0'";

const xpub_tpub = global.activeBlockchain == "main" ? "xpub" : "tpub";
const ypub_upub = global.activeBlockchain == "main" ? "ypub" : "upub";
const zpub_vpub = global.activeBlockchain == "main" ? "zpub" : "vpub";

let xpub = extendedPubkey;
if (!extendedPubkey.startsWith(xpub_tpub)) {
xpub = utils.xpubChangeVersionBytes(extendedPubkey, xpub_tpub);
}

if (!extendedPubkey.startsWith(xpub_tpub)) {
keyDetails.relatedKeys.push({
keyType: xpub_tpub,
key: utils.xpubChangeVersionBytes(xpub, xpub_tpub),
outputType: "P2PKH",
firstAddress: utils.bip32Addresses(xpub, "p2pkh", 0, 1, 0)[0]
});
}

keyDetails.relatedKeys.push({
keyType: ypub_upub,
key: utils.xpubChangeVersionBytes(xpub, ypub_upub),
outputType: "P2WPKH in P2SH",
firstAddress: utils.bip32Addresses(xpub, "p2sh(p2wpkh)", 0, 1, 0)[0]
});

keyDetails.relatedKeys.push({
keyType: zpub_vpub,
key: utils.xpubChangeVersionBytes(xpub, zpub_vpub),
outputType: "P2WPKH",
firstAddress: utils.bip32Addresses(xpub, "p2wpkh", 0, 1, 0)[0]
});

} else if (extendedPubkey.match(/^(ypub|upub).*$/)) {
keyDetails.outputType = "P2WPKH in P2SH";
keyDetails.outputTypeDesc = "Pay to Witness Public Key Hash (P2WPKH) wrapped inside Pay to Script Hash (P2SH), aka Wrapped Segwit";
keyDetails.bip32Path = "m/49'/0'";

const xpub_tpub = global.activeBlockchain == "main" ? "xpub" : "tpub";
const zpub_vpub = global.activeBlockchain == "main" ? "zpub" : "vpub";

const xpub = utils.xpubChangeVersionBytes(extendedPubkey, xpub_tpub);

keyDetails.relatedKeys.push({
keyType: xpub_tpub,
key: xpub,
outputType: "P2PKH",
firstAddress: utils.bip32Addresses(xpub, "p2pkh", 0, 1, 0)[0]
});

keyDetails.relatedKeys.push({
keyType: zpub_vpub,
key: utils.xpubChangeVersionBytes(xpub, zpub_vpub),
outputType: "P2WPKH",
firstAddress: utils.bip32Addresses(xpub, "p2wpkh", 0, 1, 0)[0]
});

} else if (extendedPubkey.match(/^(zpub|vpub).*$/)) {
keyDetails.outputType = "P2WPKH";
keyDetails.outputTypeDesc = "Pay to Witness Public Key Hash, aka Native Segwit";
keyDetails.bip32Path = "m/84'/0'";

const xpub_tpub = global.activeBlockchain == "main" ? "xpub" : "tpub";
const ypub_upub = global.activeBlockchain == "main" ? "ypub" : "upub";

const xpub = utils.xpubChangeVersionBytes(extendedPubkey, xpub_tpub);

keyDetails.relatedKeys.push({
keyType: xpub_tpub,
key: xpub,
outputType: "P2PKH",
firstAddress: utils.bip32Addresses(xpub, "p2pkh", 0, 1, 0)[0]
});

keyDetails.relatedKeys.push({
keyType: ypub_upub,
key: utils.xpubChangeVersionBytes(xpub, ypub_upub),
outputType: "P2WPKH in P2SH",
firstAddress: utils.bip32Addresses(xpub, "p2sh(p2wpkh)", 0, 1, 0)[0]
});

} else if (extendedPubkey.startsWith("Ypub")) {
keyDetails.outputType = "Multi-Sig P2WSH in P2SH";
keyDetails.bip32Path = "-";

} else if (extendedPubkey.startsWith("Zpub")) {
keyDetails.outputType = "Multi-Sig P2WSH";
keyDetails.bip32Path = "-";
}

return keyDetails;
}


// 0 is receive
// 1 is change
function getXpubAddresses(extendedPubkey, receiveOrChange=0, limit=20, offset=0) {
const xpub_tpub = global.activeBlockchain == "main" ? "xpub" : "tpub";
const ypub_upub = global.activeBlockchain == "main" ? "ypub" : "upub";
const zpub_vpub = global.activeBlockchain == "main" ? "zpub" : "vpub";

// if xpub/ypub/zpub convert to address under path m/0/0
if (extendedPubkey.match(/^(xpub|tpub).*$/)) {
let xpub = extendedPubkey;

if (!extendedPubkey.startsWith(xpub_tpub)) {
xpub = utils.xpubChangeVersionBytes(extendedPubkey, xpub_tpub);
}

return utils.bip32Addresses(xpub, "p2pkh", receiveOrChange, limit, offset);

} else if (extendedPubkey.match(/^(ypub|upub).*$/)) {
let xpub = utils.xpubChangeVersionBytes(extendedPubkey, xpub_tpub);

return utils.bip32Addresses(xpub, "p2sh(p2wpkh)", receiveOrChange, limit, offset);

} else if (extendedPubkey.match(/^(zpub|vpub).*$/)) {
let xpub = utils.xpubChangeVersionBytes(extendedPubkey, xpub_tpub);

return utils.bip32Addresses(xpub, "p2wpkh", receiveOrChange, limit, offset);
}

return [];
}


// gapLimit=20, default as per bip32
async function searchXpubTxids(extendedPubkey, gapLimit=20, addressLimit=-1) {
// addressLimit == -1 means we get every address with a transaction and 20 addresses gap at the end.
let sort = "desc";

let txLimit = 20;
let txOffset = 0;
let addressCount = 0;
let gapCounts = {"0": 0, "1": 0};
let result = {
usedAddresses: [],
emptyAddresses: {
receive: [],
change: []
}
};

while ((addressCount < addressLimit) || addressLimit == -1) {
for (let receiveOrChange = 0; receiveOrChange <= 1; receiveOrChange++) {
if (gapCounts[receiveOrChange] < gapLimit) {
txOffset = 0;

let address = getXpubAddresses(extendedPubkey, receiveOrChange, 1, addressCount)[0];
let getAddressResult = await coreApi.getAddress(address);

if (getAddressResult) {
let moreTx = true;

while (moreTx) {
let detailsResult = await addressApi.getAddressDetails(getAddressResult.address, getAddressResult.scriptPubKey, sort, txLimit, txOffset);

if (detailsResult && detailsResult.addressDetails && detailsResult.addressDetails.txids) {
if (detailsResult.addressDetails.txids.length == 0) {
result.emptyAddresses[receiveOrChange == 0 ? "receive" : "change"].push(address);

gapCounts[receiveOrChange]++;
moreTx = false;

} else {
gapCounts[receiveOrChange] = 0;

result.usedAddresses.push({
addressIndex: addressCount,
address: address,
type: receiveOrChange == 0 ? "receive" : "change",
txids: detailsResult.addressDetails.txids,
priorGap: gapCounts[receiveOrChange]
});

if (detailsResult.addressDetails.txids.length < txLimit) {
moreTx = false;
}
}
} else {
result.emptyAddresses[receiveOrChange == 0 ? "receive" : "change"].push(address);
}

txOffset += txLimit;
}
}
}
}

addressCount++;

// gap of N (20) receive and change addresses found
if (gapCounts[0] >= gapLimit && gapCounts[1] >= gapLimit) {
break;
}
}


return result;
}


module.exports = {
getKeyDetails: getKeyDetails,
getXpubAddresses: getXpubAddresses,
searchXpubTxids: searchXpubTxids
};

Loading

1 comment on commit e506070

@pointbiz
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested yet but these improvements look great. Thank you for the merge!

I'm wondering why 2 endpoints will return the addresses for the xpub?

Also, your testurl looks like it has the wrong path for the one that was under the api/utils route.

Please sign in to comment.