Changeset View
Changeset View
Standalone View
Standalone View
web/cashtab/src/utils/chronik.js
import BigNumber from 'bignumber.js'; | |||||
// Chronik methods | // Chronik methods | ||||
export const getUtxosSingleHashChronik = async (chronik, hash160) => { | export const getUtxosSingleHashChronik = async (chronik, hash160) => { | ||||
// Get utxos at a single address, which chronik takes in as a hash160 | // Get utxos at a single address, which chronik takes in as a hash160 | ||||
let utxos; | let utxos; | ||||
try { | try { | ||||
utxos = await chronik.script('p2pkh', hash160).utxos(); | utxos = await chronik.script('p2pkh', hash160).utxos(); | ||||
console.log(`utxos result for ${hash160}`, utxos); | |||||
if (utxos.length === 0) { | if (utxos.length === 0) { | ||||
// Chronik returns an empty array if there are no utxos at this hash160 | // Chronik returns an empty array if there are no utxos at this hash160 | ||||
return []; | return []; | ||||
} | } | ||||
/* Chronik returns an array of with a single object if there are utxos at this hash 160 | /* Chronik returns an array of with a single object if there are utxos at this hash 160 | ||||
[ | [ | ||||
{ | { | ||||
outputScript: <hash160>, | outputScript: <hash160>, | ||||
utxos:[{utxo}, {utxo}, ..., {utxo}] | utxos:[{utxo}, {utxo}, ..., {utxo}] | ||||
} | } | ||||
] | ] | ||||
*/ | */ | ||||
// Return only the array of utxos at this address | // Return only the array of utxos at this address | ||||
return utxos[0].utxos; | return utxos[0].utxos; | ||||
} catch (err) { | } catch (err) { | ||||
console.log(`Error in chronik.utxos(${hash160})`); | console.log( | ||||
console.log(err); | `Error in chronik.script('p2pkh', ${hash160}).utxos()`, | ||||
err, | |||||
); | |||||
} | } | ||||
}; | }; | ||||
export const returnGetUtxosChronikPromise = (chronik, hash160AndAddressObj) => { | export const returnGetUtxosChronikPromise = (chronik, hash160AndAddressObj) => { | ||||
/* | /* | ||||
Chronik thinks in hash160s, but people and wallets think in addresses | Chronik thinks in hash160s, but people and wallets think in addresses | ||||
Add the address to each utxo | Add the address to each utxo | ||||
*/ | */ | ||||
return new Promise((resolve, reject) => { | return new Promise((resolve, reject) => { | ||||
getUtxosSingleHashChronik(chronik, hash160AndAddressObj.hash160).then( | getUtxosSingleHashChronik(chronik, hash160AndAddressObj.hash160).then( | ||||
result => { | result => { | ||||
console.log( | |||||
`getUtxosSingleHashChronik result for ${hash160AndAddressObj.hash160}`, | |||||
result, | |||||
); | |||||
for (let i = 0; i < result.length; i += 1) { | for (let i = 0; i < result.length; i += 1) { | ||||
const thisUtxo = result[i]; | const thisUtxo = result[i]; | ||||
thisUtxo.address = hash160AndAddressObj.address; | thisUtxo.address = hash160AndAddressObj.address; | ||||
} | } | ||||
resolve(result); | resolve(result); | ||||
}, | }, | ||||
err => { | err => { | ||||
reject(err); | reject(err); | ||||
Show All 17 Lines | for (let i = 0; i < hash160sMappedToAddresses.length; i += 1) { | ||||
chronikUtxoPromises.push(thisPromise); | chronikUtxoPromises.push(thisPromise); | ||||
} | } | ||||
const allUtxos = await Promise.all(chronikUtxoPromises); | const allUtxos = await Promise.all(chronikUtxoPromises); | ||||
// Since each individual utxo has address information, no need to keep them in distinct arrays | // Since each individual utxo has address information, no need to keep them in distinct arrays | ||||
// Combine into one array of all utxos | // Combine into one array of all utxos | ||||
const flatUtxos = allUtxos.flat(); | const flatUtxos = allUtxos.flat(); | ||||
return flatUtxos; | return flatUtxos; | ||||
}; | }; | ||||
export const getSlpBalancesAndUtxosFromChronik = chronikUtxos => { | |||||
/* | |||||
Convert chronik utxos (returned by getUtxosChronik function, above) to match | |||||
shape of existing slpBalancesAndUtxos object | |||||
This means sequestering eToken utxos from non-eToken utxos | |||||
For legacy reasons, the term "SLP" is still sometimes used to describe an eToken | |||||
So, SLP utxos === eToken utxos, it's just a semantics difference here | |||||
*/ | |||||
const nonSlpUtxos = []; | |||||
const slpUtxos = []; | |||||
for (let i = 0; i < chronikUtxos.length; i += 1) { | |||||
// Construct nonSlpUtxos and slpUtxos arrays | |||||
const thisUtxo = chronikUtxos[i]; | |||||
const isEtoken = typeof thisUtxo.slpToken !== 'undefined'; | |||||
if (isEtoken) { | |||||
slpUtxos.push(thisUtxo); | |||||
} else { | |||||
nonSlpUtxos.push(thisUtxo); | |||||
} | |||||
} | |||||
// Iterate over the slpUtxos to create the 'tokens' object | |||||
let tokensById = {}; | |||||
slpUtxos.forEach(slpUtxo => { | |||||
/* | |||||
Note that a wallet could have many eToken utxos all belonging to the same eToken | |||||
For example, a user could have 100 of a certain eToken, but this is composed of | |||||
four utxos, one for 17, one for 50, one for 30, one for 3 | |||||
*/ | |||||
// Start with the existing object for this particular token, if it exists | |||||
let token = tokensById[slpUtxo.slpMeta.tokenId]; | |||||
if (token) { | |||||
if (slpUtxo.slpToken.amount) { | |||||
token.balance = token.balance.plus( | |||||
new BigNumber(slpUtxo.slpToken.amount), | |||||
); | |||||
} | |||||
} else { | |||||
// If it does not exist, create it | |||||
token = {}; | |||||
token.tokenId = slpUtxo.slpMeta.tokenId; | |||||
if (slpUtxo.slpToken.amount) { | |||||
token.balance = new BigNumber(slpUtxo.slpToken.amount); | |||||
} else { | |||||
token.balance = new BigNumber(0); | |||||
} | |||||
tokensById[slpUtxo.slpMeta.tokenId] = token; | |||||
} | |||||
}); | |||||
const tokens = Object.values(tokensById); | |||||
const chronikSlpBalancesAndUtxos = { slpUtxos, nonSlpUtxos, tokens }; | |||||
return chronikSlpBalancesAndUtxos; | |||||
}; | |||||
const getTxDetailsChronik = async (chronik, txid) => { | |||||
let txDetails; | |||||
try { | |||||
txDetails = await chronik.tx(txid); | |||||
return txDetails; | |||||
} catch (err) { | |||||
console.log(`Error in chronik.tx(${txid})`); | |||||
console.log(err); | |||||
} | |||||
}; | |||||
const returnGetTokenInfoChronikPromise = (chronik, tokenId) => { | |||||
return new Promise((resolve, reject) => { | |||||
getTxDetailsChronik(chronik, tokenId).then( | |||||
result => { | |||||
if (typeof result === 'undefined') { | |||||
console.log(`result`, result); | |||||
} | |||||
const thisTokenInfo = result.slpTxData.genesisInfo; | |||||
thisTokenInfo.tokenId = tokenId; | |||||
// You only want the genesis info for tokenId | |||||
resolve(thisTokenInfo); | |||||
}, | |||||
err => { | |||||
reject(err); | |||||
}, | |||||
); | |||||
}); | |||||
}; | |||||
export const addTokenInfo = async (chronik, tokens) => { | |||||
// for each token, get the genesis info | |||||
// parse token qty by decimal | |||||
const getTokenInfoPromises = []; | |||||
for (let i = 0; i < tokens.length; i += 1) { | |||||
const thisTokenId = tokens[i].tokenId; | |||||
const thisTokenInfoPromise = returnGetTokenInfoChronikPromise( | |||||
chronik, | |||||
thisTokenId, | |||||
); | |||||
getTokenInfoPromises.push(thisTokenInfoPromise); | |||||
} | |||||
let tokenInfoArray = await Promise.all(getTokenInfoPromises); | |||||
if (tokens.length !== tokenInfoArray.length) { | |||||
console.log( | |||||
`ERROR: tokenInfoArray length is ${tokenInfoArray.length}, while tokens length is ${tokens.length}`, | |||||
); | |||||
} | |||||
for (let i = 0; i < tokens.length; i += 1) { | |||||
const thisToken = tokens[i]; | |||||
const thisTokenId = thisToken.tokenId; | |||||
tokenInfoArrayLoop: for (let j = 0; j < tokenInfoArray.length; j += 1) { | |||||
const tokenInfoForTokenId = tokenInfoArray[j].tokenId; | |||||
if (thisTokenId === tokenInfoForTokenId) { | |||||
const thisGenesisInfo = tokenInfoArray[j]; | |||||
// Add this info to the utxo | |||||
tokens[i].info = thisGenesisInfo; | |||||
// Adjust the token balance for tokenDecimals | |||||
const tokenDecimals = thisGenesisInfo.decimals; | |||||
// Adjust tokenQty per decimal places | |||||
tokens[i].balance = tokens[i].balance.shiftedBy( | |||||
-1 * tokenDecimals, | |||||
); | |||||
// You won't need it again, so remove it from tokenInfoArray | |||||
tokenInfoArray.slice(j, 1); | |||||
// Do not iterate through the rest of tokenInfoArray once you have found what you are looking for | |||||
break tokenInfoArrayLoop; | |||||
} | |||||
} | |||||
} | |||||
return tokens; | |||||
}; |