├── .gitignore ├── ReadMe.md ├── logo.psd ├── public ├── os.png ├── logo-lg.png ├── logo-med.png ├── logo-sm.png └── index.html ├── package.json ├── vercel.json └── app └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | node_modules/ -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | ## Fraud score for ordinal based vaults ## -------------------------------------------------------------------------------- /logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genecyber/fraudGPT/HEAD/logo.psd -------------------------------------------------------------------------------- /public/os.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genecyber/fraudGPT/HEAD/public/os.png -------------------------------------------------------------------------------- /public/logo-lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genecyber/fraudGPT/HEAD/public/logo-lg.png -------------------------------------------------------------------------------- /public/logo-med.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genecyber/fraudGPT/HEAD/public/logo-med.png -------------------------------------------------------------------------------- /public/logo-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/genecyber/fraudGPT/HEAD/public/logo-sm.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fraudgpt", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "alchemy-sdk": "^2.4.3", 13 | "body-parser": "^1.20.1", 14 | "cheerio": "^1.0.0-rc.12", 15 | "cors": "^2.8.5", 16 | "express": "^4.18.2", 17 | "openai": "^3.1.0", 18 | "request-promise": "^4.2.6" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "version": 2, 4 | 5 | "env": { 6 | }, 7 | "builds": [ 8 | { 9 | "src": "app/index.js", 10 | "use": "@vercel/node" 11 | },{ 12 | "src": "public/**", 13 | "use": "@vercel/static" 14 | } 15 | ], 16 | "routes": [ 17 | { 18 | "src": "/v1/(.*)", 19 | "dest": "app/index.js" 20 | }, 21 | { 22 | "src": "/(.+)", 23 | "dest": "public/$1" 24 | }, 25 | { 26 | "src": "/", 27 | "dest": "public/index.html" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | // good btc punk https://opensea.io/assets/ethereum/0x82c7a8f707110f5fbb16184a5933e9f78a34c6ab/17567688516388371 2 | const express = require('express') 3 | var bodyParser = require('body-parser') 4 | const { Configuration, OpenAIApi } = require("openai") 5 | const Alchemy = require('alchemy-sdk').Alchemy 6 | const Network = require('alchemy-sdk').Network 7 | const rp = require('request-promise') 8 | const cheerio = require("cheerio") 9 | let ordinals = require("./collections/ordinals.json") 10 | var cors = require('cors') 11 | const configuration = new Configuration({ 12 | apiKey: process.env.OPENAI_API_KEY, 13 | }) 14 | const openai = new OpenAIApi(configuration) 15 | 16 | const settings = { 17 | apiKey: process.env.ALCHEMY_API_KEY, 18 | network: Network.ETH_MAINNET, 19 | } 20 | const alchemy = new Alchemy(settings) 21 | 22 | const app = express() 23 | app.use(cors()) 24 | app.options('*', cors()) // include before other routes 25 | app.use(bodyParser.json({ limit: '50mb' })); 26 | app.use(bodyParser.urlencoded({ limit: '50mb', extended: true })); 27 | const port = process.env.PORT || 3000; // default port to listen 28 | 29 | app.use(function (req, res, next) { 30 | res.header("Access-Control-Allow-Origin", "*"); 31 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 32 | next(); 33 | }); 34 | 35 | app.options("/*", function (req, res, next) { 36 | res.header('Access-Control-Allow-Origin', '*'); 37 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); 38 | res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With'); 39 | res.send(200); 40 | }) 41 | 42 | app.use(express.static('public')) 43 | 44 | app.get('/v1/meta', async (req, res)=>{ 45 | let url = req.query.url 46 | try {new URL(url)} catch(err){ 47 | return res.json({success: false, message: "invalid url"}, 500) 48 | } 49 | let tokenId = url.replace('=', '/').split('/').reverse()[0] 50 | let metadata = await fetchMetadataFromEmblem(tokenId) 51 | let liveMetadata = await getMetadataFromAlchemy(tokenId, VAULTADDRESSES.filter(item=>{return item.network == metadata.network})[0].address) 52 | let vaultBtcAddress = metadata.addresses? metadata.addresses.filter(address=>{return address.coin == 'BTC'})[0].address: "error" 53 | let vaultTaprootAddress = metadata.addresses? metadata.addresses.filter(address=>{return address.coin == 'TAP'}).length> 0? metadata.addresses.filter(address=>{return address.coin == 'TAP'})[0].address: false: false 54 | let balances = tokenId? (await fetchBalance(tokenId)).balances: null 55 | let assetName = metadata.name? metadata.name: metadata.rawMetadata && metadata.rawMetadata.name ? metadata.rawMetadata.name: metadata.contract.name? metadata.contract.name + " #" + metadata.tokenId : metadata.tokenId 56 | let properties = await classifyVaultWithGPT(assetName, metadata.description, balances) 57 | properties = properties.properties? properties.properties: properties 58 | if (properties.success == false) { 59 | return res.json(properties, 500) 60 | } 61 | let lowestInscriptionHash = properties.isOrdinal? fetchOrdinalHashes(properties): false 62 | let ordinalOwner = lowestInscriptionHash ? await fetchOwnerFromOrdinal_com(lowestInscriptionHash): properties.inscriptionLink? await fetchOwnerFromOrdinal_com(properties.inscriptionLink.split('/').reverse()[0]): null 63 | 64 | let utxoSet = await fetchUtxoFromMemPool_space(vaultBtcAddress) 65 | if ((properties.isOrdinal && ordinalOwner != vaultBtcAddress && utxoSet.mempool_stats.tx_count < 1)) { 66 | properties.risk = "high" 67 | properties.reasons.push("- empty vault") 68 | properties.reasons.push("- no pending tx") 69 | } else { 70 | properties.reasons.push('+ ordinal owner matches vault') 71 | } 72 | return res.json({assetName, description: metadata.description, metadata, liveMetadata, properties, balances, utxoSet, vaultBtcAddress, ordinalOwner}) 73 | }) 74 | 75 | app.get('/v1/fraud', async(req, res)=>{ 76 | let tokenId = req.headers.tokenid 77 | let password = req.headers.password 78 | let response = await setFraud(tokenId, password) 79 | response = JSON.parse(response) 80 | let lr_response = await refreshLooksRare(tokenId) 81 | response.lr = lr_response 82 | let os_response = await updateOpenseaMetdata(tokenId) 83 | response.os = os_response 84 | return res.json(response) 85 | }) 86 | 87 | app.post('/v1/classify', async (req, res) => { 88 | let title = req.body.title || '' 89 | let description = req.body.desc || '' 90 | let properties = await classifyVaultWithGPT(title, description) 91 | res.json(200,{'status':'success', properties }); 92 | }) 93 | 94 | app.listen(3000, () => { 95 | console.log('Server listening on port 3000'); 96 | }); 97 | 98 | function fetchOrdinalHashes(properties) { 99 | if (properties && properties.projectName && ordinals[properties.projectName]) { 100 | let collection = ordinals[properties.projectName] 101 | let lowestInscriptionId = collection[properties.assetId] && collection[properties.assetId].lowest? collection[properties.assetId].lowest: false 102 | let lowestInscriptionHash = lowestInscriptionId ? collection[properties.assetId].hashes[collection[properties.assetId].lowest] : null 103 | return lowestInscriptionHash 104 | } 105 | } 106 | 107 | async function classifyVaultWithGPT(title, description, balances) { 108 | try { 109 | let payload = groupTrainingAndPrompt({ title, description, balances }) 110 | 111 | const response = await openai.createCompletion({ 112 | model: "text-davinci-003", 113 | prompt: payload, 114 | temperature: 0.5, 115 | max_tokens: 800, 116 | top_p: 1, 117 | frequency_penalty: 0, 118 | presence_penalty: 0, 119 | }) 120 | let body = cleanAndParseJsonFromGPT(response.data.choices) 121 | delete body.title 122 | delete body.description 123 | return body 124 | } catch(err){ 125 | console.log("ERR", err) 126 | body = {success: false} 127 | return body 128 | } 129 | 130 | 131 | } 132 | 133 | function cleanAndParseJsonFromGPT(str){ 134 | if (str.length <1) { 135 | return {} 136 | } else {str = str[0]} 137 | 138 | str = str.text.replaceAll('\n ','').replaceAll('\n','').replace('properties = ','').replace('properties:','') 139 | try { 140 | str = JSON.parse(str) 141 | } catch(err){ 142 | str= {success:false, "message": err.toString()} 143 | } 144 | return str 145 | } 146 | 147 | function groupTrainingAndPrompt(prompt){ 148 | return JSON.stringify(training) + "\n" + JSON.stringify(prompt) 149 | } 150 | 151 | const fetchBalance = async (tokenId) => { 152 | const response = await rp(`https://api2.emblemvault.io/vault/balance/${tokenId}?live=false&_vercel_no_cache=1`); 153 | return JSON.parse(response) 154 | }; 155 | 156 | const fetchMetadataFromEmblem = async (tokenId) => { 157 | const response = await rp(`https://api2.emblemvault.io/meta/${tokenId}?experimental=true&raw=true`); 158 | return JSON.parse(response) 159 | } 160 | 161 | 162 | 163 | const fetchOwnerFromOrdinal_com = async (inscriptionId) => { 164 | try { 165 | const response = await rp(`https://ordinals.com/inscription/${inscriptionId}`); 166 | return getAddress(response); 167 | } catch(err){ 168 | return 'unknown' 169 | } 170 | } 171 | 172 | 173 | 174 | const getAddress = (body) => { 175 | const $ = cheerio.load(body); 176 | const address = $('dd.monospace') 177 | .filter((i, el) => $(el).prev().text() === 'address') 178 | .text(); 179 | return address; 180 | }; 181 | 182 | const fetchUtxoFromMemPool_space = async (address) => { 183 | if (!address) return false 184 | const response = await rp(`https://mempool.space/api/address/${address}`); 185 | return JSON.parse(response); 186 | }; 187 | 188 | const getMetadataFromAlchemy = async (tokenId, contractAddress) => { 189 | const response = await alchemy.nft.getNftMetadata(contractAddress, tokenId) 190 | return response; 191 | } 192 | 193 | async function setFraud(tokenId, password){ 194 | try { 195 | var options = { 196 | 'method': 'GET', 197 | 'url': `https://api2.emblemvault.io/fraudFlag/${tokenId}?_vercel_no_cache=1`, 198 | 'headers': { 199 | 'password': password 200 | } 201 | }; 202 | let response = await rp(options); 203 | return response; 204 | } catch (error) { 205 | throw error; 206 | } 207 | } 208 | 209 | async function updateOpenseaMetdata(tokenId) { 210 | const options = { 211 | method: 'GET', 212 | uri: `https://api.opensea.io/asset/0x82c7a8f707110f5fbb16184a5933e9f78a34c6ab/${tokenId}/?force_update=true` 213 | }; 214 | 215 | try { 216 | const body = await rp(options); 217 | const parsedBody = JSON.parse(body); 218 | return parsedBody 219 | } catch (error) { 220 | console.log('Error updating Opensea metadata: ', error) 221 | return false 222 | } 223 | } 224 | 225 | async function refreshLooksRare(tokenId) { 226 | try { 227 | const options = { 228 | method: 'POST', 229 | uri: 'https://api.looksrare.org/api/v1/tokens/refresh', 230 | headers: { 231 | 'X-Looks-Api-Key': process.env.LOOKSRARE_API_KEY, 232 | 'accept': 'application/json', 233 | 'content-type': 'application/json' 234 | }, 235 | body: { 236 | "collection": "0x82c7a8f707110f5fbb16184a5933e9f78a34c6ab", 237 | "tokenId": tokenId 238 | }, 239 | json: true 240 | }; 241 | 242 | let response = await rp(options) 243 | return response; 244 | } catch (error) { 245 | console.log('Error updating LooksRare metadata: ', error) 246 | return false 247 | } 248 | } 249 | 250 | module.exports = app 251 | const VAULTADDRESSES = [ 252 | { network: "mainnet", address: "0x82c7a8f707110f5fbb16184a5933e9f78a34c6ab" }, 253 | { network: "rinkeby", address: "0xe70AbBc99D8eB32124BF022196c493DB4fBc50FD" }, 254 | { network: "matic", address: "0x8b8407c6184f1f0Fd1082e83d6A3b8349cAcEd12" }, 255 | { network: "mumbai", address: "0x8b8407c6184f1f0Fd1082e83d6A3b8349cAcEd12" }, 256 | { network: "xdai", address: "0x9058d1A5Fdba852403D5b080abAF31D1379EF653" }, 257 | { network: "bsc", address: "0x9523022eb4B465Db3e3037d83e4910E3cFF1bD49" }, 258 | { network: "fantom", address: "0x5434ba8b4A37755Cb3867C9fde39342C0D382857" }, 259 | { network: "aurora", address: "0x14509fCc07892E80eD6BE4cf171407d206A92164" } 260 | ] 261 | 262 | let training = { 263 | "instructions": ["only respond with a valid properties json object", 264 | "seriously NEVER reply with invalid or incomplete json and ONLY json" 265 | ], 266 | "training": [ 267 | { 268 | "title": "Empty Vault Please Research before purchasing⚠️ Bitcoin Punk #6042 (Ordinal inscription #22940) - Contents Loading", 269 | "description": "https://ordinals.com/inscription/ed7250807a75e755f6a57d9bf804776f97cdaa7aabb3", 270 | "balances": [], 271 | "properties": { 272 | "assetName": "Bitcoin Punk #6042", 273 | "assetId": 6042, 274 | "projectName": "Bitcoin Punks", 275 | "seriesNumber": "unknown", 276 | "isOrdinal": true, 277 | "inscription": 6042, 278 | "inscriptionLink": "https://ordinals.com/inscription/ed7250807a75e755f6a57d9bf804776f97cdaa7aabb3", 279 | "risk": "high", 280 | "reasons": ["warning in title", "empty balance"] 281 | } 282 | }, 283 | { 284 | "title": "Bitcoin Punk #3210 (Ordinal Inscription #32538)", 285 | "balances": [{"coin":"BTC","name":"Bitcoin","balance":0.0000796,"price":1.7712926319999998,"project":null,"projectLogo":null,"projectSite":null}], 286 | "properties": { 287 | "assetName": "Bitcoin Punk #6042", 288 | "assetId": 6042, 289 | "projectName": "Bitcoin Punks", 290 | "seriesNumber": "unknown", 291 | "isOrdinal": true, 292 | "inscription": 6042, 293 | "risk": "low", 294 | "reasons":[] 295 | } 296 | }, 297 | { 298 | "title": "RAREPEPE", 299 | "balances": [{"coin":"ETH","name":"RAREPEPE","balance":1, type: "nft"}], 300 | "properties": { 301 | "projectName": "Rare Pepe", 302 | "risk": "high ⚠️", 303 | "reasons": ["enclosed asset is on incorrect chain"] 304 | } 305 | }, 306 | { 307 | "title": "RPEPELIGHTER", 308 | "description": "Collection:RarePepes - Series#4 Card#38", 309 | "properties": { 310 | "assetName": "RPEPELIGHTER", 311 | "assetId": 38, 312 | "projectName": "Rare Pepe", 313 | "seriesNumber": 4, 314 | "isOrdinal": false, 315 | "inscription": "n/a", 316 | "risk": "low", 317 | "reasons":[] 318 | } 319 | }, 320 | { 321 | "title": "PEPEONECOIN | Rare Pepe | Series 1 - Card 24", 322 | "description": "PEPEONECOIN | Rare Pepe | Series 1 - Card 24 http://rarepepedirectory.com/?p=137 https://xchain.io/asset/PEPEONECOIN", 323 | "properties": { 324 | "assetName": "PEPEONECOIN", 325 | "assetId": 24, 326 | "projectName": "Rare Pepe", 327 | "seriesNumber": 1, 328 | "isOrdinal": false, 329 | "inscription": "n/a", 330 | "risk": "low", 331 | "reasons":[] 332 | } 333 | }, 334 | { 335 | "title": "HARVESTER | Age Of Chains 2017", 336 | "description": "HARVESTER, Card 9 created in 2017, from the project Age Of Chains. Total supply is 6,666.", 337 | "properties": { 338 | "assetName": "HARVESTER", 339 | "assetId": 9, 340 | "projectName": "Age Of Chains", 341 | "seriesNumber": "unknown", 342 | "isOrdinal": false, 343 | "inscription": "n/a", 344 | "risk": "low", 345 | "reasons":[] 346 | } 347 | }, 348 | { 349 | "title": "id/helenyoung | 2014-07 | Namecoin Identity (id/ asset) |", 350 | "description": "Asset: id/helenyoung\n\nMint: Jul 21st, 2014\n\nThis vault contains a Namecoin Identity id/ asset. Launched in May 2012,", 351 | "properties": { 352 | "assetName": "id/helenyoung", 353 | "assetId": "n/a", 354 | "projectName": "Namecoin Identity", 355 | "seriesNumber": "n/a", 356 | "isOrdinal": false, 357 | "inscription": "n/a", 358 | "risk": "low", 359 | "reasons":[] 360 | } 361 | }, 362 | { 363 | "description":"Below and at Emblem.Finance 👇", 364 | "balances": [], 365 | "properties":{ 366 | "risk": "med", 367 | "reasons": ["copy paste description", "empty balance"] 368 | } 369 | } 370 | ] 371 | } 372 | 373 | 374 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fraud GPT 5 | 8 | 11 | 14 | 17 | 58 | 59 | 60 | 61 |
62 | 63 |
64 |

65 | 66 |
67 | Enter Marketplace URL 68 |
69 |

70 |
71 |
72 |
73 |
74 | 75 | 76 | 77 | 78 |
79 |
80 |
Get Fraud Score
81 |
82 | 83 |
84 | 85 |
86 |
87 | New to Emblem Vault? Visit us today | View Recents | Vault Risk Score (bookmarklet) 88 |
89 |
90 |
91 | 127 | 133 | 305 | 306 | 307 | --------------------------------------------------------------------------------