diff --git a/react/lib/util/chronik.ts b/react/lib/util/chronik.ts index 90c001ff..7714d257 100644 --- a/react/lib/util/chronik.ts +++ b/react/lib/util/chronik.ts @@ -23,29 +23,29 @@ export function getNullDataScriptData (outputScript: string): OpReturnData | nul const opReturnCode = '6a' const encodedProtocolPushData = '04' // '\x04' const encodedProtocol = '50415900' // 'PAY\x00' - + const prefixLen = ( opReturnCode.length + encodedProtocolPushData.length + encodedProtocol.length + 2 // version byte ) - + const regexPattern = new RegExp( `${opReturnCode}${encodedProtocolPushData}${encodedProtocol}.{2}`, 'i' ) - + if (!regexPattern.test(outputScript.slice(0, prefixLen))) { return null } - + let dataStartIndex = prefixLen + 2 - + if (outputScript.length < dataStartIndex) { return null } - + let dataPushDataHex = outputScript.slice(prefixLen, dataStartIndex) if (dataPushDataHex.toLowerCase() === '4c') { dataStartIndex = dataStartIndex + 2 @@ -55,26 +55,26 @@ export function getNullDataScriptData (outputScript: string): OpReturnData | nul if (outputScript.length < dataStartIndex + dataPushData * 2) { return null } - + const dataHexBuffer = Buffer.from( outputScript.slice(dataStartIndex, dataStartIndex + dataPushData * 2), 'hex' ) const dataString = decoder.decode(dataHexBuffer) - + const ret: OpReturnData = { rawMessage: dataString, message: parseOpReturnData(dataString), paymentId: '' } - + const paymentIdPushDataIndex = dataStartIndex + dataPushData * 2 const paymentIdStartIndex = paymentIdPushDataIndex + 2 const hasPaymentId = outputScript.length >= paymentIdStartIndex if (!hasPaymentId) { return ret } - + const paymentIdPushDataHex = outputScript.slice(paymentIdPushDataIndex, paymentIdStartIndex) const paymentIdPushData = parseInt(paymentIdPushDataHex, 16) let paymentIdString = '' @@ -87,10 +87,10 @@ export function getNullDataScriptData (outputScript: string): OpReturnData | nul paymentIdString += hexByte } ret.paymentId = paymentIdString - + return ret } - + export function toHash160 (address: string): {type: AddressType, hash160: string} { try { const { type, hash } = decodeCashAddress(address) @@ -103,13 +103,13 @@ export function toHash160 (address: string): {type: AddressType, hash160: string export async function satoshisToUnit(satoshis: bigint, networkFormat: string): Promise { const decimal = new Decimal(satoshis.toString()) - + if (networkFormat === xecaddr.Format.Xecaddr) { return decimal.div(1e2).toString() } else if (networkFormat === xecaddr.Format.Cashaddr) { return decimal.div(1e8).toString() } - + throw new Error('[CHRONIK]: Invalid address') } @@ -148,6 +148,8 @@ const getTransactionAmountAndData = async (transaction: Tx, addressString: stri const getTransactionFromChronikTransaction = async (transaction: Tx, address: string): Promise => { const { amount, opReturn } = await getTransactionAmountAndData(transaction, address) const parsedOpReturn = resolveOpReturn(opReturn) + const networkSlug = getAddressPrefix(address) + const inputAddresses = getSortedInputAddresses(networkSlug, transaction) return { hash: transaction.txid, amount, @@ -158,19 +160,20 @@ const getTransactionFromChronikTransaction = async (transaction: Tx, address: st paymentId: parsedOpReturn?.paymentId ?? '', message: parsedOpReturn?.message ?? '', rawMessage: parsedOpReturn?.rawMessage ?? '', + inputAddresses, } } export const fromHash160 = (networkSlug: string, type: AddressType, hash160: string): string => { const buffer = Buffer.from(hash160, 'hex') - + // Because ecashaddrjs only accepts Uint8Array as input type, convert const hash160ArrayBuffer = new ArrayBuffer(buffer.length) const hash160Uint8Array = new Uint8Array(hash160ArrayBuffer) for (let i = 0; i < hash160Uint8Array.length; i += 1) { hash160Uint8Array[i] = buffer[i] } - + return encodeCashAddress( networkSlug, type, @@ -180,7 +183,7 @@ export const fromHash160 = (networkSlug: string, type: AddressType, hash160: st export function outputScriptToAddress (networkSlug: string, outputScript: string | undefined): string | undefined { if (outputScript === undefined) return undefined - + const typeTestSlice = outputScript.slice(0, 4) let addressType let hash160 @@ -202,9 +205,9 @@ export function outputScriptToAddress (networkSlug: string, outputScript: string default: return undefined } - + if (hash160.length !== 40) return undefined - + return fromHash160(networkSlug, addressType as AddressType, hash160) } @@ -216,12 +219,30 @@ const resolveOpReturn = (opReturn: string): OpReturnData | null => { } } +function getSortedInputAddresses (networkSlug: string, transaction: Tx): string[] { + const addressSatsMap = new Map() + + transaction.inputs.forEach((inp) => { + const address = outputScriptToAddress(networkSlug, inp.outputScript) + if (address !== undefined && address !== '') { + const currentValue = addressSatsMap.get(address) ?? BigInt(0) + addressSatsMap.set(address, currentValue + inp.sats) + } + }) + + const sortedInputAddresses = Array.from(addressSatsMap.entries()) + .sort(([, valueA], [, valueB]) => Number(valueB - valueA)) + .map(([address]) => address) + + return sortedInputAddresses +} + export const parseWebsocketMessage = async ( wsMsg: any, setNewTx: Function, chronik: ChronikClient, address: string -) => { +): Promise => { const { type } = wsMsg; if (type === 'Error') { return; @@ -247,7 +268,7 @@ export const initializeChronikWebsocket = async ( ): Promise => { const networkSlug = getAddressPrefix(address) const blockchainUrls = config.networkBlockchainURLs[networkSlug]; - + const chronik = await ChronikClient.useStrategy( ConnectionStrategy.AsOrdered, blockchainUrls, @@ -266,4 +287,4 @@ export const initializeChronikWebsocket = async ( ws.subscribeToAddress(address); return ws; -}; \ No newline at end of file +}; diff --git a/react/yarn.lock b/react/yarn.lock index 4ec34112..29aa1379 100644 --- a/react/yarn.lock +++ b/react/yarn.lock @@ -1583,6 +1583,16 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@humanwhocodes/retry@^0.3.0": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" + integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== + +"@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + "@isaacs/balanced-match@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" @@ -3644,7 +3654,7 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.8": +"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.6", "@types/estree@^1.0.8": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== @@ -4418,7 +4428,7 @@ acorn-import-phases@^1.0.3: resolved "https://registry.yarnpkg.com/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz#16eb850ba99a056cb7cbfe872ffb8972e18c8bd7" integrity sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ== -acorn-jsx@^5.2.0, acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: +acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== @@ -5527,7 +5537,7 @@ chronik-client-cashtokens@^3.1.1-rc0: dependencies: "@types/ws" "^8.2.1" axios "^1.6.3" - ecashaddrjs "file:../../../../../Library/Caches/Yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs" + ecashaddrjs "file:../../../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs" isomorphic-ws "^4.0.1" protobufjs "^6.8.8" ws "^8.3.0" @@ -5974,17 +5984,6 @@ cross-spawn@^4.0.0: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^6.0.5: - version "6.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57" - integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" @@ -6381,7 +6380,7 @@ debug@2.6.9, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.1: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b" integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== @@ -6762,7 +6761,7 @@ ecashaddrjs@^1.0.7: big-integer "1.6.36" bs58check "^3.0.1" -ecashaddrjs@^2.0.0, "ecashaddrjs@file:../ecashaddrjs": +ecashaddrjs@^2.0.0, "ecashaddrjs@file:../../../../../Library/Caches/Yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs": version "2.0.0" resolved "https://registry.yarnpkg.com/ecashaddrjs/-/ecashaddrjs-2.0.0.tgz#d45ede7fb6168815dbcf664b8e0a6872e485d874" integrity sha512-EvK1V4D3+nIEoD0ggy/b0F4lW39/72R9aOs/scm6kxMVuXu16btc+H74eQv7okNfXaQWKgolEekZkQ6wfcMMLw== @@ -7538,7 +7537,7 @@ esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.1, esquery@^1.4.2: +esquery@^1.4.2, esquery@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== @@ -8302,13 +8301,6 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== - dependencies: - type-fest "^0.8.1" - globals@^13.19.0: version "13.24.0" resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" @@ -8316,6 +8308,11 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + globalthis@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" @@ -8779,7 +8776,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: +import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== @@ -8847,25 +8844,6 @@ ini@^1.3.5: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== -inquirer@^7.0.0: - version "7.3.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" - integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.19" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.6.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - internal-slot@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" @@ -13986,7 +13964,7 @@ semver@^6.0.0, semver@^6.1.0, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.7.2: +semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.7.2: version "7.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==