copied from other repo
This commit is contained in:
commit
dffa62de07
|
@ -0,0 +1,9 @@
|
|||
*settings.json
|
||||
!example-settings.json
|
||||
uploads
|
||||
node_modules
|
||||
*.swp
|
||||
.idea
|
||||
apidoc/
|
||||
.env
|
||||
.env.*
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"sentry": {
|
||||
"api": "SENTRY_URL_1",
|
||||
"scraper": "SENTRY_URL_2"
|
||||
},
|
||||
"baseUrl": "https://PUBLIC_DOMAIN",
|
||||
"factory": {
|
||||
"address": "FACTORY_ADDRESS",
|
||||
"block": 9046391
|
||||
},
|
||||
"token": {
|
||||
"address": "TOKEN_ADDRESS",
|
||||
"decimals": 18,
|
||||
"block": 7150030
|
||||
},
|
||||
"web3": {
|
||||
"url": "wss://INFURA_URL",
|
||||
"options": {
|
||||
"reconnect": {
|
||||
"auto": true,
|
||||
"delay": 5000,
|
||||
"maxAttempts": 5,
|
||||
"onTimeout": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"db": {
|
||||
"user": "DB_USER",
|
||||
"host": "127.0.0.1",
|
||||
"database": "DB_NAME",
|
||||
"password": "DB_PASS",
|
||||
"port": 5432
|
||||
},
|
||||
"http": {
|
||||
"port": 3031
|
||||
},
|
||||
"ipfs": {
|
||||
"nodes": [
|
||||
"http://localhost:5001/api/v0"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,494 @@
|
|||
[
|
||||
{
|
||||
"inputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "BridgeTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "ManagerTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "store",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint24",
|
||||
"name": "id",
|
||||
"type": "uint24"
|
||||
}
|
||||
],
|
||||
"name": "NewStore",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OracleTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "operator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "PayoutTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "bridge",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "diamondCut",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "facetAddress",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "enum IDiamondCut.FacetCutAction",
|
||||
"name": "action",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "initParams",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address payable",
|
||||
"name": "creator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "contract IMotherShip",
|
||||
"name": "mothership",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isERC1363Token",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "fee",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "royalty",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "storeName",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "storeSymbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "baseURI",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "proxyRegistryAddress",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint24",
|
||||
"name": "storeId",
|
||||
"type": "uint24"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "sendRightAway",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "manager",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "oracle",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "payout",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address payable",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function",
|
||||
"constant": true
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "facetAddress",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "enum IDiamondCut.FacetCutAction",
|
||||
"name": "action",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes4[]",
|
||||
"name": "functionSelectors",
|
||||
"type": "bytes4[]"
|
||||
}
|
||||
],
|
||||
"internalType": "struct IDiamondCut.FacetCut[]",
|
||||
"name": "_diamondCut",
|
||||
"type": "tuple[]"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address payable",
|
||||
"name": "creator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "contract IMotherShip",
|
||||
"name": "mothership",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isERC1363Token",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "fee",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "royalty",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "storeName",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "storeSymbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "baseURI",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "proxyRegistryAddress",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint24",
|
||||
"name": "storeId",
|
||||
"type": "uint24"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "sendRightAway",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"internalType": "struct DiamondERC1155.InitializationParameters",
|
||||
"name": "_initParams",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"name": "initialize",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "_name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "_symbol",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "newStore",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "_name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "_symbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "_metadataURI",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_customToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "_isERC1363",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"name": "newStore",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_manager",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "setManager",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_bridge",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "setBridge",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_oracle",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "setOracle",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address payable",
|
||||
"name": "_payout",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "setPayout",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
#set -eo pipefail
|
||||
|
||||
while true
|
||||
do
|
||||
node --max-old-space-size=8192 index.js ./test-settings.json || true
|
||||
done
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
while true
|
||||
do
|
||||
node scraper.js ./test-settings.json
|
||||
done
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
# install apidoc:
|
||||
# npm install -g apidoc
|
||||
|
||||
# use fswatch to invoke this command whever index.js updates:
|
||||
# fswatch index.js | xargs -n1 -I{} ./generate-api-doc.sh
|
||||
|
||||
echo "Generating documentation..."
|
||||
apidoc -i ./ -o apidoc/ -e node_modules -f index.js && echo success
|
|
@ -0,0 +1,93 @@
|
|||
select y.nft_id,
|
||||
y.store,
|
||||
y.owner,
|
||||
y.meta_id,
|
||||
y.eth_price,
|
||||
y.token_price,
|
||||
y.metadata,
|
||||
y.metadata_uri,
|
||||
y.eth_for_sale,
|
||||
y.token_for_sale,
|
||||
x.dollar_price,
|
||||
y.inserted,
|
||||
z.cnt
|
||||
from nft as y,
|
||||
(
|
||||
select coalesce(a1.store, b1.store) as store,
|
||||
coalesce(a1.meta_id, b1.meta_id) as meta_id,
|
||||
coalesce(a1.nft_id, b1.nft_id) as nft_id,
|
||||
a1.dollar_price
|
||||
from (
|
||||
select coalesce(eth_nfts.store, token_nfts.store) as store,
|
||||
coalesce(eth_nfts.meta_id, token_nfts.meta_id) as meta_id,
|
||||
coalesce(eth_nfts.nft_id, token_nfts.nft_id) as nft_id,
|
||||
eth_nfts.min_eth_price,
|
||||
token_nfts.min_token_price,
|
||||
coalesce(eth_nfts.dollar_price, token_nfts.dollar_price) as dollar_price
|
||||
from (select distinct on (n1.store, n1.meta_id) n1.store,
|
||||
n1.meta_id,
|
||||
n1.nft_id,
|
||||
min(n1.eth_price) as min_eth_price,
|
||||
min(n1.eth_price) / (10 ^ 18) *
|
||||
(select val from lookup where key = 'ubiqUsdRatio')::decimal as dollar_price
|
||||
from nft n1
|
||||
where n1.hidden = false
|
||||
and n1.owner <> '0x0000000000000000000000000000000000000000'
|
||||
and n1.metadata is not null
|
||||
and n1.eth_for_sale = true
|
||||
group by n1.store, n1.meta_id, n1.eth_price, n1.nft_id
|
||||
) as eth_nfts
|
||||
full join
|
||||
(select distinct on (n1.store, n1.meta_id) n1.store,
|
||||
n1.meta_id,
|
||||
n1.nft_id,
|
||||
min(n1.token_price) as min_token_price,
|
||||
min(n1.token_price) / (10 ^ 18) *
|
||||
(select val from lookup where key = 'ubiqUsdRatio')::decimal /
|
||||
(select val from lookup where key = 'ubiqGransRatio')::decimal as dollar_price
|
||||
from nft n1
|
||||
where n1.hidden = false
|
||||
and n1.owner <> '0x0000000000000000000000000000000000000000'
|
||||
and n1.metadata is not null
|
||||
and n1.token_for_sale = true
|
||||
group by n1.store, n1.meta_id, n1.token_price, n1.nft_id
|
||||
) as token_nfts
|
||||
on eth_nfts.store = token_nfts.store and eth_nfts.meta_id = token_nfts.meta_id
|
||||
) as a1
|
||||
full join
|
||||
(
|
||||
select any_nfts.store, any_nfts.meta_id, min(any_nfts.nft_id) as nft_id
|
||||
from nft as any_nfts
|
||||
inner join (
|
||||
select nosale_nfts.store,
|
||||
nosale_nfts.meta_id,
|
||||
array_agg(nosale_nfts.token_for_sale),
|
||||
sum(case when nosale_nfts.token_for_sale = true then 1 else 0 end) as num_tokens_for_sale,
|
||||
sum(case when nosale_nfts.eth_for_sale = true then 1 else 0 end) as eth_tokens_for_sale
|
||||
from nft as nosale_nfts
|
||||
where nosale_nfts.metadata is not null
|
||||
group by nosale_nfts.store, nosale_nfts.meta_id
|
||||
having sum(case when nosale_nfts.token_for_sale = true then 1 else 0 end) = 0
|
||||
and sum(case when nosale_nfts.eth_for_sale = true then 1 else 0 end) = 0
|
||||
order by nosale_nfts.store, nosale_nfts.meta_id
|
||||
) as ag
|
||||
on ag.store = any_nfts.store and ag.meta_id = any_nfts.meta_id
|
||||
where any_nfts.metadata is not null
|
||||
group by any_nfts.store, any_nfts.meta_id
|
||||
) as b1
|
||||
on a1.nft_id = b1.nft_id
|
||||
) as x,
|
||||
(
|
||||
select i.meta_id, i.store, a.verified, count(*) as cnt
|
||||
from nft as i, store as s, account as a
|
||||
where i.metadata is not null
|
||||
and i.hidden = false
|
||||
and i.owner <> '0x0000000000000000000000000000000000000000'
|
||||
and s.address = i.store
|
||||
and a.id = s.creator
|
||||
OWNER_CLAUSE
|
||||
group by i.meta_id, i.store, a.verified
|
||||
) as z
|
||||
where x.nft_id = y.nft_id
|
||||
and x.store = z.store
|
||||
and x.meta_id = z.meta_id
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Action": [
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": "arn:aws:s3:::dev-erc1155"
|
||||
},
|
||||
{
|
||||
"Action": [
|
||||
"s3:DeleteObject",
|
||||
"s3:GetObject",
|
||||
"s3:PutObject"
|
||||
],
|
||||
"Effect": "Allow",
|
||||
"Resource": "arn:aws:s3:::dev-erc1155/*"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
select inserted, name, return_values from
|
||||
(select inserted, cast('Comment' as text) as name, jsonb_build_object('store', comment.store, 'meta_id', comment.meta_id, 'author', author, 'content', content) as return_values from (select store, meta_id, inserted, author, content from comment) comment join (select store, meta_id, owner from nft where owner = $1) nft on comment.store = nft.store and comment.meta_id = nft.meta_id and comment.author != $1
|
||||
union
|
||||
select inserted, name, return_values from event where name in ('BuySingleNft', 'TokenBuySingleNft') and return_values->>'from' = $1) notifications
|
||||
union
|
||||
select inserted, cast('Resale' as text) as name, return_values from event where name in ('BuySingleNft') and address in (select address from store where creator = $1) and return_values->>'from' != $1
|
||||
union
|
||||
select inserted, cast('TokenResale' as text) as name, return_values from event where name in ('TokenBuySingleNft') and address in (select address from store where creator = $1) and return_values->>'from' != $1
|
||||
order by inserted desc limit $2 offset $3;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "token-gallery-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sentry/browser": "^6.2.1",
|
||||
"@sentry/node": "^5.24.2",
|
||||
"@sentry/tracing": "^6.2.1",
|
||||
"bignumber.js": "^9.0.1",
|
||||
"ethereumjs-util": "5.1.5",
|
||||
"express": "^4.17.1",
|
||||
"feed": "^4.2.1",
|
||||
"gm": "^1.23.1",
|
||||
"ipfs-http-client": "^51.0.0",
|
||||
"memory-streams": "^0.1.3",
|
||||
"multer": "^1.4.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"pg": "^8.2.1",
|
||||
"timed-cache": "^1.1.5",
|
||||
"uuid-random": "^1.3.2",
|
||||
"web3": "^1.3.4",
|
||||
"web3-eth": "^1.3.4",
|
||||
"web3-eth-contract": "^1.3.4",
|
||||
"web3-utils": "^1.3.4"
|
||||
},
|
||||
"apidoc": {
|
||||
"title": "token.gallery backend API",
|
||||
"url" : "https://token.gallery",
|
||||
"sampleUrl": "https://token.gallery"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,494 @@
|
|||
const utils = require('ethereumjs-util');
|
||||
const fs = require('fs');
|
||||
const { Client } = require('pg');
|
||||
const Web3 = require('web3');
|
||||
const fetch = require("node-fetch");
|
||||
const BigNumber = require("bignumber.js");
|
||||
const BN = require('bn.js');
|
||||
const { create } = require('ipfs-http-client');
|
||||
const all = require('it-all')
|
||||
const uint8ArrayConcat = require('uint8arrays/concat')
|
||||
const uint8ArrayToString = require('uint8arrays/to-string')
|
||||
|
||||
const Sentry = require("@sentry/node");
|
||||
const Tracing = require("@sentry/tracing");
|
||||
|
||||
const settings = require(process.argv[2]);
|
||||
|
||||
Sentry.init({
|
||||
dsn: settings.sentry.scraper,
|
||||
tracesSampleRate: 1.0
|
||||
});
|
||||
|
||||
const ipfsNodes = settings.ipfs.nodes.map(n => create(n) );
|
||||
|
||||
async function fetchFromIpfs(hash) {
|
||||
if (hash.startsWith("ipfs://")) {
|
||||
hash = hash.substring(7);
|
||||
}
|
||||
|
||||
const promises = ipfsNodes.map(n => all(n.cat(hash)));
|
||||
const result = await Promise.any(promises);
|
||||
const rawData = uint8ArrayConcat(result);
|
||||
|
||||
return uint8ArrayToString(rawData);
|
||||
}
|
||||
|
||||
const provider = new Web3.providers.WebsocketProvider(settings.web3.url, settings.web3.options)
|
||||
const web3 = new Web3(provider, null, { transactionConfirmationBlocks: 1 });
|
||||
|
||||
provider.on("error", e => {
|
||||
Sentry.captureException(e);
|
||||
console.error("Websocket error, exiting", e);
|
||||
process.exit(1)
|
||||
});
|
||||
provider.on("connect", () => {
|
||||
console.log("Websocket connected");
|
||||
});
|
||||
// "end" event is handled further down
|
||||
|
||||
async function fetchJson(url) { return await (await fetch(url)).json() }
|
||||
|
||||
// This is just here to keep the program from closing when it hits the end.
|
||||
setInterval(() => {}, 1000 * 60 );
|
||||
|
||||
(async () => {
|
||||
const client = new Client(settings.db);
|
||||
await client.connect();
|
||||
|
||||
const factoryAbi = JSON.parse(await fs.promises.readFile("./factory.abi.json"));
|
||||
const storeAbi = JSON.parse(await fs.promises.readFile("./store.abi.json"));
|
||||
|
||||
async function lookup(key) {
|
||||
const result = await client.query("select val from lookup where key = $1 limit 1", [ key ]);
|
||||
return result.rows.length == 0 ? null : result.rows[0].val;
|
||||
}
|
||||
|
||||
async function setLatestBlock(blockNumber) {
|
||||
const result = await client.query("update lookup set val = $1 where key = 'lastFactoryBlock' and cast(val as integer) < cast($1 as integer)", [ blockNumber ]);
|
||||
if (result.rowCount == 0) console.log(`Ignoring last block update ${blockNumber} as was older than current`);
|
||||
}
|
||||
|
||||
async function getLatestBlock() {
|
||||
const result = await client.query("select cast(val as integer) as val from lookup where key = 'lastFactoryBlock' limit 1");
|
||||
return result.rows[0].val;
|
||||
}
|
||||
|
||||
async function insertEvent(event) {
|
||||
const result = await client.query(
|
||||
"insert into event( inserted, name, address, return_values, log_index, transaction_index, block) values ( now(), $1, $2, $3, $4, $5, $6 ) on conflict ( address, log_index, transaction_index, block ) do nothing returning *",
|
||||
[ event.event, event.address, event.returnValues, event.logIndex, event.transactionIndex, event.blockNumber ]
|
||||
);
|
||||
if (result.rowCount == 0) {
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
return result.rows[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
async function getNft(storeAddress, nftId) {
|
||||
const result = await client.query("select * from nft where nft_id = $1 and store = $2 limit 1", [ nftId, storeAddress ]);
|
||||
if (result.rowCount == 0) { return null; }
|
||||
else {
|
||||
const r = result.rows[0];
|
||||
return {
|
||||
id: r.id,
|
||||
nftId: r.nft_id,
|
||||
storeAddress: r.store,
|
||||
ownerAddress: r.owner,
|
||||
inserted: r.inserted,
|
||||
base: r.base,
|
||||
metadata: r.metadata,
|
||||
metadataUri: r.metadata_uri,
|
||||
hidden: r.hidden,
|
||||
ethPrice: new BigNumber(r.eth_price),
|
||||
tokenPrice: new BigNumber(r.token_price)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerStoreEventHandlers(contract) {
|
||||
contract.events.TransferSingle({ fromBlock: lastSeenBlock }).on("data", handleNftTransferEvent);
|
||||
contract.events.TransferBatch({ fromBlock: lastSeenBlock }).on("data", handleNftBatchTransferEvent);
|
||||
contract.events.BuySingleNft({ fromBlock: lastSeenBlock }).on("data", handleBuySingleNft);
|
||||
contract.events.TokenBuySingleNft({ fromBlock: lastSeenBlock }).on("data", handleTokenBuySingleNft);
|
||||
contract.events.CreatorTransferred({ fromBlock: lastSeenBlock }).on("data", handleStoreTransferEvent);
|
||||
contract.events.PriceChange({ fromBlock: lastSeenBlock }).on("data", handlePriceChangeEvent);
|
||||
}
|
||||
|
||||
async function handleNewStoreEvent(event) {
|
||||
const address = event.returnValues.store;
|
||||
console.log(`New store: ${address}`);
|
||||
try {
|
||||
var contract = new web3.eth.Contract(storeAbi, address);
|
||||
var name = await contract.methods.name().call();
|
||||
var symbol = await contract.methods.symbol().call();
|
||||
var creator = await contract.methods.creator().call();
|
||||
}
|
||||
catch (error) {
|
||||
Sentry.captureException(e);
|
||||
// This happens sometimes I think because nodes aren't in sync when we continue on a single confirmation.
|
||||
console.error("There was an error handling new store, bailing and restarting to resume from where we left off.", error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!stores[address]) {
|
||||
stores[address] = {
|
||||
address,
|
||||
name,
|
||||
symbol,
|
||||
creator,
|
||||
contract
|
||||
}
|
||||
}
|
||||
|
||||
const eventId = await insertEvent(event);
|
||||
|
||||
if (eventId == -1) {
|
||||
console.log("Store already in database, not inserting.");
|
||||
}
|
||||
else {
|
||||
const result = await client.query(
|
||||
"insert into store(address, name, symbol, creator, inserted) values($1, $2, $3, $4, now()) on conflict(address) do nothing",
|
||||
[address, name, symbol, creator]
|
||||
);
|
||||
if (result.rowCount == 0) console.error("Store not inserted for some reason.");
|
||||
}
|
||||
|
||||
if (event.blockNumber > lastSeenBlock) {
|
||||
lastSeenBlock = event.blockNumber;
|
||||
await setLatestBlock(event.blockNumber);
|
||||
}
|
||||
|
||||
registerStoreEventHandlers(contract);
|
||||
}
|
||||
|
||||
|
||||
async function handleStoreTransferEvent(event) {
|
||||
console.log("Store transfer event");
|
||||
const from = event.returnValues.from;
|
||||
const to = event.returnValues.to;
|
||||
|
||||
const storeAddress = event.address;
|
||||
|
||||
const eventId = await insertEvent(event);
|
||||
|
||||
if (eventId == -1) {
|
||||
console.log("Skipping transfer store event.");
|
||||
}
|
||||
else {
|
||||
const result = await client.query("update store set creator = $1 where address = $2", [ to, storeAddress ]);
|
||||
}
|
||||
|
||||
if (event.blockNumber > lastSeenBlock) {
|
||||
lastSeenBlock = event.blockNumber;
|
||||
await setLatestBlock(event.blockNumber);
|
||||
}
|
||||
}
|
||||
|
||||
// sometimes these come in before the mint so they have to be deferred.
|
||||
const handlePriceChangeEvent = async function (event, deferred = false) {
|
||||
console.log("Nft price change event");
|
||||
|
||||
async function updateNft(price, forSale, tokenPrice, tokenForSale, storeAddress, nftId) {
|
||||
const params = [ price, forSale, tokenPrice, tokenForSale, storeAddress, nftId ];
|
||||
const result = await client.query("update nft set eth_price = $1, eth_for_sale = $2, token_price = $3, token_for_sale = $4 where store = $5 and nft_id = $6", params);
|
||||
if (result.rowCount != 1) {
|
||||
console.error(`Updated ${result.rowCount} rows instead of 1 for some reason, deferring and trying again.`);
|
||||
setTimeout(async function(){ await handlePriceChangeEvent(event, true); }, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
const storeAddress = event.address;
|
||||
const nftId = event.returnValues.id;
|
||||
const price = event.returnValues.price;
|
||||
const forSale = event.returnValues.forSale;
|
||||
const tokenPrice = event.returnValues.tokenPrice;
|
||||
const tokenForSale = event.returnValues.tokenForSale;
|
||||
|
||||
if (deferred) {
|
||||
// We KNOW there's already an event so skip trying to insert it
|
||||
await updateNft(price, forSale, tokenPrice, tokenForSale, storeAddress, nftId);
|
||||
}
|
||||
else {
|
||||
const eventId = await insertEvent(event);
|
||||
if (eventId == -1) {
|
||||
console.log("Skipping nft price change event.");
|
||||
}
|
||||
else {
|
||||
await updateNft(price, forSale, tokenPrice, tokenForSale, storeAddress, nftId);
|
||||
}
|
||||
}
|
||||
|
||||
if (event.blockNumber > lastSeenBlock) {
|
||||
lastSeenBlock = event.blockNumber;
|
||||
await setLatestBlock(event.blockNumber);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleBuySingleNft(event, deferred = false, count = 5) {
|
||||
console.log("NFT buy event (using Ubiq)");
|
||||
|
||||
if (!deferred) {
|
||||
const eventId = await insertEvent(event);
|
||||
}
|
||||
|
||||
// Don't need to show the transfer event because the buy event has same info.
|
||||
const params = [
|
||||
event.blockNumber,
|
||||
event.transactionIndex,
|
||||
event.returnValues.from,
|
||||
event.returnValues.to,
|
||||
event.returnValues.id
|
||||
];
|
||||
console.log(params);
|
||||
const result = await client.query("update event set hidden=true where name = 'TransferSingle' and block = $1 and transaction_index = $2 and return_values->>'_from' = $3 and return_values->>'_to' = $4 and return_values->>'_id' = $5", params);
|
||||
if (result.rowCount == 0) {
|
||||
// Sometimes the events come out of order
|
||||
if (count > 0) {
|
||||
console.log("No transfer activity was found to hide, so waiting and trying again later.");
|
||||
setTimeout(() => { handleBuySingleNft(event, true, count-1); }, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function handleTokenBuySingleNft(event, deferred = false, count = 5) {
|
||||
console.log("NFT buy event (using token)");
|
||||
|
||||
if (!deferred) {
|
||||
const eventId = await insertEvent(event);
|
||||
}
|
||||
|
||||
// Don't need to show the transfer event because the buy event has same info.
|
||||
const params = [
|
||||
event.blockNumber,
|
||||
event.transactionIndex,
|
||||
event.returnValuesfrom,
|
||||
event.returnValues.to,
|
||||
event.returnValues.id
|
||||
];
|
||||
const result = await client.query("update event set hidden=true where name = 'TransferSingle' and block = $1 and transaction_index = $2 and return_values->>'_from' = $3 and return_values->>'_to' = $4 and return_values->>'_id' = $5", params);
|
||||
if (result.rowCount == 0) {
|
||||
// Sometimes the events come out of order
|
||||
if (count > 0) {
|
||||
console.log("No transfer activity was found to hide, so waiting and trying again later.");
|
||||
setTimeout(() => { handleTokenBuySingleNft(event, true, count-1); }, 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNftBatchTransferEvent(event) {
|
||||
console.log("NFT batch transfer event");
|
||||
|
||||
const eventId = await insertEvent(event); // Save it even if we're not using it.
|
||||
|
||||
const ids = event.returnValues.ids;
|
||||
|
||||
// remove aggregate fields
|
||||
delete event.returnValues['3'];
|
||||
delete event.returnValues['4'];
|
||||
delete event.returnValues.ids;
|
||||
delete event.returnValues.values;
|
||||
|
||||
event.event = 'TransferSingle';
|
||||
event.returnValues.value = '1';
|
||||
|
||||
// Can't have same one for every derived event, so faking them, but predictably and recognizably.
|
||||
let logIndex = event.logIndex + 10000;
|
||||
for (const id of ids) {
|
||||
event.returnValues.id = id;
|
||||
event.logIndex = logIndex;
|
||||
await handleNftTransferEvent(event);
|
||||
++logIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles token transfers and mints.
|
||||
*/
|
||||
async function handleNftTransferEvent(event) {
|
||||
console.log("NFT transfer event");
|
||||
|
||||
const eventId = await insertEvent(event);
|
||||
|
||||
const operator = event.returnValues.operator;
|
||||
const from = event.returnValues.from;
|
||||
const to = event.returnValues.to;
|
||||
const nftId = event.returnValues.id;
|
||||
|
||||
const isMintOperation = (from == "0x0000000000000000000000000000000000000000");
|
||||
const isBurnOperation = (!isMintOperation && to == "0x0000000000000000000000000000000000000000");
|
||||
|
||||
const storeAddress = event.address;
|
||||
const contract = stores[storeAddress].contract;
|
||||
|
||||
try {
|
||||
// erc1155 can hold both fungible and nonfungible
|
||||
var isNft = await contract.methods.isNonFungibleItem(nftId).call();
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error getting if contract is nonfungible. bailing and starting from where we left off.", error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (isNft) {
|
||||
if (isMintOperation) {
|
||||
console.log("Mint operation");
|
||||
|
||||
try {
|
||||
var metaId = await contract.methods.metaId(nftId).call();
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error getting meta id. bailing and starting from where we left off.", error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (eventId != -1) {
|
||||
const result = await client.query(
|
||||
"insert into nft(nft_id, store, owner, inserted, base, meta_id) values($1, $2, $3, now(), false, $4) on conflict(nft_id, store) do nothing",
|
||||
[ nftId, storeAddress, to, metaId ]
|
||||
);
|
||||
if (result.rowCount == 0) {
|
||||
console.log("NFT wasn't inserted for some reason.");
|
||||
}
|
||||
}
|
||||
|
||||
const hasMetadata = (await client.query("select count(*) from nft where nft_id = $1 and store = $2 and metadata is not null", [ nftId, storeAddress ])) > 0;
|
||||
|
||||
if (!hasMetadata) {
|
||||
// insert metadata if it exists.
|
||||
const contract = stores[storeAddress].contract;
|
||||
// this shouldn't ever fail because we call the contract earlier and it succeeded.
|
||||
const uri = await contract.methods.uri(nftId).call();
|
||||
|
||||
try {
|
||||
let metadata;
|
||||
if (uri.startsWith("ipfs://")) {
|
||||
metadata = await fetchFromIpfs(uri);
|
||||
} else {
|
||||
metadata = await fetchJson(uri);
|
||||
}
|
||||
|
||||
const result = await client.query("update nft set metadata = $1, metadata_uri = $2 where nft_id = $3 and store = $4 and metadata is null", [ metadata, uri, nftId, storeAddress ]);
|
||||
if (result.rowCount == 1) {
|
||||
console.log("Inserted NFT metadata");
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof AggregateError) {
|
||||
console.error("Aggregate error fetching metadata: ", e.errors);
|
||||
} else {
|
||||
console.error("Error fetching metadata", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isBurnOperation) {
|
||||
console.log("Burn operation");
|
||||
if (eventId != -1) {
|
||||
console.log("Burning in database");
|
||||
const result = await client.query(
|
||||
"update nft set owner = $1, eth_price=0, eth_for_sale=false, token_price=0, token_for_sale=false where nft_id = $2 and store = $3",
|
||||
[ to, nftId, storeAddress ]
|
||||
);
|
||||
if (result.rowCount == 0) console.log("Burn not updated in db for some reason.");
|
||||
}
|
||||
else {
|
||||
console.log("Already recorded in db.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Normal transfer operation");
|
||||
if (eventId != -1) {
|
||||
const result = await client.query(
|
||||
"update nft set owner = $1, eth_price=0, eth_for_sale=false, token_price=0, token_for_sale=false where nft_id = $2 and store = $3",
|
||||
[ to, nftId, storeAddress ]
|
||||
);
|
||||
if (result.rowCount == 0) console.log("Skipping NFT transfer, probably because it was already processed");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Not an NFT, ignoring")
|
||||
}
|
||||
|
||||
if (event.blockNumber > lastSeenBlock) {
|
||||
lastSeenBlock = event.blockNumber;
|
||||
await setLatestBlock(event.blockNumber);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNewBlock(blockHeader) {
|
||||
if (blockHeader.number == lastSeenBlock) {
|
||||
console.log(`Ignoring duplicate block ${lastSeenBlock}`);
|
||||
}
|
||||
else if (blockHeader.number > lastSeenBlock) {
|
||||
lastSeenBlock = blockHeader.number;
|
||||
await setLatestBlock(blockHeader.number);
|
||||
}
|
||||
else {
|
||||
console.log("Received block header out of order, assuming already processed and ignoring.");
|
||||
}
|
||||
}
|
||||
|
||||
const stores = {};
|
||||
let lastSeenBlock = await getLatestBlock();
|
||||
|
||||
const storeResult = await client.query("select address, name, symbol, creator from store order by inserted asc");
|
||||
console.log(`Number of stores queried: ${storeResult.rowCount}`);
|
||||
for (const row of storeResult.rows) {
|
||||
const contract = new web3.eth.Contract(storeAbi, row.address);
|
||||
|
||||
registerStoreEventHandlers(contract);
|
||||
|
||||
stores[row.address] = {
|
||||
address: row.address,
|
||||
name: row.name,
|
||||
symbol: row.symbol,
|
||||
creator: row.creator,
|
||||
contract
|
||||
}
|
||||
}
|
||||
|
||||
const factory = new web3.eth.Contract(factoryAbi, settings.factory.address);
|
||||
console.log(`Watching for new stores since block: ${lastSeenBlock}`);
|
||||
|
||||
factory.events.NewStore({ fromBlock: lastSeenBlock })
|
||||
.on("data", handleNewStoreEvent);
|
||||
|
||||
// handle provider disconnects
|
||||
provider.on("error", e => {
|
||||
Sentry.captureException(e);
|
||||
console.log("Connection ended, re-establishing connection")
|
||||
web3.eth.clearSubscriptions();
|
||||
web3.setProvider(provider);
|
||||
|
||||
web3.eth.subscribe('newBlockHeaders')
|
||||
.on('data', handleNewBlock)
|
||||
.on('error', e => {
|
||||
Sentry.captureException(e);
|
||||
});
|
||||
|
||||
console.log("Re-setting all event listeners");
|
||||
factory.events.NewStore({ fromBlock: lastSeenBlock }).on("data", handleNewStoreEvent);
|
||||
for (const address of Object.keys(stores)) {
|
||||
const store = stores[address];
|
||||
registerStoreEventHandlers(store.contract);
|
||||
}
|
||||
});
|
||||
|
||||
// this is also done in the reconnect above.
|
||||
web3.eth.subscribe('newBlockHeaders')
|
||||
.on('data', handleNewBlock)
|
||||
.on('error', e => {
|
||||
Sentry.captureException(e);
|
||||
});
|
||||
|
||||
// for each store, add a watch, starting at last block number above,
|
||||
// for transfer and mint events
|
||||
})().catch(e => {
|
||||
Sentry.captureException(e);
|
||||
console.error("Top-level failure, bailing and starting over from the top.", e);
|
||||
process.exit(1);
|
||||
});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,344 @@
|
|||
[
|
||||
{
|
||||
"inputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "tokenOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "tokens",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "previousOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "tokens",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "_totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "contractOwner",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "newContractOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "setContractOwner",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokens",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokens",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokens",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "remaining",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokens",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes",
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "approveAndCall",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "tokenAddress",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "tokens",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferAnyERC20Token",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "success",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,51 @@
|
|||
const Web3Util = require('web3-utils');
|
||||
const { Client } = require('pg');
|
||||
const https = require('https');
|
||||
const { argv } = require('process');
|
||||
|
||||
const account = Web3Util.toChecksumAddress(process.argv[2]);
|
||||
const method = process.argv[3].toUpperCase();
|
||||
const url = process.argv[4];
|
||||
const path = new URL(url).pathname;
|
||||
|
||||
const settings = require('./test-settings.json');
|
||||
|
||||
const client = new Client(settings.db);
|
||||
client.connect().then(async () => {
|
||||
const result = await client.query("select token from account where id = $1", [ account ]);
|
||||
const token = result.rows[0].token;
|
||||
const now = new Date();
|
||||
const hashString = `${now} ${account} ${token} ${path}`;
|
||||
console.log(hashString);
|
||||
const hash = Web3Util.keccak256(hashString);
|
||||
|
||||
const headers = {
|
||||
'X-NftStore-Now': now,
|
||||
'X-NftStore-Account': account,
|
||||
'Authorization': `Bearer ${hash}`
|
||||
}
|
||||
|
||||
if (argv.length > 5) {
|
||||
var body = process.argv[5];
|
||||
headers['Content-Type'] = 'application/json';
|
||||
headers['Content-Length'] = body.length;
|
||||
}
|
||||
|
||||
const req = https.request(url, { method, headers }, res => {
|
||||
console.log(res.headers);
|
||||
res.on('data', d => {
|
||||
console.log(d.toString());
|
||||
});
|
||||
res.on('error', e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
res.on('end', () => {
|
||||
process.exit(res.statusCode);
|
||||
});
|
||||
});
|
||||
if (argv.length > 5) {
|
||||
req.write(body);
|
||||
}
|
||||
req.end();
|
||||
});
|
Loading…
Reference in New Issue