forked from dsgriffin/nft-sales-x-bot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.js
136 lines (110 loc) · 4.94 KB
/
app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// external
const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
const axios = require('axios');
const { ethers } = require('ethers');
const _ = require('lodash');
// local
const { markets } = require('./markets.js');
const { currencies } = require('./currencies.js');
const { transferEventTypes, saleEventTypes } = require('./log_event_types.js');
const { tweet } = require('./tweet');
const abi = require('./abi.json');
// connect to Alchemy websocket
const web3 = createAlchemyWeb3(`wss://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`);
// sometimes web3.js can return duplicate transactions in a split second, so
let lastTransactionHash;
async function monitorContract() {
const contract = new web3.eth.Contract(abi, process.env.CONTRACT_ADDRESS);
contract.events.Transfer({})
.on('connected', (subscriptionId) => {
console.log(subscriptionId);
})
.on('data', async (data) => {
const transactionHash = data.transactionHash.toLowerCase();
// duplicate transaction - skip process
if (transactionHash == lastTransactionHash) {
return;
}
lastTransactionHash = transactionHash;
const receipt = await web3.eth.getTransactionReceipt(transactionHash);
const recipient = receipt.to.toLowerCase();
// not a marketplace transaction transfer, skip
if (!(recipient in markets)) {
return;
}
// retrieve market details
const market = _.get(markets, recipient);
// default to eth, see currencies.js for currently support currencies
let currency = {
'name': 'ETH',
'decimals': 18,
'threshold': 1
};
let tokens = [];
let totalPrice;
for (let log of receipt.logs) {
const logAddress = log.address.toLowerCase();
// if non-ETH transaction
if (logAddress in currencies) {
currency = currencies[logAddress];
}
// token(s) part of the transaction
if (log.data == "0x" && transferEventTypes.includes(log.topics[0])) {
const tokenId = web3.utils.hexToNumberString(log.topics[3]);
tokens.push(tokenId);
}
// transaction log - decode log in correct format depending on market & retrieve price
if (logAddress == recipient && saleEventTypes.includes(log.topics[0])) {
const decodedLogData = web3.eth.abi.decodeLog(market.logDecoder, log.data, []);
totalPrice = ethers.utils.formatUnits(decodedLogData.price, currency.decimals);
}
}
// remove any dupes
tokens = _.uniq(tokens);
// custom - don't post sales below a currencies manually set threshold
// if (Number(totalPrice) < currency.threshold) {
// console.log(`Sale under ${currency.threshold}: Token ID: ${tokens[0]}, Price: ${totalPrice}`);
// return;
// }
// retrieve metadata for the first (or only) ERC21 asset sold
const tokenData = await getTokenData(tokens[0]);
// if more than one asset sold, link directly to etherscan tx, otherwise the marketplace item
if (tokens.length > 1) {
tweet(`${_.get(tokenData, 'assetName', `#` + tokens[0])} & other assets bought for ${totalPrice} ${currency.name} on ${market.name} https://etherscan.io/tx/${transactionHash}`);
} else {
tweet(`${_.get(tokenData, 'assetName', `#` + tokens[0])} bought for ${totalPrice} ${currency.name} on ${market.name} ${market.site}${process.env.CONTRACT_ADDRESS}/${tokens[0]}`);
}
})
.on('changed', (event) => {
console.log('change');
})
.on('error', (error, receipt) => {
// if the transaction was rejected by the network with a receipt, the second parameter will be the receipt.
console.error(error);
console.error(receipt);
});
}
async function getTokenData(tokenId) {
try {
// retrieve metadata for asset from opensea
const response = await axios.get(`https://api.opensea.io/api/v1/asset/${process.env.CONTRACT_ADDRESS}/${tokenId}`, {
headers: {
'X-API-KEY': process.env.X_API_KEY
}
});
const data = response.data;
// just the asset name for now, but retrieve whatever you need
return {
'assetName': _.get(data, 'name')
};
} catch (error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
} else {
console.error(error.message);
}
}
}
// initate websocket connection
monitorContract();