diff --git a/artifacts/lockup.json b/artifacts/lockup.json new file mode 100644 index 0000000..8b94f28 --- /dev/null +++ b/artifacts/lockup.json @@ -0,0 +1,48 @@ +{ + "version": 9, + "compilerVersion": "1.19.0+commit.72eaeba", + "contract": "Lockup", + "md5": "c54afe5c905a671b9be3909711368949", + "structs": [], + "library": [], + "alias": [], + "abi": [ + { + "type": "function", + "name": "redeem", + "index": 0, + "params": [ + { + "name": "sig", + "type": "Sig" + }, + { + "name": "pubkey", + "type": "PubKey" + }, + { + "name": "__scrypt_ts_txPreimage", + "type": "SigHashPreimage" + } + ] + }, + { + "type": "constructor", + "params": [ + { + "name": "pkhash", + "type": "Ripemd160" + }, + { + "name": "lockUntilHeight", + "type": "int" + } + ] + } + ], + "stateProps": [], + "buildType": "debug", + "file": "file:///Users/guillaume/Desktop/powco/boostpow-api/artifacts/lockup.scrypt", + "hex": "2097dfd76851bf465e8f715593b217714858bbe9570ff3bd5e33840a34e20ff0262102ba79df5f8ae7604a9830f03c7933028186aede0675a16f025dc4f8be8eec0382201008ce7480da41702918d1ec8e6849ba32b4d65b1e40dc669c31a1e6306b266c0000610079040065cd1d9f690079547a75537a537a537a5179537a75527a527a7575615579014161517957795779210ac407f0e4bd44bfc207355a778b046225a7068fc59ee7eda43ad905aadbffc800206c266b30e6a1319c66dc401e5bd6b432ba49688eecd118297041da8074ce081059795679615679aa0079610079517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e01007e81517a75615779567956795679567961537956795479577995939521414136d08c5ed2bf3ba048afe6dcaebafeffffffffffffffffffffffffffffff00517951796151795179970079009f63007952799367007968517a75517a75517a7561527a75517a517951795296a0630079527994527a75517a6853798277527982775379012080517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f517f7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e7c7e01205279947f7754537993527993013051797e527e54797e58797e527e53797e52797e57797e0079517a75517a75517a75517a75517a75517a75517a75517a75517a75517a75517a75517a75517a756100795779ac517a75517a75517a75517a75517a75517a75517a75517a75517a7561517a75517a756169557961007961007982775179517954947f75517958947f77517a75517a756161007901007e81517a7561517a7561040065cd1d9f6955796100796100798277517951790128947f755179012c947f77517a75517a756161007901007e81517a7561517a756105ffffffff009f69557961007961007982775179517954947f75517958947f77517a75517a756161007901007e81517a7561517a75615279a2695679a95179876957795779ac7777777777777777", + "sourceMapFile": "" +} \ No newline at end of file diff --git a/artifacts/lockup.scrypt b/artifacts/lockup.scrypt new file mode 100644 index 0000000..473abd7 --- /dev/null +++ b/artifacts/lockup.scrypt @@ -0,0 +1,18 @@ + +contract Lockup { + int lockUntilHeight; + Ripemd160 pkhash; + constructor(Ripemd160 pkhash, int lockUntilHeight) { + require(lockUntilHeight < 500000000); + this.lockUntilHeight = lockUntilHeight; + this.pkhash = pkhash; + } + public function redeem(Sig sig, PubKey pubkey, SigHashPreimage __scrypt_ts_txPreimage) { + require(Tx.checkPreimageSigHashType(__scrypt_ts_txPreimage, SigHashType(b'41'))); + require(SigHash.nLocktime(__scrypt_ts_txPreimage) < 500000000); + require(SigHash.nSequence(__scrypt_ts_txPreimage) < 0xffffffff); + require(SigHash.nLocktime(__scrypt_ts_txPreimage) >= this.lockUntilHeight); + require(hash160(pubkey) == this.pkhash); + require(checkSig(sig, pubkey)); + } +} \ No newline at end of file diff --git a/artifacts/lockup.scrypt.map b/artifacts/lockup.scrypt.map new file mode 100644 index 0000000..b1ee587 --- /dev/null +++ b/artifacts/lockup.scrypt.map @@ -0,0 +1 @@ +[[],[[9,0,12,13]],[[2,0,14,21],[6,0,14,4]],[[2,0,17,12],[12,0,17,4]],[[2,0,19,4],[14,0,19,24],[24,0,19,16],[32,0,19,53],[36,0,19,36]],[[4,0,21,8],[12,0,21,15],[28,0,21,15],[30,0,21,33]],[[4,0,22,8],[8,0,22,13],[25,0,22,8],[27,0,22,31]],[[4,0,23,8],[8,0,23,13],[16,0,23,8],[18,0,23,22]],[],[[2,0,27,4],[18,0,27,11],[25,0,27,23],[29,0,27,18],[34,0,27,36],[41,0,27,28]],[],[[4,0,28,8],[19,0,28,24],[54,0,28,15],[56,0,28,35]],[[4,0,29,8],[19,0,29,24],[54,0,29,15],[56,0,29,35]],[[4,0,30,8],[19,0,31,21],[54,0,31,12],[57,0,31,33],[61,0,31,38]],[[4,0,34,8],[12,0,35,12],[20,0,35,20],[28,0,35,12],[31,0,35,31],[35,0,35,36]],[[4,0,39,8],[12,0,39,20],[20,0,39,15],[21,0,39,29],[26,0,39,34],[32,0,39,15]],[],[]] \ No newline at end of file diff --git a/artifacts/lockup.transformer.json b/artifacts/lockup.transformer.json new file mode 100644 index 0000000..74275fc --- /dev/null +++ b/artifacts/lockup.transformer.json @@ -0,0 +1,9 @@ +{ + "success": true, + "errors": [], + "scryptfile": "lockup.scrypt", + "sourceMapFile": "lockup.scrypt.map", + "ctxMethods": [ + "redeem" + ] +} \ No newline at end of file diff --git a/bsv-spv-importers/lockup-listener.ts b/bsv-spv-importers/lockup-listener.ts new file mode 100644 index 0000000..6277cd1 --- /dev/null +++ b/bsv-spv-importers/lockup-listener.ts @@ -0,0 +1,116 @@ +require('dotenv').config() + +const { Listener } = require("bsv-spv"); + +const name = "lockup"; +const ticker = "BSV"; +const blockHeight = 807000; // Number. If negative then it's number from the tip. +const dataDir = __dirname; +const port = 8080; // Same as Masters port above +const listener = new Listener({ name, ticker, blockHeight, dataDir }); + +const { connect } = require('amqplib') + +import { bsv } from "scrypt-ts" +import axios from "axios" +import { detectLockupFromTxHex } from "../src/contracts/lockup"; + +var amqp; + +async function startAmqp() { + + const connection = await connect(process.env.amqp_url) + + amqp = await connection.createChannel() + + await amqp.assertExchange('powco') + +} + +startAmqp() + + +const onBlock = async ({ + header, + started, + finished, + size, + height, + txCount, + transactions, + startDate, +}) => { + for (const [index, tx, pos, len] of transactions) { + + const hex = tx.toHex() + + let [lockup, vout] = await detectLockupFromTxHex(hex) + + if(lockup){ + + try { + + const result = await axios.post('https://hls.pow.co/api/v1/contracts', { + origin: `${tx.hash}_${vout}`, + class_name: 'Lockup', + props: { + lockUntilHeight: lockup.lockUntilHeight, + pkhash: lockup.pkhash + } + }) + + } catch (error) { + + console.log('ingest.lockup.error', error) + + } + + } + + + } +}; + +listener.on("mempool_tx", async ({ transaction, size }) => { + + try { + + const hex = transaction.toHex() + + let [lockup, vout] = await detectLockupFromTxHex(hex) + + if(lockup){ + + try { + + const result = await axios.post('https://hls.pow.co/api/v1/contracts', { + origin: `${transaction.hash}_${vout}`, + class_name: 'Lockup', + props: { + lockUntilHeight: lockup.lockUntilHeight, + pkhash: lockup.pkhash + } + }) + + } catch (error) { + + console.log('ingest.lockup.error', error) + + } + } + + + } catch(error) { + + } + +}); +listener.on("block_reorg", ({ height, hash }) => { + // Re-org after height +}); +listener.on("block_saved", ({ height, hash }) => { + listener.syncBlocks(onBlock); +}); + +listener.syncBlocks(onBlock); +listener.connect({ port }); diff --git a/package-lock.json b/package-lock.json index 174cc8c..8e84d14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,7 +58,7 @@ "redis": "^4.6.5", "require-all": "^3.0.0", "run-sdk": "^0.6.41", - "scrypt-ts": "^0.1.7", + "scrypt-ts": "^1.3.5", "sequelize": "^6.26.0", "snarkdown": "^2.0.0", "socket.io": "^4.5.1", @@ -248,9 +248,9 @@ } }, "contracts/personal-interest/node_modules/scrypt-ts": { - "version": "0.2.2-beta.5", - "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-0.2.2-beta.5.tgz", - "integrity": "sha512-KSgErYMaP7bObWgnXcU4dKXwVmD3U/qS3+TQeykBDMW695kiF86sAvu/NfoSNu2jddYhWfuSK0PwBF6eeD8OOw==", + "version": "0.2.2-test.1", + "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-0.2.2-test.1.tgz", + "integrity": "sha512-Z7JRK95lumCc04NGxXHL71IoXn0mz8LZCPWLly7mg5Dq1nnMZJKxTUN0DICq79OXg3CUtseDTPoOzyyicQSF3Q==", "hasInstallScript": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", @@ -260,7 +260,7 @@ "lodash": "^4.17.21", "object-hash": "^3.0.0", "reflect-metadata": "^0.1.13", - "scryptlib": "^2.1.19", + "scryptlib": "^2.1.17", "socket.io-client": "^4.6.1", "superagent": "^8.0.9", "ts-patch": "^2.0.2", @@ -17351,21 +17351,18 @@ } }, "node_modules/scrypt-ts": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-0.1.7.tgz", - "integrity": "sha512-vZqCzTxJqEhbo0xngQQMV8RpOSJQuJPmMBFQ25o/1XCUdFdd//JT8BRKxyg8u/moUKe+R4r+LmZP3vNBDTVvDA==", - "hasInstallScript": true, + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-1.3.11.tgz", + "integrity": "sha512-D0uLnfVV3a5+NDB5WXxVz/zha7BOGYbx2TqKjstSIieiInIWNjTyBKTUsNxehe4FCEaXaokFW9s3iAs3a+xkTw==", "dependencies": { - "@phenomnomnominal/tsquery": "^5.0.0", + "deep-equal": "^2.2.0", "fast-diff": "^1.2.0", "lodash": "^4.17.21", "object-hash": "^3.0.0", "reflect-metadata": "^0.1.13", - "scryptlib": "^2.1.10", - "sourcemap-codec": "^1.4.8", - "superagent": "^8.0.9", - "ts-patch": "^2.0.2", - "typescript": "=4.8.4" + "scryptlib": "^2.1.29", + "socket.io-client": "^4.6.1", + "superagent": "^8.0.9" }, "engines": { "node": ">=16" @@ -17432,25 +17429,14 @@ "node": ">=6.4.0 <13 || >=14" } }, - "node_modules/scrypt-ts/node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, "node_modules/scryptlib": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/scryptlib/-/scryptlib-2.1.21.tgz", - "integrity": "sha512-CtkfKZqe74soTtWiqsQtXYYK/u6W8CuowVuvpvttay7kIVce+xpbok10piprNISElcwr8cD66T3RqF3nHJTrqg==", + "version": "2.1.29", + "resolved": "https://registry.npmjs.org/scryptlib/-/scryptlib-2.1.29.tgz", + "integrity": "sha512-rhGMdyl81/KB3pIoAkpd1CeODLNAvsLchS842k+VQJZjh6z0mPaBaJY+nxa7uW+fraF6chCW9bUWaYPQ/OSK/g==", "hasInstallScript": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.7", + "@jridgewell/sourcemap-codec": "^1.4.15", "bsv": "^1.5.6", "compare-versions": "^3.6.0", "find-node-modules": "^2.1.3", @@ -17459,7 +17445,6 @@ "node-fetch": "^3.0.0", "patch-package": "^6.4.7", "rimraf": "^3.0.2", - "sourcemap-codec": "^1.4.8", "yargs": "^17.6.2" }, "bin": { @@ -17469,6 +17454,11 @@ "node": ">=14.0.0" } }, + "node_modules/scryptlib/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, "node_modules/scryptlib/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -18491,12 +18481,6 @@ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "deprecated": "See https://github.com/lydell/source-map-url#deprecated" }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead" - }, "node_modules/spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", @@ -19340,63 +19324,6 @@ "node": ">=0.3.1" } }, - "node_modules/ts-patch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-patch/-/ts-patch-2.1.0.tgz", - "integrity": "sha512-+6LbQSGgHUnK+grgk9nvKhesc0/dDNxms0IL1XPZeTfmPFCx/QSuwz9k+9yFe0xYDD7xBlHYK0Zp0qrTCaJcAw==", - "dependencies": { - "chalk": "^4.1.2", - "glob": "^8.0.3", - "global-prefix": "^3.0.0", - "minimist": "^1.2.6", - "resolve": "^1.22.1", - "shelljs": "^0.8.5", - "strip-ansi": "^6.0.1" - }, - "bin": { - "ts-patch": "bin/cli.js" - }, - "peerDependencies": { - "typescript": ">=4.0.0" - } - }, - "node_modules/ts-patch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/ts-patch/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ts-patch/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/tslib": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", @@ -33085,9 +33012,9 @@ "dev": true }, "scrypt-ts": { - "version": "0.2.2-beta.5", - "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-0.2.2-beta.5.tgz", - "integrity": "sha512-KSgErYMaP7bObWgnXcU4dKXwVmD3U/qS3+TQeykBDMW695kiF86sAvu/NfoSNu2jddYhWfuSK0PwBF6eeD8OOw==", + "version": "0.2.2-test.1", + "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-0.2.2-test.1.tgz", + "integrity": "sha512-Z7JRK95lumCc04NGxXHL71IoXn0mz8LZCPWLly7mg5Dq1nnMZJKxTUN0DICq79OXg3CUtseDTPoOzyyicQSF3Q==", "requires": { "@jridgewell/sourcemap-codec": "^1.4.15", "@phenomnomnominal/tsquery": "^5.0.0", @@ -33096,7 +33023,7 @@ "lodash": "^4.17.21", "object-hash": "^3.0.0", "reflect-metadata": "^0.1.13", - "scryptlib": "^2.1.19", + "scryptlib": "^2.1.17", "socket.io-client": "^4.6.1", "superagent": "^8.0.9", "ts-patch": "^2.0.2", @@ -34553,20 +34480,18 @@ } }, "scrypt-ts": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-0.1.7.tgz", - "integrity": "sha512-vZqCzTxJqEhbo0xngQQMV8RpOSJQuJPmMBFQ25o/1XCUdFdd//JT8BRKxyg8u/moUKe+R4r+LmZP3vNBDTVvDA==", + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/scrypt-ts/-/scrypt-ts-1.3.11.tgz", + "integrity": "sha512-D0uLnfVV3a5+NDB5WXxVz/zha7BOGYbx2TqKjstSIieiInIWNjTyBKTUsNxehe4FCEaXaokFW9s3iAs3a+xkTw==", "requires": { - "@phenomnomnominal/tsquery": "^5.0.0", + "deep-equal": "^2.2.0", "fast-diff": "^1.2.0", "lodash": "^4.17.21", "object-hash": "^3.0.0", "reflect-metadata": "^0.1.13", - "scryptlib": "^2.1.10", - "sourcemap-codec": "^1.4.8", - "superagent": "^8.0.9", - "ts-patch": "^2.0.2", - "typescript": "=4.8.4" + "scryptlib": "^2.1.29", + "socket.io-client": "^4.6.1", + "superagent": "^8.0.9" }, "dependencies": { "form-data": { @@ -34614,20 +34539,16 @@ "qs": "^6.11.0", "semver": "^7.3.8" } - }, - "typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==" } } }, "scryptlib": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/scryptlib/-/scryptlib-2.1.21.tgz", - "integrity": "sha512-CtkfKZqe74soTtWiqsQtXYYK/u6W8CuowVuvpvttay7kIVce+xpbok10piprNISElcwr8cD66T3RqF3nHJTrqg==", + "version": "2.1.29", + "resolved": "https://registry.npmjs.org/scryptlib/-/scryptlib-2.1.29.tgz", + "integrity": "sha512-rhGMdyl81/KB3pIoAkpd1CeODLNAvsLchS842k+VQJZjh6z0mPaBaJY+nxa7uW+fraF6chCW9bUWaYPQ/OSK/g==", "requires": { "@discoveryjs/json-ext": "^0.5.7", + "@jridgewell/sourcemap-codec": "^1.4.15", "bsv": "^1.5.6", "compare-versions": "^3.6.0", "find-node-modules": "^2.1.3", @@ -34636,10 +34557,14 @@ "node-fetch": "^3.0.0", "patch-package": "^6.4.7", "rimraf": "^3.0.2", - "sourcemap-codec": "^1.4.8", "yargs": "^17.6.2" }, "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -35424,11 +35349,6 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" - }, "spawn-wrap": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", @@ -36081,50 +36001,6 @@ } } }, - "ts-patch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-patch/-/ts-patch-2.1.0.tgz", - "integrity": "sha512-+6LbQSGgHUnK+grgk9nvKhesc0/dDNxms0IL1XPZeTfmPFCx/QSuwz9k+9yFe0xYDD7xBlHYK0Zp0qrTCaJcAw==", - "requires": { - "chalk": "^4.1.2", - "glob": "^8.0.3", - "global-prefix": "^3.0.0", - "minimist": "^1.2.6", - "resolve": "^1.22.1", - "shelljs": "^0.8.5", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "requires": { - "brace-expansion": "^2.0.1" - } - } - } - }, "tslib": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", diff --git a/package.json b/package.json index 9a4384b..b7557b5 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "redis": "^4.6.5", "require-all": "^3.0.0", "run-sdk": "^0.6.41", - "scrypt-ts": "^0.1.7", + "scrypt-ts": "^1.3.5", "sequelize": "^6.26.0", "snarkdown": "^2.0.0", "socket.io": "^4.5.1", diff --git a/scrypt.index.json b/scrypt.index.json new file mode 100644 index 0000000..170d5fd --- /dev/null +++ b/scrypt.index.json @@ -0,0 +1 @@ +{"scryptBase":"artifacts","bindings":[{"symbol":"Lockup","path":"lockup.scrypt"}]} \ No newline at end of file diff --git a/src/contracts/lockup.ts b/src/contracts/lockup.ts new file mode 100644 index 0000000..9d82383 --- /dev/null +++ b/src/contracts/lockup.ts @@ -0,0 +1,82 @@ +import { + method, + prop, + SmartContract, + assert, + PubKeyHash, + Sig, + PubKey, + hash160, + bsv +} from 'scrypt-ts' + +export class Lockup extends SmartContract { + @prop() + lockUntilHeight: bigint + + @prop() + pkhash: PubKeyHash + + constructor(pkhash: PubKeyHash, lockUntilHeight: bigint) { + super(...arguments) + assert(lockUntilHeight < 500000000, 'must use blockHeight locktime') + this.lockUntilHeight = lockUntilHeight + this.pkhash = pkhash + } + + @method() + public redeem(sig: Sig, pubkey: PubKey) { + assert(this.ctx.locktime < 500000000, 'must use blockHeight locktime') + assert(this.ctx.sequence < 0xffffffff, 'must use sequence locktime') + assert( + this.ctx.locktime >= this.lockUntilHeight, + 'lockUntilHeight not reached' + ) + assert( + hash160(pubkey) == this.pkhash, + 'public key hashes are not equal' + ) + // Check signature validity. + assert(this.checkSig(sig, pubkey), 'signature check failed') + } +} + +let initialized = false + +export async function init(){ + + if (initialized) { return } + + await Lockup.compile() + + initialized = true + +} + +export async function detectLockupFromTxHex(txhex:string): Promise<[Lockup | null,number]> { + + await init() + + let lockup = null + let vout = 0 + + const tx = new bsv.Transaction(txhex) + + for (let i = 0; i < tx.outputs.length; i++) { + + try { + + lockup = Lockup.fromTx(tx,i) + vout = i + + break + + } catch (error) { + + console.log(error) + } + + } + + return [lockup,vout] +} diff --git a/src/models/index.ts b/src/models/index.ts index 4979168..b864f36 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -26,6 +26,7 @@ export interface Database { Block: any; WebhookReceived: any; Video: any; + SmartContracts: any; }; var db: Database | any = {}; diff --git a/src/models/smart_contract.ts b/src/models/smart_contract.ts index b6d7f6d..da44d58 100644 --- a/src/models/smart_contract.ts +++ b/src/models/smart_contract.ts @@ -1,7 +1,7 @@ import { Model, DataTypes } from 'sequelize' -import sequelize from '../src/sequelize' +import sequelize from '../sequelize' export class SmartContract extends Model { /** @@ -16,25 +16,54 @@ export class SmartContract extends Model { get id(): number { return this.getDataValue('id') -} + + } get origin(): string { return this.getDataValue('origin') } + get location(): string { + + return this.getDataValue('location') +} + get class_name(): string { return this.getDataValue('class_name') } + + set txid(value) { + + this.setDataValue('txid', value) + } + + set vout(value) { + + this.setDataValue('vout', value) + } + + get props() { + + return this.getDataValue('props') + } + + set balance(amount) { + + this.setDataValue('balance', amount) + } } SmartContract.init({ class_name: DataTypes.STRING, class_id: DataTypes.STRING, origin: DataTypes.STRING, + txid: DataTypes.STRING, + vout: DataTypes.INTEGER, location: DataTypes.STRING, props: DataTypes.JSON, + balance: DataTypes.INTEGER, timestamp: DataTypes.INTEGER }, { sequelize, diff --git a/src/sequelize.ts b/src/sequelize.ts new file mode 100644 index 0000000..3acbfd5 --- /dev/null +++ b/src/sequelize.ts @@ -0,0 +1,17 @@ +const fs = require('fs'); +const path = require('path'); +import { Sequelize, DataTypes, Model } from 'sequelize' +const process = require('process'); +const basename = path.basename(__filename); +const env = process.env.NODE_ENV || 'development'; +const config = require(__dirname + '/../config/config.json')[env]; + +let sequelize: Sequelize; + +if (config.use_env_variable) { + sequelize = new Sequelize(process.env[config.use_env_variable], config); +} else { + sequelize = new Sequelize(config.database, config.username, config.password, config); +} + +export default sequelize diff --git a/src/server.ts b/src/server.ts index 1632cc9..e25541c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -960,6 +960,69 @@ export async function buildServer(): Server { } }) + server.route({ + method: 'GET', + path: '/api/v1/lockups/ranking', + handler: handlers.Lockups.index, + options: { + description: 'Rank content by lockups', + tags: ['api', 'lockups'], + response: { + failAction: 'log', + schema: Joi.object({ + txid: Joi.string().required(), + lockups: Joi.array().items(Joi.object({ + origin: Joi.string(), + location: Joi.string(), + props: Joi.object({ + pkhash: Joi.string(), + lockUntilHeight: Joi.number() + }), + txid: Joi.string(), + vout: Joi.number(), + balance: Joi.number(), + timestamp: Joi.date(), + vibes: Joi.number() + })) + }) + } + } + }) + + server.route({ + method: 'GET', + path: '/api/v1/lockups/ranking/{address}', + handler: handlers.Lockups.byAddress, + options: { + description: 'Rank content by lockups', + tags: ['api', 'lockups'], + response: { + failAction: 'log', + schema: Joi.object({ + txid: Joi.string().required(), + lockups: Joi.array().items(Joi.object({ + origin: Joi.string(), + location: Joi.string(), + props: Joi.object({ + pkhash: Joi.string(), + lockUntilHeight: Joi.number() + }), + txid: Joi.string(), + vout: Joi.number(), + balance: Joi.number(), + timestamp: Joi.date(), + vibes: Joi.number() + })) + }), + validate: { + params: Joi.object({ + address: Joi.string().required() + }) + } + } + } + }) + const swaggerOptions = { info: { title: 'Powco API Docs', diff --git a/src/server/handlers/lockups.ts b/src/server/handlers/lockups.ts new file mode 100644 index 0000000..5e5a18a --- /dev/null +++ b/src/server/handlers/lockups.ts @@ -0,0 +1,297 @@ +import { badRequest } from 'boom' + +import { bsv } from 'scrypt-ts' + +import { Lockup } from '../../contracts/lockup' + +import { fetchTransaction } from '../../whatsonchain' +import models from "../../models" + +import { sequelize } from '../../models' +import { Op } from 'sequelize' +import axios from 'axios' + +interface Ranking { + txid: string; + lockups: any[]; +} + +export async function index(req) { + + try { + + const chainInfo = await axios.get('https://api.whatsonchain.com/v1/bsv/main/chain/info') + + const currentBlock = chainInfo.data.blocks + + const { limit, offset, start, end } = req.query + + const where = { + class_name: 'Lockup', + props: { + lockUntilHeight: { + [Op.gt]: currentBlock + } + } + } + + if (start){ + + where['timestamp'] = { + [Op.gte]: new Date(start * 1000) + } + + } + + if (end){ + + where['timestamp'] = { + [Op.lte]: new Date(end * 1000) + } + + } + + const lockups = await models.SmartContract.findAll({ + where, + limit, + offset, + attributes: [ + 'origin', + 'location', + 'props', + 'txid', + 'vout', + 'balance', + 'timestamp', + [sequelize.literal('(balance * LN("props"."lockUntilHeight") * 10)'), 'vibes'], + ], + order: [ + ['vibes', 'DESC'] + ] + }) + + const txids = lockups.map((lockup) => lockup.txid); + + const content = await models.Content.findAll({ + where: { + txid: { + [Op.in]: txids, + }, + }, + }); + const rankings: Ranking[] = [] + + for (const lockup of lockups) { + const lockupContent = content.find((c) => c.txid === lockup.txid); + let lockupTxid = '' + if (lockupContent.bmap.MAP[0].type === "like"){ + lockupTxid = lockupContent.bmap.MAP[0].tx + } else { + lockupTxid = lockup.txid + } + + // Check if lockupTxid already exists in rankings + const existingRanking = rankings.find((ranking) => ranking.txid === lockupTxid); + + if (existingRanking) { + // If the txid already exists, add the current lockup to its lockup array + existingRanking.lockups.push(lockup); + } else { + // If the txid doesn't exist, create a new ranking entry + rankings.push({ + txid: lockupTxid, + lockups: [lockup], + }); + } + + + } + + // Sort the rankings array based on the sum of all lockup vibes in descending order + rankings.sort((a, b) => { + const sumVibesA = a.lockups.reduce((acc, lockup) => acc + lockup.vibes, 0); + const sumVibesB = b.lockups.reduce((acc, lockup) => acc + lockup.vibes, 0); + + return sumVibesB - sumVibesA; + }); + + return { rankings } + + } catch (error) { + + console.log(error) + + return badRequest(error) + + } +} + +export async function byAddress(req) { + + try { + + const chainInfo = await axios.get('https://api.whatsonchain.com/v1/bsv/main/chain/info') + + const currentBlock = chainInfo.data.blocks + + const { limit, offset, start, end } = req.query + + const { address } = req.params + + const where = { + class_name: 'Lockup', + props: { + lockUntilHeight: { + [Op.gt]: currentBlock + }, + pkhash: {[Op.eq]: address} + } + } + + if (start){ + + where['timestamp'] = { + [Op.gte]: new Date(start * 1000) + } + + } + + if (end){ + + where['timestamp'] = { + [Op.lte]: new Date(end * 1000) + } + + } + + const lockups = await models.SmartContract.findAll({ + where, + limit, + offset, + attributes: [ + 'origin', + 'location', + 'props', + 'txid', + 'vout', + 'balance', + 'timestamp', + [sequelize.literal('(balance * LN("props"."lockUntilHeight") * 10)'), 'vibes'], + ], + order: [ + ['vibes', 'DESC'] + ] + }) + + const txids = lockups.map((lockup) => lockup.txid); + + const content = await models.Content.findAll({ + where: { + txid: { + [Op.in]: txids, + }, + }, + }); + const rankings: Ranking[] = [] + + for (const lockup of lockups) { + const lockupContent = content.find((c) => c.txid === lockup.txid); + let lockupTxid = '' + if (lockupContent.bmap.MAP[0].type === "like"){ + lockupTxid = lockupContent.bmap.MAP[0].tx + } else { + lockupTxid = lockup.txid + } + + // Check if lockupTxid already exists in rankings + const existingRanking = rankings.find((ranking) => ranking.txid === lockupTxid); + + if (existingRanking) { + // If the txid already exists, add the current lockup to its lockup array + existingRanking.lockups.push(lockup); + } else { + // If the txid doesn't exist, create a new ranking entry + rankings.push({ + txid: lockupTxid, + lockups: [lockup], + }); + } + + + } + + // Sort the rankings array based on the sum of all lockup vibes in descending order + rankings.sort((a, b) => { + const sumVibesA = a.lockups.reduce((acc, lockup) => acc + lockup.vibes, 0); + const sumVibesB = b.lockups.reduce((acc, lockup) => acc + lockup.vibes, 0); + + return sumVibesB - sumVibesA; + }); + + return { rankings } + + } catch (error) { + + console.log(error) + + return badRequest(error) + + } +} + + +export async function show(req) { + + try { + + const [txid, vout] = req.params.origin.split('_') + + const txhex = await fetchTransaction({ txid }) + + const tx = new bsv.Transaction(txhex) + + console.log({ tx }) + + const lockup = Lockup.fromTx(tx, vout || 0) + + console.log({ lockup }) + + const result = { + address: lockup.pkhash.toString(), + blockHeight: Number(lockup.lockUntilHeight), + satoshis: tx.getOutputAmount(vout || 0), + origin: req.params.origin, + } + + console.log(result) + + const [record] = await models.SmartContract.findOrCreate({ + where: { + origin: req.params.origin, + class_name: 'Lockup' + }, + defaults: { + origin: req.params.origin, + class_name: 'Lockup', + props: { + lockUntilHeight: Number(lockup.lockUntilHeight), + pkhash: lockup.pkhash.toString(), + } + } + }) + + console.log(record.toJSON()) + + return { + lockup: result + } + + } catch (error) { + + console.log(error) + + badRequest(error) + + } +} + diff --git a/tsconfig-scryptTS.json b/tsconfig-scryptTS.json index b730110..e3df4f8 100644 --- a/tsconfig-scryptTS.json +++ b/tsconfig-scryptTS.json @@ -1,33 +1,33 @@ -{ - "compilerOptions": { - "target": "ES2020", - "lib": [ - "dom", - "dom.iterable", - "ES2020" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "commonjs", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "experimentalDecorators": true, - "plugins": [ - { - "transform": "scrypt-ts/dist/transformation/transformer", - "outDir": "./artifacts", - "transformProgram": true - } - ] - }, - "include": [ - "src/contracts/**.ts" - ] -} +{ + "compilerOptions": { + "target": "ES2020", + "lib": [ + "dom", + "dom.iterable", + "ES2020" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "commonjs", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "experimentalDecorators": true, + "plugins": [ + { + "transform": "scrypt-ts/dist/transformation/transformer", + "outDir": "./artifacts", + "transformProgram": true + } + ] + }, + "include": [ + "src/contracts/**.ts" + ] +} \ No newline at end of file