Merge branch 'ABI-Defined-Dashboars' into add-crawler-of-data

pull/390/head
Andrey Dolgolev 2021-11-11 16:11:29 +02:00
commit 77371d891c
56 zmienionych plików z 20487 dodań i 1846 usunięć

Wyświetl plik

@ -0,0 +1,198 @@
import argparse
import json
import logging
import time
from typing import Optional
from moonstreamdb.db import yield_db_session_ctx
from sqlalchemy.orm.session import Session
from web3 import Web3
from ..ethereum import connect
from .deployment_crawler import ContractDeploymentCrawler, MoonstreamDataStore
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def run_crawler_asc(
w3: Web3,
session: Session,
from_block: Optional[int],
to_block: Optional[int],
synchronize: bool,
batch_size: int,
respect_state: bool,
sleep_time: int,
):
"""
Runs crawler in ascending order
"""
moonstream_data_store = MoonstreamDataStore(session)
contract_deployment_crawler = ContractDeploymentCrawler(w3, moonstream_data_store)
if respect_state:
from_block = moonstream_data_store.get_last_labeled_block_number() + 1
logger.info(f"Respecting state, starting from block {from_block}")
if from_block is None:
from_block = moonstream_data_store.get_first_block_number()
logger.info(f"Starting block set to : {from_block}")
if to_block is None:
to_block = moonstream_data_store.get_last_block_number()
logger.info(f"Ending block set to : {to_block}")
assert (
from_block <= to_block
), "from_block must be less than or equal to to_block in asc order, used --order desc"
logger.info(f"Starting crawling from block {from_block} to block {to_block}")
contract_deployment_crawler.crawl(
from_block=from_block,
to_block=to_block,
batch_size=batch_size,
)
if synchronize:
last_crawled_block = to_block
while True:
contract_deployment_crawler.crawl(
from_block=last_crawled_block + 1,
to_block=None, # to_block will be set to last_crawled_block
batch_size=batch_size,
)
time.sleep(sleep_time)
def run_crawler_desc(
w3: Web3,
session: Session,
from_block: Optional[int],
to_block: Optional[int],
synchronize: bool,
batch_size: int,
respect_state: bool,
sleep_time: int,
):
"""
Runs crawler in descending order
"""
moonstream_data_store = MoonstreamDataStore(session)
contract_deployment_crawler = ContractDeploymentCrawler(w3, moonstream_data_store)
if respect_state:
to_block = moonstream_data_store.get_first_block_number() - 1
logger.info(f"Respecting state, ending at block {to_block}")
if from_block is None:
from_block = moonstream_data_store.get_last_block_number()
logger.info(f"Starting block set to : {from_block}")
if to_block is None:
to_block = moonstream_data_store.get_first_block_number()
logger.info(f"Ending block set to : {to_block}")
assert (
from_block >= to_block
), "from_block must be greater than or equal to to_block in desc order, used --order asc"
logger.info(f"Starting crawling from block {from_block} to block {to_block}")
contract_deployment_crawler.crawl(
from_block=from_block,
to_block=to_block,
batch_size=batch_size,
)
if synchronize:
last_crawled_block = to_block
while True:
to_block = moonstream_data_store.get_first_block_number()
contract_deployment_crawler.crawl(
from_block=last_crawled_block - 1,
to_block=to_block,
batch_size=batch_size,
)
time.sleep(sleep_time)
def handle_parser(args: argparse.Namespace):
with yield_db_session_ctx() as session:
w3 = connect()
if args.order == "asc":
run_crawler_asc(
w3=w3,
session=session,
from_block=args.start,
to_block=args.to,
synchronize=args.synchronize,
batch_size=args.batch,
respect_state=args.respect_state,
sleep_time=args.sleep,
)
elif args.order == "desc":
run_crawler_desc(
w3=w3,
session=session,
from_block=args.start,
to_block=args.to,
synchronize=args.synchronize,
batch_size=args.batch,
respect_state=args.respect_state,
sleep_time=args.sleep,
)
def generate_parser():
"""
--start, -s: block to start crawling from, default: minimum block from database
--to, -t: block to stop crawling at, default: maximum block from database
--order: order to crawl : (desc, asc) default: asc
--synchronize: Continious crawling, default: False
--batch, -b : batch size, default: 10
--respect-state: If set to True:\n If order is asc: start=last_labeled_block+1\n If order is desc: start=first_labeled_block-1
"""
parser = argparse.ArgumentParser(description="Moonstream Deployment Crawler")
parser.add_argument(
"--start", "-s", type=int, default=None, help="block to start crawling from"
)
parser.add_argument(
"--to", "-t", type=int, default=None, help="block to stop crawling at"
)
parser.add_argument(
"--order",
"-o",
type=str,
default="asc",
choices=["asc", "desc"],
help="order to crawl : (desc, asc)",
)
parser.add_argument(
"--synchronize", action="store_true", default=False, help="Continious crawling"
)
parser.add_argument("--batch", "-b", type=int, default=10, help="batch size")
parser.add_argument(
"--respect-state",
action="store_true",
default=False,
help="If set to True:\n If order is asc: start=last_labeled_block+1\n If order is desc: start=first_labeled_block-1",
)
parser.add_argument(
"--sleep",
type=int,
default=3 * 60,
help="time to sleep synzhronize mode waiting for new block crawled to db",
)
parser.set_defaults(func=handle_parser)
return parser
def main():
parser = generate_parser()
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()

Wyświetl plik

@ -0,0 +1,265 @@
import logging
from dataclasses import dataclass
from typing import Iterator, List, Optional, Tuple, cast
from hexbytes import HexBytes
from moonstreamdb.models import EthereumBlock, EthereumLabel, EthereumTransaction
from sqlalchemy.orm import Query, Session
from web3 import Web3
from web3.types import TxReceipt
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@dataclass
class ContractDeployment:
address: str
block_number: int
transaction_hash: str
deployer_address: str
block_timestamp: int
gas_used: int
gas_price: int
transaction_fee: int
@dataclass
class RawDeploymentTx:
transaction_hash: str
gas_price: int
timestamp: int
class MoonstreamDataStore:
def __init__(self, db_session: Session) -> None:
self.db_session = db_session
self.label = "contract_deployment"
def get_last_labeled_block_number(
self,
) -> int:
"""
Returns the last block number that has been labeled.
"""
last_block = (
self.db_session.query(EthereumLabel)
.filter(EthereumLabel.label == self.label)
.order_by(EthereumLabel.block_number.desc())
.first()
)
if last_block is None:
return 0
else:
return last_block.block_number
def get_first_labeled_block_number(self) -> int:
"""
Returns the first block number that has been labeled.
"""
first_block = (
self.db_session.query(EthereumLabel)
.filter(EthereumLabel.label == self.label)
.order_by(EthereumLabel.block_number)
.first()
)
if first_block is None:
return 0
return first_block.block_number
def get_last_block_number(self) -> int:
"""
Returns the last block number that has been processed.
"""
last_block = (
self.db_session.query(EthereumBlock)
.order_by(EthereumBlock.block_number.desc())
.first()
)
if last_block is None:
return 0
return last_block.block_number
def get_first_block_number(self) -> int:
"""
Returns the first block number that has been processed.
"""
first_block = (
self.db_session.query(EthereumBlock)
.order_by(EthereumBlock.block_number.asc())
.first()
)
if first_block is None:
return 0
return first_block.block_number
def get_raw_contract_deployment_transactions(
self, from_block: int, to_block: int
) -> List[RawDeploymentTx]:
"""
Returns a list of raw contract deployment transactions.
"""
result = (
self.db_session.query(
EthereumTransaction.hash,
EthereumTransaction.gas_price,
EthereumBlock.timestamp,
)
.join(
EthereumBlock,
EthereumTransaction.block_number == EthereumBlock.block_number,
)
.filter(EthereumBlock.block_number >= from_block)
.filter(EthereumBlock.block_number <= to_block)
.filter(EthereumTransaction.to_address == None)
.all()
)
return [
RawDeploymentTx(
transaction_hash=row[0],
gas_price=row[1],
timestamp=row[2],
)
for row in result
]
def save_contract_deployment_labels(
self, contract_deployment_list: List[ContractDeployment]
) -> None:
"""
Saves a list of contract deployment labels.
"""
transaction_hashes = [
contract_deployment.transaction_hash
for contract_deployment in contract_deployment_list
]
existing_labels = (
self.db_session.query(EthereumLabel.transaction_hash)
.filter(EthereumLabel.label == self.label)
.filter(EthereumLabel.transaction_hash.in_(transaction_hashes))
.all()
)
existing_labels_tx_hashes = [
label_tx_hash[0] for label_tx_hash in existing_labels
]
new_labels = [
EthereumLabel(
transaction_hash=contract_deployment.transaction_hash,
block_number=contract_deployment.block_number,
block_timestamp=contract_deployment.block_timestamp,
label=self.label,
address=contract_deployment.address,
label_data={
"deployer": contract_deployment.deployer_address,
"gasUsed": int(contract_deployment.gas_used),
"gasPrice": int(contract_deployment.gas_price),
"transactionFee": int(contract_deployment.transaction_fee),
},
)
for contract_deployment in contract_deployment_list
if contract_deployment.transaction_hash not in existing_labels_tx_hashes
]
if not new_labels:
return
try:
logger.info(f"Saving {len(new_labels)} new contract deployment labels.")
self.db_session.add_all(new_labels)
self.db_session.commit()
except Exception as e:
logger.error(f"Error saving contract deployment labels: {e}")
self.db_session.rollback()
def get_transaction_receipt(web3: Web3, tx_hash: str) -> TxReceipt:
"""
Returns the transaction receipt for the given transaction hash.
"""
return web3.eth.get_transaction_receipt(cast(HexBytes, tx_hash))
def get_contract_deployment_transactions(
web3: Web3,
datastore: MoonstreamDataStore,
from_block: int,
to_block: int,
) -> List[ContractDeployment]:
"""
Returns a list of ContractDeployment objects for all contract deployment transactions in the given block range.
"""
logger.info(
f"Getting contract deployment transactions from {from_block} to {to_block}"
)
contract_deployment_transactions = []
for raw_deployment_tx in datastore.get_raw_contract_deployment_transactions(
from_block, to_block
):
receipt = get_transaction_receipt(web3, raw_deployment_tx.transaction_hash)
if receipt is None:
continue
contract_deployment_transactions.append(
ContractDeployment(
address=cast(str, receipt["contractAddress"]),
block_number=receipt["blockNumber"],
transaction_hash=receipt["transactionHash"].hex(),
deployer_address=receipt["from"],
block_timestamp=raw_deployment_tx.timestamp,
gas_used=receipt["gasUsed"],
gas_price=raw_deployment_tx.gas_price,
transaction_fee=receipt["gasUsed"] * raw_deployment_tx.gas_price,
)
)
return contract_deployment_transactions
# Function Fully Generated by copilot, looks correct, lol
def get_batch_block_range(
from_block: int, to_block: int, batch_size: int
) -> Iterator[Tuple[int, int]]:
"""
Returns a list of block ranges with the given batch size, from_block and to_block inclusive.
"""
if from_block <= to_block:
while from_block <= to_block:
yield (from_block, min(from_block + batch_size - 1, to_block))
from_block += batch_size
else:
while to_block <= from_block:
yield (from_block, max(from_block - batch_size + 1, to_block))
from_block -= batch_size
class ContractDeploymentCrawler:
"""
Crawls contract deployments from MoonstreamDB transactions with the usage of web3
to get transaction recipts
"""
def __init__(self, web3: Web3, datastore: MoonstreamDataStore):
self.web3 = web3
self.datastore = datastore
def crawl(
self, from_block: Optional[int], to_block: Optional[int], batch_size: int = 200
) -> None:
"""
Crawls contract deployments in batches with the given batch size
If from_block is None then the first block from datastore is used as start
If to_block is None then the latest block from datastore is used
"""
if from_block is None:
from_block = self.datastore.get_first_block_number()
if to_block is None:
to_block = self.datastore.get_last_block_number()
for batch_from_block, batch_to_block in get_batch_block_range(
from_block, to_block, batch_size
):
contract_deployment_transactions = get_contract_deployment_transactions(
self.web3, self.datastore, batch_from_block, batch_to_block
)
self.datastore.save_contract_deployment_labels(
contract_deployment_transactions
)

Wyświetl plik

@ -0,0 +1,43 @@
from typing import Optional
from unittest import TestCase
from web3.main import Web3
from .deployment_crawler import get_batch_block_range
class TestDeploymentCrawler(TestCase):
def test_get_batch_block_range(self):
from_block = 0
to_block = 101
batch_size = 10
result = get_batch_block_range(from_block, to_block, batch_size)
last_end: Optional[int] = None
for batch_start, batch_end in result:
if last_end is not None:
self.assertEqual(batch_start, last_end + 1)
self.assertTrue(batch_start <= batch_end)
self.assertTrue(batch_start <= to_block)
self.assertTrue(batch_end <= to_block)
last_end = batch_end
self.assertEqual(last_end, to_block)
def test_get_batch_block_range_with_from_block_gt_to_block(self):
from_block = 101
to_block = 0
batch_size = 10
result = get_batch_block_range(from_block, to_block, batch_size)
last_end: Optional[int] = None
for batch_start, batch_end in result:
if last_end is not None:
self.assertEqual(batch_start, last_end - 1)
last_end = batch_end
self.assertTrue(batch_start >= batch_end)
self.assertTrue(batch_start >= to_block)
self.assertTrue(batch_end >= to_block)
self.assertEqual(last_end, to_block)

Wyświetl plik

@ -12,7 +12,7 @@ chardet==4.0.0
charset-normalizer==2.0.4
click==8.0.1
cytoolz==0.11.0
-e git+https://git@github.com/bugout-dev/moonstream.git@0a771ddfbca1254be331149ccf2d162aa09b7bc0#egg=moonstreamdb&subdirectory=db
-e git+https://git@github.com/bugout-dev/moonstream.git@67fe019f1086c435dd3b58f1ade2778acc2167c7#egg=moonstreamdb&subdirectory=db
eth-abi==2.1.1
eth-account==0.5.5
eth-hash==0.3.2

Wyświetl plik

@ -51,6 +51,7 @@ setup(
"identity=mooncrawl.identity:main",
"etherscan=mooncrawl.etherscan:main",
"nft=mooncrawl.nft.cli:main",
"contractcrawler=mooncrawl.contract.cli:main",
]
},
)

Wyświetl plik

@ -28,10 +28,13 @@ target_metadata = MoonstreamBase.metadata
from moonstreamdb.models import (
EthereumBlock,
EthereumTransaction,
EthereumPendingTransaction,
EthereumLabel,
ESDEventSignature,
PolygonBlock,
PolygonTransaction,
PolygonLabel,
ESDFunctionSignature,
ESDEventSignature,
OpenSeaCrawlingState,
)
@ -40,9 +43,12 @@ def include_symbol(tablename, schema):
EthereumBlock.__tablename__,
EthereumTransaction.__tablename__,
EthereumLabel.__tablename__,
EthereumPendingTransaction.__tablename__,
ESDEventSignature.__tablename__,
PolygonBlock.__tablename__,
PolygonTransaction.__tablename__,
PolygonLabel.__tablename__,
ESDFunctionSignature.__tablename__,
ESDEventSignature.__tablename__,
OpenSeaCrawlingState.__tablename__,
}

Wyświetl plik

@ -0,0 +1,56 @@
"""Drop pending tx table
Revision ID: 3d55105b0603
Revises: 0b46c8e17bf2
Create Date: 2021-11-08 10:03:18.246561
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '3d55105b0603'
down_revision = '0b46c8e17bf2'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index('ix_ethereum_pending_transactions_block_number', table_name='ethereum_pending_transactions')
op.drop_index('ix_ethereum_pending_transactions_from_address', table_name='ethereum_pending_transactions')
op.drop_index('ix_ethereum_pending_transactions_gas', table_name='ethereum_pending_transactions')
op.drop_index('ix_ethereum_pending_transactions_gas_price', table_name='ethereum_pending_transactions')
op.drop_index('ix_ethereum_pending_transactions_hash', table_name='ethereum_pending_transactions')
op.drop_index('ix_ethereum_pending_transactions_to_address', table_name='ethereum_pending_transactions')
op.drop_index('ix_ethereum_pending_transactions_value', table_name='ethereum_pending_transactions')
op.drop_table('ethereum_pending_transactions')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('ethereum_pending_transactions',
sa.Column('hash', sa.VARCHAR(length=256), autoincrement=False, nullable=False),
sa.Column('block_number', sa.BIGINT(), autoincrement=False, nullable=False),
sa.Column('from_address', sa.VARCHAR(length=256), autoincrement=False, nullable=True),
sa.Column('to_address', sa.VARCHAR(length=256), autoincrement=False, nullable=True),
sa.Column('gas', sa.NUMERIC(precision=78, scale=0), autoincrement=False, nullable=True),
sa.Column('gas_price', sa.NUMERIC(precision=78, scale=0), autoincrement=False, nullable=True),
sa.Column('input', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('nonce', sa.VARCHAR(length=256), autoincrement=False, nullable=True),
sa.Column('transaction_index', sa.BIGINT(), autoincrement=False, nullable=True),
sa.Column('value', sa.NUMERIC(precision=78, scale=0), autoincrement=False, nullable=True),
sa.Column('indexed_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text("timezone('utc'::text, statement_timestamp())"), autoincrement=False, nullable=False),
sa.ForeignKeyConstraint(['block_number'], ['ethereum_blocks.block_number'], name='fk_ethereum_pending_transactions_block_number_ethereum_blocks', ondelete='CASCADE'),
sa.PrimaryKeyConstraint('hash', name='pk_ethereum_pending_transactions')
)
op.create_index('ix_ethereum_pending_transactions_value', 'ethereum_pending_transactions', ['value'], unique=False)
op.create_index('ix_ethereum_pending_transactions_to_address', 'ethereum_pending_transactions', ['to_address'], unique=False)
op.create_index('ix_ethereum_pending_transactions_hash', 'ethereum_pending_transactions', ['hash'], unique=False)
op.create_index('ix_ethereum_pending_transactions_gas_price', 'ethereum_pending_transactions', ['gas_price'], unique=False)
op.create_index('ix_ethereum_pending_transactions_gas', 'ethereum_pending_transactions', ['gas'], unique=False)
op.create_index('ix_ethereum_pending_transactions_from_address', 'ethereum_pending_transactions', ['from_address'], unique=False)
op.create_index('ix_ethereum_pending_transactions_block_number', 'ethereum_pending_transactions', ['block_number'], unique=False)
# ### end Alembic commands ###

Wyświetl plik

@ -0,0 +1,112 @@
"""Polygon models
Revision ID: f991fc7493c8
Revises: 3d55105b0603
Create Date: 2021-11-08 10:44:05.882650
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'f991fc7493c8'
down_revision = '3d55105b0603'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('polygon_blocks',
sa.Column('block_number', sa.BigInteger(), nullable=False),
sa.Column('difficulty', sa.BigInteger(), nullable=True),
sa.Column('extra_data', sa.VARCHAR(length=128), nullable=True),
sa.Column('gas_limit', sa.BigInteger(), nullable=True),
sa.Column('gas_used', sa.BigInteger(), nullable=True),
sa.Column('base_fee_per_gas', sa.Numeric(precision=78, scale=0), nullable=True),
sa.Column('hash', sa.VARCHAR(length=256), nullable=True),
sa.Column('logs_bloom', sa.VARCHAR(length=1024), nullable=True),
sa.Column('miner', sa.VARCHAR(length=256), nullable=True),
sa.Column('nonce', sa.VARCHAR(length=256), nullable=True),
sa.Column('parent_hash', sa.VARCHAR(length=256), nullable=True),
sa.Column('receipt_root', sa.VARCHAR(length=256), nullable=True),
sa.Column('uncles', sa.VARCHAR(length=256), nullable=True),
sa.Column('size', sa.Integer(), nullable=True),
sa.Column('state_root', sa.VARCHAR(length=256), nullable=True),
sa.Column('timestamp', sa.BigInteger(), nullable=True),
sa.Column('total_difficulty', sa.VARCHAR(length=256), nullable=True),
sa.Column('transactions_root', sa.VARCHAR(length=256), nullable=True),
sa.Column('indexed_at', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', statement_timestamp())"), nullable=False),
sa.PrimaryKeyConstraint('block_number', name=op.f('pk_polygon_blocks'))
)
op.create_index(op.f('ix_polygon_blocks_block_number'), 'polygon_blocks', ['block_number'], unique=True)
op.create_index(op.f('ix_polygon_blocks_hash'), 'polygon_blocks', ['hash'], unique=False)
op.create_index(op.f('ix_polygon_blocks_timestamp'), 'polygon_blocks', ['timestamp'], unique=False)
op.create_table('polygon_labels',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('label', sa.VARCHAR(length=256), nullable=False),
sa.Column('block_number', sa.BigInteger(), nullable=True),
sa.Column('address', sa.VARCHAR(length=256), nullable=True),
sa.Column('transaction_hash', sa.VARCHAR(length=256), nullable=True),
sa.Column('label_data', postgresql.JSONB(astext_type=sa.Text()), nullable=True),
sa.Column('block_timestamp', sa.BigInteger(), nullable=True),
sa.Column('log_index', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', statement_timestamp())"), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_polygon_labels')),
sa.UniqueConstraint('id', name=op.f('uq_polygon_labels_id'))
)
op.create_index(op.f('ix_polygon_labels_address'), 'polygon_labels', ['address'], unique=False)
op.create_index(op.f('ix_polygon_labels_block_number'), 'polygon_labels', ['block_number'], unique=False)
op.create_index(op.f('ix_polygon_labels_block_timestamp'), 'polygon_labels', ['block_timestamp'], unique=False)
op.create_index(op.f('ix_polygon_labels_label'), 'polygon_labels', ['label'], unique=False)
op.create_index(op.f('ix_polygon_labels_transaction_hash'), 'polygon_labels', ['transaction_hash'], unique=False)
op.create_table('polygon_transactions',
sa.Column('hash', sa.VARCHAR(length=256), nullable=False),
sa.Column('block_number', sa.BigInteger(), nullable=False),
sa.Column('from_address', sa.VARCHAR(length=256), nullable=True),
sa.Column('to_address', sa.VARCHAR(length=256), nullable=True),
sa.Column('gas', sa.Numeric(precision=78, scale=0), nullable=True),
sa.Column('gas_price', sa.Numeric(precision=78, scale=0), nullable=True),
sa.Column('max_fee_per_gas', sa.Numeric(precision=78, scale=0), nullable=True),
sa.Column('max_priority_fee_per_gas', sa.Numeric(precision=78, scale=0), nullable=True),
sa.Column('input', sa.Text(), nullable=True),
sa.Column('nonce', sa.VARCHAR(length=256), nullable=True),
sa.Column('transaction_index', sa.BigInteger(), nullable=True),
sa.Column('transaction_type', sa.Integer(), nullable=True),
sa.Column('value', sa.Numeric(precision=78, scale=0), nullable=True),
sa.Column('indexed_at', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', statement_timestamp())"), nullable=False),
sa.ForeignKeyConstraint(['block_number'], ['polygon_blocks.block_number'], name=op.f('fk_polygon_transactions_block_number_polygon_blocks'), ondelete='CASCADE'),
sa.PrimaryKeyConstraint('hash', name=op.f('pk_polygon_transactions'))
)
op.create_index(op.f('ix_polygon_transactions_block_number'), 'polygon_transactions', ['block_number'], unique=False)
op.create_index(op.f('ix_polygon_transactions_from_address'), 'polygon_transactions', ['from_address'], unique=False)
op.create_index(op.f('ix_polygon_transactions_gas'), 'polygon_transactions', ['gas'], unique=False)
op.create_index(op.f('ix_polygon_transactions_gas_price'), 'polygon_transactions', ['gas_price'], unique=False)
op.create_index(op.f('ix_polygon_transactions_hash'), 'polygon_transactions', ['hash'], unique=True)
op.create_index(op.f('ix_polygon_transactions_to_address'), 'polygon_transactions', ['to_address'], unique=False)
op.create_index(op.f('ix_polygon_transactions_value'), 'polygon_transactions', ['value'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_polygon_transactions_value'), table_name='polygon_transactions')
op.drop_index(op.f('ix_polygon_transactions_to_address'), table_name='polygon_transactions')
op.drop_index(op.f('ix_polygon_transactions_hash'), table_name='polygon_transactions')
op.drop_index(op.f('ix_polygon_transactions_gas_price'), table_name='polygon_transactions')
op.drop_index(op.f('ix_polygon_transactions_gas'), table_name='polygon_transactions')
op.drop_index(op.f('ix_polygon_transactions_from_address'), table_name='polygon_transactions')
op.drop_index(op.f('ix_polygon_transactions_block_number'), table_name='polygon_transactions')
op.drop_table('polygon_transactions')
op.drop_index(op.f('ix_polygon_labels_transaction_hash'), table_name='polygon_labels')
op.drop_index(op.f('ix_polygon_labels_label'), table_name='polygon_labels')
op.drop_index(op.f('ix_polygon_labels_block_timestamp'), table_name='polygon_labels')
op.drop_index(op.f('ix_polygon_labels_block_number'), table_name='polygon_labels')
op.drop_index(op.f('ix_polygon_labels_address'), table_name='polygon_labels')
op.drop_table('polygon_labels')
op.drop_index(op.f('ix_polygon_blocks_timestamp'), table_name='polygon_blocks')
op.drop_index(op.f('ix_polygon_blocks_hash'), table_name='polygon_blocks')
op.drop_index(op.f('ix_polygon_blocks_block_number'), table_name='polygon_blocks')
op.drop_table('polygon_blocks')
# ### end Alembic commands ###

Wyświetl plik

@ -155,15 +155,43 @@ class EthereumLabel(Base): # type: ignore
)
class EthereumPendingTransaction(Base): # type: ignore
__tablename__ = "ethereum_pending_transactions"
class PolygonBlock(Base): # type: ignore
__tablename__ = "polygon_blocks"
block_number = Column(
BigInteger, primary_key=True, unique=True, nullable=False, index=True
)
difficulty = Column(BigInteger)
extra_data = Column(VARCHAR(128))
gas_limit = Column(BigInteger)
gas_used = Column(BigInteger)
base_fee_per_gas = Column(Numeric(precision=78, scale=0), nullable=True)
hash = Column(VARCHAR(256), index=True)
logs_bloom = Column(VARCHAR(1024))
miner = Column(VARCHAR(256))
nonce = Column(VARCHAR(256))
parent_hash = Column(VARCHAR(256))
receipt_root = Column(VARCHAR(256))
uncles = Column(VARCHAR(256))
size = Column(Integer)
state_root = Column(VARCHAR(256))
timestamp = Column(BigInteger, index=True)
total_difficulty = Column(VARCHAR(256))
transactions_root = Column(VARCHAR(256))
indexed_at = Column(
DateTime(timezone=True), server_default=utcnow(), nullable=False
)
class PolygonTransaction(Base): # type: ignore
__tablename__ = "polygon_transactions"
hash = Column(
VARCHAR(256), primary_key=True, unique=True, nullable=False, index=True
)
block_number = Column(
BigInteger,
ForeignKey("ethereum_blocks.block_number", ondelete="CASCADE"),
ForeignKey("polygon_blocks.block_number", ondelete="CASCADE"),
nullable=False,
index=True,
)
@ -171,9 +199,12 @@ class EthereumPendingTransaction(Base): # type: ignore
to_address = Column(VARCHAR(256), index=True)
gas = Column(Numeric(precision=78, scale=0), index=True)
gas_price = Column(Numeric(precision=78, scale=0), index=True)
max_fee_per_gas = Column(Numeric(precision=78, scale=0), nullable=True)
max_priority_fee_per_gas = Column(Numeric(precision=78, scale=0), nullable=True)
input = Column(Text)
nonce = Column(VARCHAR(256))
transaction_index = Column(BigInteger)
transaction_type = Column(Integer, nullable=True)
value = Column(Numeric(precision=78, scale=0), index=True)
indexed_at = Column(
@ -181,9 +212,58 @@ class EthereumPendingTransaction(Base): # type: ignore
)
class PolygonLabel(Base): # type: ignore
"""
Example of label_data:
{
"label": "ERC20",
"label_data": {
"name": "Uniswap",
"symbol": "UNI"
}
},
{
"label": "Exchange"
"label_data": {...}
}
"""
__tablename__ = "polygon_labels"
id = Column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
unique=True,
nullable=False,
)
label = Column(VARCHAR(256), nullable=False, index=True)
block_number = Column(
BigInteger,
nullable=True,
index=True,
)
address = Column(
VARCHAR(256),
nullable=True,
index=True,
)
transaction_hash = Column(
VARCHAR(256),
nullable=True,
index=True,
)
label_data = Column(JSONB, nullable=True)
block_timestamp = Column(BigInteger, index=True)
log_index = Column(Integer, nullable=True)
created_at = Column(
DateTime(timezone=True), server_default=utcnow(), nullable=False
)
class ESDFunctionSignature(Base): # type: ignore
"""
Function signature from Ethereum Signature Database.
Function signature from blockchain (Ethereum/Polygon) Signature Database.
"""
__tablename__ = "esd_function_signatures"
@ -198,7 +278,7 @@ class ESDFunctionSignature(Base): # type: ignore
class ESDEventSignature(Base): # type: ignore
"""
Function signature from Ethereum Signature Database.
Function signature from blockchain (Ethereum/Polygon) Signature Database.
"""
__tablename__ = "esd_event_signatures"

Wyświetl plik

@ -2,4 +2,4 @@
Moonstream database version.
"""
MOONSTREAMDB_VERSION = "0.1.1"
MOONSTREAMDB_VERSION = "0.2.0"

Wyświetl plik

@ -1,3 +1,6 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

16112
frontend/package-lock.json wygenerowano 100644

Plik diff jest za duży Load Diff

Wyświetl plik

@ -18,11 +18,13 @@
"axios": "^0.21.1",
"color": "^4.0.1",
"downshift": "^6.1.7",
"core-js": "^3.19.1",
"focus-visible": "^5.2.0",
"framer-motion": "^4.1.17",
"mixpanel-browser": "^2.41.0",
"mobx": "^6.3.6",
"moment": "^2.29.1",
"next": "11.0.1",
"next": "11.1.2",
"nprogress": "^0.2.0",
"react": "^17.0.2",
"react-ace": "^9.5.0",
@ -30,18 +32,23 @@
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.0.2",
"react-dom": "^17.0.2",
"react-draggable": "^4.4.4",
"react-hook-form": "^6.9.2",
"react-hubspot-form": "^1.3.7",
"react-icons": "^4.2.0",
"react-icons": "^4.3.1",
"react-pro-sidebar": "^0.6.0",
"react-query": "^3.18.1",
"react-showdown": "^2.3.0",
"react-slick": "^0.28.1",
"react-split-pane": "^0.1.92",
"react-xarrows": "^2.0.2",
"redoc": "^2.0.0-rc.57",
"showdown": "^1.9.1",
"showdown-highlight": "^2.1.8",
"uuid": "^8.3.2",
"web3-utils": "^1.6.0"
"web3-utils": "^1.6.0",
"slick-carousel": "^1.8.1",
"styled-components": "^5.3.3"
},
"devDependencies": {
"@babel/core": "^7.14.3",

Wyświetl plik

@ -4,8 +4,11 @@ import "/styles/nprogress.css";
import "/styles/sidebar.css";
import "highlight.js/styles/github.css";
import "focus-visible/dist/focus-visible";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import dynamic from "next/dynamic";
import { QueryClient, QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import HeadLinks from "../src/components/HeadLinks";
import HeadSEO from "../src/components/HeadSEO";
const AppContext = dynamic(() => import("../src/AppContext"), {
@ -23,6 +26,16 @@ export default function CachingApp({ Component, pageProps }) {
const router = useRouter();
useEffect(() => {
if (
router.pathname !== "/entry-point" &&
window &&
localStorage.getItem("entry_point")
) {
localStorage.removeItem("entry_point");
}
}, [router]);
useEffect(() => {
const handleStart = () => {
NProgress.start();
@ -64,6 +77,7 @@ export default function CachingApp({ Component, pageProps }) {
{pageProps.metaTags && <HeadSEO {...pageProps.metaTags} />}
<HeadLinks links={headLinks} />
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools initialIsOpen={false} />
<AppContext>{getLayout(<Component {...pageProps} />)}</AppContext>
</QueryClientProvider>
</>

Wyświetl plik

@ -0,0 +1,344 @@
import React, { useContext, useEffect, useState } from "react";
import { getLayout } from "../../src/layouts/AppLayout";
import {
Spinner,
Flex,
Heading,
Stack,
Text,
Spacer,
IconButton,
} from "@chakra-ui/react";
import Scrollable from "../../src/components/Scrollable";
import RangeSelector from "../../src/components/RangeSelector";
import useDashboard from "../../src/core/hooks/useDashboard";
import { useRouter } from "../../src/core/hooks";
import { BiTrash } from "react-icons/bi";
import OverlayContext from "../../src/core/providers/OverlayProvider/context";
const HOUR_KEY = "Hourly";
const DAY_KEY = "Daily";
let timeMap = {};
timeMap[HOUR_KEY] = "hour";
timeMap[DAY_KEY] = "day";
const Analytics = () => {
const { toggleAlert } = useContext(OverlayContext);
useEffect(() => {
if (typeof window !== "undefined") {
document.title = `NFT Analytics`;
}
}, []);
// const [nodesReady, setNodeReady] = useState({
// ntx: false,
// values: false,
// mints: false,
// NFTOwners: false,
// minters: false,
// });
// const nTxRef_ = useRef();
// const valueRef_ = useRef();
// const mintsRef_ = useRef();
// const uniqueNFTOwnersRef_ = useRef();
// const mintersRef_ = useRef();
// const nTxRef = useCallback(
// (node) => {
// if (node !== null && !nodesReady.ntx) {
// setNodeReady({ ...nodesReady, ntx: true });
// nTxRef_.current = node;
// }
// },
// [nodesReady]
// );
// const valueRef = useCallback(
// (node) => {
// if (node !== null && !nodesReady.values) {
// setNodeReady({ ...nodesReady, values: true });
// valueRef_.current = node;
// }
// },
// [nodesReady]
// );
// const mintsRef = useCallback(
// (node) => {
// if (node !== null && !nodesReady.mints) {
// setNodeReady({ ...nodesReady, mints: true });
// mintsRef_.current = node;
// }
// },
// [nodesReady]
// );
// const uniqueNFTOwnersRef = useCallback(
// (node) => {
// if (node !== null && !nodesReady.NFTOwners) {
// setNodeReady({ ...nodesReady, NFTOwners: true });
// uniqueNFTOwnersRef_.current = node;
// }
// },
// [nodesReady]
// );
// const mintersRef = useCallback(
// (node) => {
// if (node !== null && !nodesReady.minters) {
// setNodeReady({ ...nodesReady, minters: true });
// mintersRef_.current = node;
// }
// },
// [nodesReady]
// );
const [timeRange, setTimeRange] = useState(HOUR_KEY);
const router = useRouter();
const { dashboardId } = router.params;
console.log("router paras:", router.params, dashboardId);
const { dashboardCache, deleteDashboard } = useDashboard(dashboardId);
// useLayoutEffect(() => {
// const items = [
// nTxRef_,
// valueRef_,
// mintsRef_,
// uniqueNFTOwnersRef_,
// mintersRef_,
// ];
// console.log("useeffect fired");
// if (items.some((item) => !!item.current)) {
// console.log("brder fun");
// var firstItemInCurrentRow = items[0];
// items.forEach((item) => {
// if (item.current) {
// if (item !== firstItemInCurrentRow) {
// // Check if the current item is at the same
// // height as the first item in the current row.
// if (
// item.current.offsetTop === firstItemInCurrentRow.current.offsetTop
// ) {
// item.current.style.borderLeft =
// "3px dashed var(--chakra-colors-gray-600)";
// } else {
// // This item was lower, it must be
// // the first in a new row.
// firstItemInCurrentRow = item;
// item.current.style.borderLeft = "0px dashed black";
// }
// }
// } else {
// firstItemInCurrentRow = item;
// }
// });
// }
// }, [nodesReady, windowSize]);
if (dashboardCache.isLoading) return <Spinner />;
const plotMinW = "500px";
return (
<Scrollable>
<Flex
h="100%"
w="100%"
m={0}
px="7%"
direction="column"
alignItems="center"
minH="100vh"
>
<Stack direction="row" w="100%" placeItems="center">
<Heading as="h1" py={2} fontSize={["md", "xl"]}>
NFT market analysis
</Heading>
<Spacer />
<RangeSelector
initialRange={timeRange}
ranges={Object.keys(timeMap)}
size={["sm", "md", null]}
onChange={(e) => setTimeRange(e)}
/>
<IconButton
icon={<BiTrash />}
variant="ghost"
colorScheme="red"
size="sm"
onClick={() => toggleAlert(() => deleteDashboard.mutate())}
/>
</Stack>
<Stack
w="100%"
wrap="wrap"
my={2}
h="auto"
direction="row"
minW="240px"
spacing={[0, 0, null]}
boxShadow="md"
borderRadius="lg"
bgColor="gray.100"
>
{/* <StatsCard
ref={(node) => nTxRef(node)}
labelKey="nft_transfers"
totalKey="num_transactions"
timeRange={timeMap[timeRange]}
netLabel="Ethereum mainnet"
label="Number of NFT purchases"
/>
<StatsCard
ref={(node) => valueRef(node)}
labelKey="nft_transfer_value"
totalKey="total_value"
timeRange={timeMap[timeRange]}
netLabel="Ethereum mainnet"
label="Money spent"
/>
<StatsCard
ref={(node) => mintsRef(node)}
labelKey="nft_mints"
timeRange={timeMap[timeRange]}
netLabel="Ethereum mainnet"
label="NFTs created"
/>
<StatsCard
ref={(node) => uniqueNFTOwnersRef(node)}
labelKey="nft_owners"
timeRange={timeMap[timeRange]}
netLabel="Ethereum mainnet"
label="Number of buyers"
/>
<StatsCard
ref={(node) => mintersRef(node)}
labelKey="nft_minters"
timeRange={timeMap[timeRange]}
netLabel="Ethereum mainnet"
label="Number of creators"
/> */}
</Stack>
<Flex w="100%" direction="row" flexWrap="wrap-reverse">
<Flex
flexBasis={plotMinW}
flexGrow={1}
minW={plotMinW}
minH="320px"
maxH="420px"
direction="column"
boxShadow="md"
m={2}
>
<Text
w="100%"
py={2}
bgColor="gray.50"
fontWeight="600"
textAlign="center"
>
New NFTs
</Text>
{/* <NFTChart keyPosition={`nft_mints`} timeRange={timeRange} /> */}
</Flex>
<Flex
flexBasis={plotMinW}
flexGrow={1}
minW={plotMinW}
minH="320px"
maxH="420px"
direction="column"
boxShadow="md"
m={2}
>
<Text
w="100%"
py={2}
bgColor="gray.50"
fontWeight="600"
textAlign="center"
>
NFT creators
</Text>
{/* <NFTChart keyPosition={`nft_minters`} timeRange={timeRange} /> */}
</Flex>
<Flex
flexBasis={plotMinW}
flexGrow={1}
minW={plotMinW}
minH="320px"
maxH="420px"
direction="column"
boxShadow="md"
m={2}
>
<Text
w="100%"
py={2}
bgColor="gray.50"
fontWeight="600"
textAlign="center"
>
NFT Buyers
</Text>
{/* <NFTChart keyPosition={`nft_owners`} timeRange={timeRange} /> */}
</Flex>
<Flex
flexBasis={plotMinW}
flexGrow={1}
minW={plotMinW}
minH="320px"
maxH="420px"
direction="column"
boxShadow="md"
m={2}
>
<Text
w="100%"
py={2}
bgColor="gray.50"
fontWeight="600"
textAlign="center"
>
Transaction volume
</Text>
{/* <NFTChart
keyPosition={`nft_transfers`}
keyTotal={`num_transactions`}
timeRange={timeRange}
/> */}
</Flex>
<Flex
flexBasis={plotMinW}
flexGrow={1}
minW={plotMinW}
minH="320px"
maxH="420px"
direction="column"
boxShadow="md"
m={2}
>
<Text
w="100%"
py={2}
bgColor="gray.50"
fontWeight="600"
textAlign="center"
>
Transaction value
</Text>
{/* <NFTChart
keyPosition={`nft_transfer_value`}
keyTotal={`total_value`}
timeRange={timeRange}
/> */}
</Flex>
</Flex>
</Flex>
</Scrollable>
);
};
Analytics.getLayout = getLayout;
export default Analytics;

Wyświetl plik

@ -0,0 +1,44 @@
import React from "react";
import { RedocStandalone } from "redoc";
import { Box } from "@chakra-ui/react";
import { getLayout } from "../src/layouts/RootLayout";
import { DEFAULT_METATAGS } from "../src/core/constants";
const Docs = () => {
return (
// <Box overflowY="hidden" w="100%" maxH="100%" minH="100vh">
<>
<Box w="100%" maxH="100vh" overflowY="scroll">
<RedocStandalone
specUrl="https://api.moonstream.to/openapi.json"
options={{
theme: {
colors: {
primary: { main: "#212990" },
success: { main: "#92D050" },
warning: { main: "#FD5602" },
error: { main: "#C53030" },
gray: { 50: "#f7f8fa", 100: "#eff1f4" },
},
rightPanel: { backgroundColor: "#34373d" },
},
}}
/>
</Box>
</>
// </Box>
);
};
export async function getStaticProps() {
const metaTags = {
title: "Moonstream: API Documentation",
description: "API Documentation to use moonstream.to",
keywords: "API, docs",
url: "https://www.moonstream.to/docs",
};
return { props: { metaTags: { ...DEFAULT_METATAGS, ...metaTags } } };
}
Docs.getLayout = getLayout;
export default Docs;

Wyświetl plik

@ -0,0 +1,29 @@
import { useRouter } from "next/router";
import { useContext, useLayoutEffect } from "react";
import UserContext from "../src/core/providers/UserProvider/context";
import { getLayout } from "../src/layouts/EntryPointLayout";
const EntryPoint = () => {
const router = useRouter();
const { isInit } = useContext(UserContext);
useLayoutEffect(() => {
if (router.isReady && isInit && router.asPath !== router.pathname + `/`) {
if (localStorage.getItem("entry_point")) {
router.replace("/404", router.asPath);
} else {
localStorage.setItem("entry_point", 1);
router.replace(router.asPath, undefined, {
shallow: true,
});
}
}
}, [router, isInit]);
return "";
};
EntryPoint.getLayout = getLayout;
export default EntryPoint;

Wyświetl plik

@ -1,4 +1,10 @@
import React, { useState, Suspense, useEffect, useLayoutEffect } from "react";
import React, {
useState,
useContext,
Suspense,
useEffect,
useLayoutEffect,
} from "react";
import {
Fade,
Flex,
@ -23,16 +29,81 @@ import {
MIXPANEL_PROPS,
MIXPANEL_EVENTS,
} from "../src/core/providers/AnalyticsProvider/constants";
import { AWS_ASSETS_PATH } from "../src/core/constants";
import { AWS_ASSETS_PATH, DEFAULT_METATAGS } from "../src/core/constants";
import mixpanel from "mixpanel-browser";
import { MODAL_TYPES } from "../src/core/providers/OverlayProvider/constants";
const ConnectedButtons = dynamic(
() => import("../src/components/ConnectedButtons"),
import UIContext from "../src/core/providers/UIProvider/context";
import TrustedBadge from "../src/components/TrustedBadge";
import Slider from "react-slick";
import SchematicPlayground from "../src/components/SchematicPlayground";
import { v4 as uuidv4 } from "uuid";
import RouteButton from "../src/components/RouteButton";
import { FaDiscord } from "react-icons/fa";
const SplitWithImage = dynamic(
() => import("../src/components/SplitWithImage"),
{
ssr: false,
}
);
const FaGithubSquare = dynamic(() =>
import("react-icons/fa").then((mod) => mod.FaGithubSquare)
);
const GiSuspicious = dynamic(() =>
import("react-icons/gi").then((mod) => mod.GiSuspicious)
);
const GiHook = dynamic(() =>
import("react-icons/gi").then((mod) => mod.GiHook)
);
const IoTelescopeSharp = dynamic(() =>
import("react-icons/io5").then((mod) => mod.IoTelescopeSharp)
);
const AiFillApi = dynamic(() =>
import("react-icons/ai").then((mod) => mod.AiFillApi)
);
const BiTransfer = dynamic(() =>
import("react-icons/bi").then((mod) => mod.BiTransfer)
);
const RiDashboardFill = dynamic(() =>
import("react-icons/ri").then((mod) => mod.RiDashboardFill)
);
const FaFileContract = dynamic(() =>
import("react-icons/fa").then((mod) => mod.FaFileContract)
);
const GiMeshBall = dynamic(() =>
import("react-icons/gi").then((mod) => mod.GiMeshBall)
);
const GiLogicGateXor = dynamic(() =>
import("react-icons/gi").then((mod) => mod.GiLogicGateXor)
);
const VscOrganization = dynamic(() =>
import("react-icons/vsc").then((mod) => mod.VscOrganization)
);
const FaVoteYea = dynamic(() =>
import("react-icons/fa").then((mod) => mod.FaVoteYea)
);
const RiOrganizationChart = dynamic(() =>
import("react-icons/ri").then((mod) => mod.RiOrganizationChart)
);
const FiActivity = dynamic(() =>
import("react-icons/fi").then((mod) => mod.FiActivity)
);
const RiMapPinUserLine = dynamic(() =>
import("react-icons/ri").then((mod) => mod.RiMapPinUserLine)
);
const AiOutlinePieChart = dynamic(() =>
import("react-icons/ai").then((mod) => mod.AiOutlinePieChart)
);
const BiBot = dynamic(() => import("react-icons/bi").then((mod) => mod.BiBot));
const HEADING_PROPS = {
fontWeight: "700",
fontSize: ["4xl", "5xl", "4xl", "5xl", "6xl", "7xl"],
@ -47,14 +118,40 @@ const assets = {
pendingTransactions: `${AWS_ASSETS_PATH}/Ethereum+pending+transactions.png`,
priceInformation: `${AWS_ASSETS_PATH}/Price+information.png`,
socialMediaPosts: `${AWS_ASSETS_PATH}/Social+media+posts.png`,
cryptoTraders: `${AWS_ASSETS_PATH}/crypto+traders.png`,
smartDevelopers: `${AWS_ASSETS_PATH}/smart+contract+developers.png`,
cointelegraph: `${AWS_ASSETS_PATH}/featured_by/Cointelegraph_logo.png`,
cryptoinsiders: `${AWS_ASSETS_PATH}/featured_by/crypto_insiders.png`,
cryptoslate: `${AWS_ASSETS_PATH}/featured_by/cs-media-logo-light.png`,
bitcoinLogo: `${AWS_ASSETS_PATH}/bitcoin.png`,
ethereumBlackLogo: `${AWS_ASSETS_PATH}/eth-diamond-black.png`,
ethereumRainbowLogo: `${AWS_ASSETS_PATH}/eth-diamond-rainbow.png`,
maticLogo: `${AWS_ASSETS_PATH}/matic-token-inverted-icon.png`,
erc20: `${AWS_ASSETS_PATH}/ERC 20.png`,
DAO: `${AWS_ASSETS_PATH}/DAO .png`,
NFT: `${AWS_ASSETS_PATH}/NFT.png`,
};
const carousel_content = [
{ title: "Bitcoin coming soon!", img: assets["bitcoinLogo"] },
{ title: "Ethereum", img: assets["ethereumBlackLogo"] },
{ title: "Ethereum transaction pool", img: assets["ethereumRainbowLogo"] },
{ title: "Polygon coming soon!", img: assets["maticLogo"] },
{ title: "Bitcoin coming soon!", img: assets["bitcoinLogo"] },
{ title: "Ethereum", img: assets["ethereumBlackLogo"] },
{ title: "Ethereum transaction pool", img: assets["ethereumRainbowLogo"] },
{ title: "Polygon coming soon!", img: assets["maticLogo"] },
];
const Homepage = () => {
const ui = useContext(UIContext);
const [background, setBackground] = useState("background720");
const [backgroundLoaded720, setBackgroundLoaded720] = useState(false);
const [backgroundLoaded1920, setBackgroundLoaded1920] = useState(false);
const [backgroundLoaded2880, setBackgroundLoaded2880] = useState(false);
const [backgroundLoaded3840, setBackgroundLoaded3840] = useState(false);
const [imageIndex, setImageIndex] = useState(0);
const router = useRouter();
const { isInit } = useUser();
const { toggleModal } = useModals();
@ -147,6 +244,24 @@ const Homepage = () => {
};
}, []);
const settings = {
infinite: true,
lazyLoad: true,
speed: 2000,
autoplay: true,
autoplaySpeed: 0,
// cssEase: "linear",
cssEase: "cubic-bezier(0.165, 0.840, 0.440, 1.000)",
// cssEase: "ease-in",
slidesToScroll: 1,
slidesToShow: ui.isMobileView ? 3 : 5,
centerMode: true,
centerPadding: 0,
// nextArrow: "",
// prevArrow: "",
beforeChange: (current, next) => setImageIndex(next),
};
return (
<Suspense fallback="">
<Fade in>
@ -194,6 +309,7 @@ const Homepage = () => {
alignItems="center"
spacing={6}
maxW={["1620px", null, null, null, "1620px", "2222px"]}
w="100%"
px="7%"
h="100%"
pt={["10vh", null, "20vh"]}
@ -215,6 +331,54 @@ const Homepage = () => {
understand exactly how people are using your smart
contracts.
</chakra.span>
<Box
w="100vw"
minH="200px"
// px="7%"
py={0}
overflowX="hidden"
overflowY="visible"
>
<Slider
{...settings}
// adaptiveHeight={true}
arrows={false}
autoplay={true}
autoplaySpeed={100}
>
{carousel_content.map((content_item, idx) => (
<Box
pt="80px"
h="auto"
w="150px"
maxW="150px"
// size="150px"
key={uuidv4()}
className={
idx === imageIndex
? "slide activeSlide"
: "slide"
}
// bgColor="blue.900"
// borderRadius="lg"
// boxShadow="lg"
>
<ChakraImage
fit="contain"
boxSize={["64px", "96px", "130px", null]}
src={content_item.img}
/>
<Text
py={2}
color="blue.300"
fontSize={["sm", "md", null]}
>
{content_item.title}
</Text>
</Box>
))}
</Slider>
</Box>
</Stack>
</Flex>
</Box>
@ -222,84 +386,61 @@ const Homepage = () => {
</GridItem>
<GridItem px="7%" colSpan="12" pt={0} minH="100vh">
<chakra.span
textAlign="center"
fontWeight="600"
fontSize="lg"
w="100%"
h="fit-content"
>
<Text
mb={18}
fontSize={["md", "2xl", "3xl", "3xl", "3xl", "4xl"]}
>
We believe that the blockchain is for everyone. This
requires complete <b>transparency</b>. Thats why all our
software is{" "}
<chakra.span
display="inline-block"
textColor="orange.900"
as={Link}
href="https://github.com/bugout-dev/moonstream"
>
<i>open source</i>
</chakra.span>
</Text>
</chakra.span>
<Heading
{...HEADING_PROPS}
textAlign="center"
mt={48}
mt={[24, 32, 48]}
pb={[12, 12, 12, null, 24]}
>
See how your smart contracts are being used from:
Get analytics for your:
</Heading>
<SimpleGrid columns={[1, 2, 2, 4, null, 4]}>
<Stack spacing={1} px={1} alignItems="center">
<ChakraImage
boxSize={["220px", "220px", "xs", null, "xs"]}
objectFit="contain"
src={assets["minedTransactions"]}
src={assets["NFT"]}
alt="mined transactions"
/>
<Heading textAlign="center ">
Ethereum mined transactions
</Heading>
<Heading textAlign="center ">NFTs</Heading>
</Stack>
<Stack spacing={1} px={1} alignItems="center">
<ChakraImage
boxSize={["220px", "220px", "xs", null, "xs"]}
objectFit="contain"
src={assets["pendingTransactions"]}
src={assets["erc20"]}
alt="mined transactions"
/>
<Heading textAlign="center ">
Ethereum pending transactions
</Heading>
<Heading textAlign="center ">Tokens</Heading>
</Stack>
<Stack spacing={1} px={1} alignItems="center">
<ChakraImage
boxSize={["220px", "220px", "xs", null, "xs"]}
objectFit="contain"
src={assets["cryptoTraders"]}
alt="mined transactions"
/>
<Heading textAlign="center ">DEXs</Heading>
</Stack>
<Stack spacing={1} px={1} alignItems="center">
<ChakraImage
boxSize={["220px", "220px", "xs", null, "xs"]}
objectFit="contain"
src={assets["priceInformation"]}
src={assets["DAO"]}
alt="mined transactions"
/>
<Heading textAlign="center ">Centralized exchanges</Heading>
</Stack>
<Stack spacing={1} px={1} alignItems="center">
<ChakraImage
boxSize={["220px", "220px", "xs", null, "xs"]}
objectFit="contain"
src={assets["socialMediaPosts"]}
alt="mined transactions"
/>
<Heading textAlign="center ">Social media posts</Heading>
<Heading textAlign="center ">{`DAOs`}</Heading>
</Stack>
</SimpleGrid>
<Center>
<Heading pt="160px" pb="60px">
Moonstream is meant for you if
<Heading
pt={["32px", "160px", null]}
pb={["12px", "60px", null]}
fontSize={["18px", "32px", null]}
textAlign="center"
>
Your game changer in blockchain analytics
</Heading>
</Center>
<Flex
@ -307,103 +448,347 @@ const Homepage = () => {
direction={["column", "row", "column", null, "column"]}
flexWrap={["nowrap", "nowrap", "nowrap", null, "nowrap"]}
pb="32px"
placeContent="center"
>
<ConnectedButtons
speedBase={0.3}
title={"You need a fusion of..."}
button4={{
label: "Blockchain analytics",
speed: 1,
// link: "/#analytics",
onClick: () => {
mixpanel.get_distinct_id() &&
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Connected buttons: scroll to analytics`,
});
},
}}
button1={{
label: "TX pool real time data",
speed: 9,
// link: "/#txpool",
onClick: () => {
mixpanel.get_distinct_id() &&
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Connected buttons: scroll to txpool`,
});
},
}}
button2={{
label: "Exchange price stream",
speed: 6,
// link: "/#exchanges",
onClick: () => {
mixpanel.get_distinct_id() &&
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Connected buttons: scroll to exchanges`,
});
},
}}
button3={{
label: "Social media posts",
speed: 3,
// link: "/#smartDeveloper",
onClick: () => {
mixpanel.get_distinct_id() &&
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Connected buttons: scroll to developer`,
});
},
}}
/>
<SchematicPlayground />
</Flex>
</GridItem>
<GridItem
px="7%"
colSpan="12"
pt="66px"
bgColor="blue.50"
pb={["20px", "30px", "92px", null, "92px", "196px"]}
>
<Heading {...HEADING_PROPS} textAlign="center" pb={14} pt={0}>
Featured by{" "}
</Heading>
<Flex wrap="wrap" direction="row" justifyContent="center">
<Suspense fallback={""}>
<TrustedBadge
name="cointelegraph"
caseURL=""
ImgURL={assets["cointelegraph"]}
/>
<TrustedBadge
name="CryptoInsiders"
ImgURL={assets["cryptoinsiders"]}
/>
<TrustedBadge
name="cryptoslate"
ImgURL={assets["cryptoslate"]}
/>
</Suspense>
</Flex>
</GridItem>
<GridItem
px="7%"
colSpan="12"
pt={["2rem", "2rem", "5.125rem", null, "5.125rem"]}
pb={["0", "66px", null, "66px"]}
id="txpool"
minH={ui.isMobileView ? "100vh" : null}
>
<SplitWithImage
cta={{
label: "Want to find out more?",
onClick: () => {
mixpanel.get_distinct_id() &&
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Early access CTA: developer txpool button`,
});
toggleModal("hubspot-developer");
},
}}
elementName={"element1"}
colorScheme="green"
badge={`NFTs`}
title={`Custom analytics for NFTs`}
body={`Moonstream automatically understands smart contracts. Create your own custom dashboards. Doesnt matter what the custom behavior is, you can track it.`}
bullets={[
{
text: `Who owns your NFTs?`,
icon: AiOutlinePieChart,
color: "green.50",
bgColor: "green.900",
},
{
text: `Who is selling your NFTs?`,
icon: FaFileContract,
color: "green.50",
bgColor: "green.900",
},
{
text: `How much are your NFTs being sold for on OpenSea, Nifty Gateway, Rarible?`,
icon: RiDashboardFill,
color: "green.50",
bgColor: "green.900",
},
{
text: `Who is using the custom features of your NFTs?`,
icon: GiMeshBall,
color: "green.50",
bgColor: "green.900",
},
{
text: `How are they using them?`,
icon: RiMapPinUserLine,
color: "green.50",
bgColor: "green.900",
},
]}
imgURL={assets["NFT"]}
/>
</GridItem>
<GridItem
px="7%"
colSpan="12"
pt={["2rem", "2rem", "5.125rem", null, "5.125rem"]}
pb={["0", "66px", null, "66px"]}
id="exchanges"
minH={ui.isMobileView ? "100vh" : null}
>
<SplitWithImage
cta={{
label: "Want to find out more?",
onClick: () => {
mixpanel.get_distinct_id() &&
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Early access CTA: developer exchanges button`,
});
toggleModal("hubspot-developer");
},
}}
elementName={"element2"}
mirror={true}
colorScheme="orange"
badge={`ERC20`}
title={`Feel the pulse of token activity`}
body={`Visualize market activity with Moonstream dashboards. Monitor token activity on the blockchain and in the transaction pool.`}
bullets={[
{
text: `Who owns your tokens?`,
icon: GiSuspicious,
color: "orange.50",
bgColor: "orange.900",
},
{
text: `What is your weekly, daily, or hourly transaction volume?`,
icon: AiFillApi,
color: "orange.50",
bgColor: "orange.900",
},
{
text: `Which exchanges is your token trending on?`,
icon: IoTelescopeSharp,
color: "orange.50",
bgColor: "orange.900",
},
{
text: `Which other tokens is your token being traded for?`,
icon: BiTransfer,
color: "orange.50",
bgColor: "orange.900",
},
{
text: `How many people are holding your token versus actively using it?
`,
icon: GiLogicGateXor,
color: "orange.50",
bgColor: "orange.900",
},
]}
imgURL={assets["erc20"]}
/>
</GridItem>
<GridItem
px="7%"
colSpan="12"
pt={["2rem", "2rem", "5.125rem", null, "5.125rem"]}
pb={["0", "66px", null, "66px"]}
id="smartDeveloper"
minH={ui.isMobileView ? "100vh" : null}
>
<SplitWithImage
cta={{
label: "Want to find out more?",
onClick: () => {
mixpanel.get_distinct_id() &&
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Early access CTA: developer smartDeveloper button`,
});
toggleModal("hubspot-developer");
},
}}
elementName={"element3"}
colorScheme="blue"
title={`All the data you need to make a market`}
badge={`DEXs`}
body={`Monitor the performance of your DEX live from the blockchain and from the transaction pool. Build dashboards that show you DEX activity monthly, weekly, daily, hourly, or by the minute.`}
bullets={[
{
text: `Who is providing liquidity on your DEX?`,
icon: GiSuspicious,
color: "blue.50",
bgColor: "blue.900",
},
{
text: `How much liquidity for each token pair?`,
icon: GiMeshBall,
color: "blue.50",
bgColor: "blue.900",
},
{
text: `Bot vs. human activity on your exchange`,
icon: BiBot,
color: "blue.50",
bgColor: "blue.900",
},
{
text: `How large is your transaction pool backlog?`,
icon: GiHook,
color: "blue.50",
bgColor: "blue.900",
},
]}
imgURL={assets["cryptoTraders"]}
/>
</GridItem>
<GridItem
px="7%"
colSpan="12"
pt={["2rem", "2rem", "5.125rem", null, "5.125rem"]}
pb={["0", "66px", null, "66px"]}
id="analytics"
minH={ui.isMobileView ? "100vh" : null}
>
<SplitWithImage
mirror
cta={{
label: "Want to find out more?",
onClick: () => {
mixpanel.get_distinct_id() &&
mixpanel.track(`${MIXPANEL_EVENTS.BUTTON_CLICKED}`, {
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Early access CTA: developer analytics button`,
});
toggleModal("hubspot-developer");
},
}}
elementName={"element3"}
colorScheme="red"
badge={`DAOs`}
title={`What really matters is community`}
body={`Gain insight into your community. Build community dashboards to make participation more open. Monitor your DAO ecosystem.`}
bullets={[
{
text: `Who are your community members?`,
icon: VscOrganization,
color: "red.50",
bgColor: "red.900",
},
{
text: `Who is actively participating?`,
icon: GiSuspicious,
color: "red.50",
bgColor: "red.900",
},
{
text: `What are the open initiatives for your DAO?`,
icon: FaVoteYea,
color: "red.50",
bgColor: "red.900",
},
{
text: `What is the level of participation for each initiative?`,
icon: FiActivity,
color: "red.50",
bgColor: "red.900",
},
{
text: `Which DAOs or other protocols interact with yours?
`,
icon: RiOrganizationChart,
color: "red.50",
bgColor: "red.900",
},
]}
imgURL={assets["DAO"]}
/>
</GridItem>
<GridItem
placeItems="center"
w="100%"
colSpan="12"
pt={["0", "0", "5.125rem", null, "5.125rem"]}
pb="120px"
px="7%"
>
<Center>
<Stack placeContent="center">
<Text fontWeight="500" fontSize="24px">
Want to find out more? Reach out to us on{" "}
<Link
color="orange.900"
onClick={() => {
mixpanel.get_distinct_id() &&
mixpanel.track(
`${MIXPANEL_EVENTS.BUTTON_CLICKED}`,
{
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Join our discord`,
}
);
}}
isExternal
href={"https://discord.gg/K56VNUQGvA"}
<Stack direction="column" justifyContent="center">
<chakra.span
textAlign="center"
fontWeight="600"
fontSize="lg"
w="100%"
h="fit-content"
>
<Text
mb={18}
fontSize={["md", "2xl", "3xl", "3xl", "3xl", "4xl"]}
>
We believe that the blockchain is for everyone. This
requires complete <b>transparency</b>. Thats why all our
software is{" "}
<chakra.span
display="inline-block"
textColor="orange.900"
as={Link}
href="https://github.com/bugout-dev/moonstream"
>
Discord
</Link>{" "}
or{" "}
<Link
color="orange.900"
onClick={() => {
mixpanel.get_distinct_id() &&
mixpanel.track(
`${MIXPANEL_EVENTS.BUTTON_CLICKED}`,
{
[`${MIXPANEL_PROPS.BUTTON_NAME}`]: `Early access CTA: developer want to find more button`,
}
);
toggleModal({ type: MODAL_TYPES.HUBSPOT });
}}
>
request early access
</Link>
<i>open source</i>
</chakra.span>
</Text>
</Stack>
</Center>
</chakra.span>
<Flex direction="row" flexWrap="wrap" placeContent="center">
<RouteButton
placeSelf="center"
isExternal
href={`https://github.com/bugout-dev/moonstream`}
size="md"
variant="outline"
colorScheme="blue"
w="250px"
leftIcon={<FaGithubSquare />}
>
git clone moonstream
</RouteButton>
<RouteButton
placeSelf="center"
isExternal
href={"https://discord.gg/K56VNUQGvA"}
size="md"
variant="outline"
colorScheme="blue"
leftIcon={<FaDiscord />}
w="250px"
>
Join our Discord
</RouteButton>
</Flex>
<RouteButton
placeSelf="center"
isExternal
w={["100%", "100%", "fit-content", null]}
maxW={["250px", null, "fit-content"]}
href={`https://github.com/bugout-dev/moonstream`}
size="lg"
variant="solid"
colorScheme="orange"
>
Sign up
</RouteButton>
</Stack>
</GridItem>
</Grid>
</Flex>
@ -414,16 +799,6 @@ const Homepage = () => {
};
export async function getStaticProps() {
const metaTags = {
title: "Moonstream.to: All your crypto data in one stream",
description:
"From the Ethereum transaction pool to Elon Musks latest tweets get all the crypto data you care about in one stream.",
keywords:
"blockchain, crypto, data, trading, smart contracts, ethereum, solana, transactions, defi, finance, decentralized",
url: "https://www.moonstream.to",
image: `${AWS_ASSETS_PATH}/crypto+traders.png`,
};
const assetPreload = Object.keys(assets).map((key) => {
return {
rel: "preload",
@ -436,7 +811,7 @@ export async function getStaticProps() {
const preloads = assetPreload.concat(preconnects);
return {
props: { metaTags, preloads },
props: { metaTags: DEFAULT_METATAGS, preloads },
};
}

Wyświetl plik

@ -129,7 +129,6 @@ const Product = () => {
bgColor="transparent"
backgroundImage={`url(${assets[`${background}`]})`}
bgSize="cover"
// boxSize="full"
minH="100vh"
direction="column"
alignItems="center"

Wyświetl plik

@ -1,68 +0,0 @@
import React, { useContext, useEffect } from "react";
import { getLayout } from "../../src/layouts/EntriesLayout";
import StreamEntryDetails from "../../src/components/SteamEntryDetails";
import UIContext from "../../src/core/providers/UIProvider/context";
import {
Box,
Heading,
Text,
Stack,
UnorderedList,
ListItem,
} from "@chakra-ui/react";
import RouteButton from "../../src/components/RouteButton";
const Entry = () => {
console.count("render stream!");
const ui = useContext(UIContext);
useEffect(() => {
if (typeof window !== "undefined") {
if (ui?.currentTransaction) {
document.title = `Stream details: ${ui.currentTransaction.hash}`;
} else {
document.title = `Stream`;
}
}
}, [ui?.currentTransaction]);
if (ui?.currentTransaction) {
return <StreamEntryDetails />;
} else
return (
<Box px="7%" pt={12}>
<>
<Stack direction="column">
<Heading>Stream view</Heading>
<Text>
In this view you can follow events that happen on your subscribed
addresses
</Text>
<UnorderedList pl={4}>
<ListItem>
Click filter icon on right top corner to filter by specific
address across your subscriptions
</ListItem>
<ListItem>
On event cards you can click at right corner to see detailed
view!
</ListItem>
<ListItem>
For any adress of interest here you can copy it and subscribe at
subscription screen
</ListItem>
</UnorderedList>
<RouteButton
variant="solid"
size="md"
colorScheme="green"
href="/welcome"
>
Learn how to use moonstream
</RouteButton>
</Stack>
</>
</Box>
);
};
Entry.getLayout = getLayout;
export default Entry;

Wyświetl plik

@ -0,0 +1,36 @@
import React from "react";
import { VStack, Link, Heading, Icon } from "@chakra-ui/react";
import { getLayout, getLayoutProps } from "../src/layouts/InfoPageLayout";
import { MdPictureAsPdf } from "react-icons/md";
const Papers = () => {
return (
<VStack>
<Heading py={12}>Whitepapers</Heading>
<Link
color="orange.900"
href="https://github.com/bugout-dev/moonstream/blob/main/datasets/nfts/papers/ethereum-nfts.pdf"
>
An analysis of 7,020,950 NFT transactions on the Ethereum blockchain -
October 22, 2021
<Icon as={MdPictureAsPdf} color="red" display="inline-block" />
</Link>
</VStack>
);
};
Papers.getLayout = getLayout;
export async function getStaticProps() {
const metaTags = {
title: "Moonstream: Whitepapers",
description: "Whitepapers by moonstream.to",
keywords:
"blockchain, crypto, data, trading, smart contracts, ethereum, solana, transactions, defi, finance, decentralized, analytics, product, whitepapers",
url: "https://www.moonstream.to/whitepapers",
};
const layoutProps = getLayoutProps();
layoutProps.props.metaTags = { ...layoutProps.props.metaTags, ...metaTags };
return { ...layoutProps };
}
export default Papers;

Wyświetl plik

@ -1,4 +1,4 @@
import React from "react";
import React, { useContext } from "react";
import RouterLink from "next/link";
import {
Menu,
@ -12,18 +12,23 @@ import {
} from "@chakra-ui/react";
import { RiAccountCircleLine } from "react-icons/ri";
import useLogout from "../core/hooks/useLogout";
import UIContext from "../core/providers/UIProvider/context";
import { ALL_NAV_PATHES } from "../core/constants";
import { v4 } from "uuid";
const AccountIconButton = (props) => {
const { logout } = useLogout();
const ui = useContext(UIContext);
return (
<Menu>
<MenuButton
{...props}
variant="inherit"
colorScheme="inherit"
as={IconButton}
aria-label="Account menu"
icon={<RiAccountCircleLine size="26px" />}
// variant="outline"
icon={<RiAccountCircleLine m={0} size="26px" />}
color="gray.100"
/>
<MenuList
@ -40,6 +45,17 @@ const AccountIconButton = (props) => {
</RouterLink>
</MenuGroup>
<MenuDivider />
{ui.isMobileView &&
ALL_NAV_PATHES.map((pathToLink) => {
return (
<MenuItem key={v4()}>
<RouterLink href={pathToLink.path}>
{pathToLink.title}
</RouterLink>
</MenuItem>
);
})}
<MenuDivider />
<MenuItem
onClick={() => {
logout();

Wyświetl plik

@ -29,7 +29,7 @@ const AddNewIconButton = (props) => {
as={IconButton}
// onClick={ui.addNewDrawerState.onOpen}
aria-label="Account menu"
icon={<PlusSquareIcon />}
icon={<PlusSquareIcon m={0} size="26px" />}
// variant="outline"
color="gray.100"
/>

Wyświetl plik

@ -171,9 +171,10 @@ const AppNavbar = () => {
variant="link"
justifyContent="space-evenly"
alignContent="center"
h="32px"
size={iconSize}
colorScheme="blue"
m={0}
h="100%"
/>
{!isSearchBarActive && (
<IconButton
@ -198,10 +199,7 @@ const AppNavbar = () => {
<Link href="/" alignSelf="center">
<Image
alignSelf="center"
// as={Link}
// to="/"
h="2.5rem"
minW="2.5rem"
maxH="2.5rem"
src={WHITE_LOGO_W_TEXT_URL}
alt="Go to app root"
/>

Wyświetl plik

@ -19,8 +19,6 @@ const ArrowCTA = (props) => {
const box3Ref = useRef(null);
const box4Ref = useRef(null);
// const gridRow = props.button4 ? [5, 4, 2, null, 2] : [4, 3, 2, null, 2];
const updateXarrow = useXarrow();
useEffect(() => {

Wyświetl plik

@ -0,0 +1,44 @@
import React, { useEffect, useState } from "react";
import Draggable from "react-draggable";
import { useXarrow } from "react-xarrows";
const DragOnGrid = React.forwardRef((props, ref) => {
const updateXarrow = useXarrow();
const [position, setPosition] = useState({
x: props.defaultPosition.x * props.gridStep,
y: props.defaultPosition.y * props.gridStep,
});
const [cellSize, setCellSize] = useState(props.gridStep);
useEffect(() => {
setPosition({
x: (position.x * props.gridStep) / cellSize,
y: (position.y * props.gridStep) / cellSize,
});
setCellSize(props.gridStep);
//eslint-disable-next-line
}, [props.gridStep]);
const handleDrag = (e, eData) => {
setTimeout(() => {
updateXarrow();
}, 50);
setPosition({ x: position.x + eData.deltaX, y: position.y + eData.deltaY });
};
return (
<Draggable
nodeRef={ref}
axis="both"
handle=".handle"
position={{ ...position }}
grid={[props.gridStep, props.gridStep]}
scale={1}
onDrag={handleDrag}
>
{props.children}
</Draggable>
);
});
export default DragOnGrid;

Wyświetl plik

@ -1,517 +0,0 @@
import React, { useEffect, useContext, useState, useCallback } from "react";
import {
Flex,
Spinner,
Button,
Center,
Text,
Menu,
MenuButton,
MenuList,
MenuItem,
MenuGroup,
IconButton,
Input,
Select,
Drawer,
DrawerBody,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
DrawerContent,
DrawerCloseButton,
useDisclosure,
Tag,
TagLabel,
TagCloseButton,
Stack,
Spacer,
} from "@chakra-ui/react";
import { useSubscriptions } from "../core/hooks";
import StreamEntry from "./StreamEntry";
import UIContext from "../core/providers/UIProvider/context";
import { FaFilter } from "react-icons/fa";
import useStream from "../core/hooks/useStream";
import { ImCancelCircle } from "react-icons/im";
import { previousEvent } from "../core/services/stream.service";
import { PAGE_SIZE } from "../core/constants";
import DataContext from "../core/providers/DataProvider/context";
const FILTER_TYPES = {
ADDRESS: 0,
GAS: 1,
GAS_PRICE: 2,
AMOUNT: 3,
HASH: 4,
DISABLED: 99,
};
const DIRECTIONS = { SOURCE: "from", DESTINATION: "to" };
const CONDITION = {
EQUAL: 0,
CONTAINS: 1,
LESS: 2,
LESS_EQUAL: 3,
GREATER: 4,
GREATER_EQUAL: 5,
NOT_EQUAL: 6,
};
const EntriesNavigation = () => {
const { cursor, setCursor, streamCache, setStreamCache } =
useContext(DataContext);
const ui = useContext(UIContext);
const [firstLoading, setFirstLoading] = useState(true);
const { isOpen, onOpen, onClose } = useDisclosure();
const { subscriptionsCache } = useSubscriptions();
const [initialized, setInitialized] = useState(false);
const [newFilterState, setNewFilterState] = useState([
{
type: FILTER_TYPES.ADDRESS,
direction: DIRECTIONS.SOURCE,
condition: CONDITION.EQUAL,
value: null,
},
]);
const [filterState, setFilterState] = useState([]);
const {
eventsIsLoading,
eventsRefetch,
latestEventsRefetch,
nextEventRefetch,
previousEventRefetch,
streamBoundary,
setDefaultBoundary,
loadPreviousEventHandler,
loadNewesEventHandler,
loadOlderEventsIsFetching,
loadNewerEventsIsFetching,
previousEventIsFetching,
nextEventIsFetching,
olderEvent,
} = useStream(
ui.searchTerm.q,
streamCache,
setStreamCache,
cursor,
setCursor
);
useEffect(() => {
if (!streamBoundary.start_time && !streamBoundary.end_time) {
setDefaultBoundary();
} else if (!initialized) {
eventsRefetch();
latestEventsRefetch();
nextEventRefetch();
previousEventRefetch();
setInitialized(true);
} else if (
streamCache.length == 0 &&
olderEvent?.event_timestamp &&
firstLoading
) {
loadPreviousEventHandler();
setFirstLoading(false);
}
}, [
streamBoundary,
initialized,
setInitialized,
setDefaultBoundary,
eventsRefetch,
latestEventsRefetch,
nextEventRefetch,
previousEventRefetch,
]);
const setFilterProps = useCallback(
(filterIdx, props) => {
const newFilterProps = [...newFilterState];
newFilterProps[filterIdx] = { ...newFilterProps[filterIdx], ...props };
setNewFilterState(newFilterProps);
},
[newFilterState, setNewFilterState]
);
useEffect(() => {
if (
subscriptionsCache.data?.subscriptions[0]?.id &&
newFilterState[0]?.value === null
) {
setFilterProps(0, {
value: subscriptionsCache?.data?.subscriptions[0]?.address,
});
}
}, [subscriptionsCache, newFilterState, setFilterProps]);
const canCreate = false;
const canDelete = false;
const dropNewFilterArrayItem = (idx) => {
const oldArray = [...newFilterState];
const newArray = oldArray.filter(function (ele) {
return ele != oldArray[idx];
});
setNewFilterState(newArray);
};
const dropFilterArrayItem = (idx) => {
const oldArray = [...filterState];
const newArray = oldArray.filter(function (ele) {
return ele != oldArray[idx];
});
setFilterState(newArray);
setNewFilterState(newArray);
ui.setSearchTerm(
newArray
.map((filter) => {
return filter.direction + ":" + filter.value;
})
.join("+")
);
};
const handleFilterSubmit = () => {
setFilterState(newFilterState);
ui.setSearchTerm(
newFilterState
.map((filter) => {
return filter.direction + ":" + filter.value;
})
.join("+")
);
onClose();
};
const handleAddressChange = (idx) => (e) => {
setFilterProps(idx, { value: e.target.value });
};
const handleConditionChange = (idx) => (e) => {
setFilterProps(idx, { condition: parseInt(e.target.value) });
};
const handleFilterStateCallback = (props) => {
const currentFilterState = [...filterState];
currentFilterState.push({ ...props });
ui.setSearchTerm(
currentFilterState
.map((filter) => {
return filter.direction + ":" + filter.value;
})
.join("+")
);
setFilterState(currentFilterState);
};
if (subscriptionsCache.isLoading) return "";
return (
<Flex
id="JournalNavigation"
height="100%"
maxH="100%"
overflow="hidden"
direction="column"
flexGrow={1}
>
{streamCache && !eventsIsLoading ? (
<>
<Drawer onClose={onClose} isOpen={isOpen} size="lg">
<DrawerOverlay />
<DrawerContent bgColor="gray.100">
<DrawerCloseButton />
<DrawerHeader>{`Filter results`}</DrawerHeader>
<DrawerBody>
<Text pt={2} fontWeight="600">
Source:
</Text>
{newFilterState.map((filter, idx) => {
if (filter.type === FILTER_TYPES.DISABLED) return "";
return (
<Flex
key={`subscription-filter-item-${idx}`}
direction="column"
>
<Flex
mt={4}
direction="row"
flexWrap="nowrap"
placeItems="center"
bgColor="gray.300"
borderRadius="md"
>
{filter.type === FILTER_TYPES.ADDRESS && (
<>
<Flex w="120px" placeContent="center">
{filter.direction === DIRECTIONS.SOURCE
? `From:`
: `To:`}
</Flex>
<Select
pr={2}
w="180px"
onChange={handleConditionChange(idx)}
>
<option value={CONDITION.EQUAL}>Is</option>
<option value={CONDITION.NOT_EQUAL}>
Is not
</option>
</Select>
{filter.direction === DIRECTIONS.SOURCE && (
<Select
variant="solid"
colorScheme="blue"
name="address"
onChange={handleAddressChange(idx)}
>
{!subscriptionsCache.isLoading &&
subscriptionsCache?.data?.subscriptions.map(
(subscription, idx) => {
return (
<option
value={subscription.address}
key={`subscription-filter-item-${idx}`}
>
{`${
subscription.label
} - ${subscription.address.slice(
0,
5
)}...${subscription.address.slice(
-3
)}`}
</option>
);
}
)}
</Select>
)}
{filter.direction === DIRECTIONS.DESTINATION && (
<Input
type="text"
onChange={(e) =>
setFilterProps(idx, {
value: e.target.value,
})
}
placeholder="Type in address"
/>
)}
</>
)}
<IconButton
placeItems="center"
colorScheme="blue"
variant="ghost"
onClick={() => dropNewFilterArrayItem(idx)}
icon={<ImCancelCircle />}
/>
</Flex>
</Flex>
);
})}
<Menu>
<MenuButton
as={Button}
mt={4}
colorScheme="orange"
variant="solid"
>
Add filter row
</MenuButton>
<MenuList>
<MenuGroup title="source"></MenuGroup>
<MenuItem
onClick={() =>
setNewFilterState([
...newFilterState,
{
type: FILTER_TYPES.ADDRESS,
direction: DIRECTIONS.SOURCE,
condition: CONDITION.EQUAL,
value:
subscriptionsCache?.data?.subscriptions[0]
?.address,
},
])
}
>
Source
</MenuItem>
<MenuItem
onClick={() =>
setNewFilterState([
...newFilterState,
{
type: FILTER_TYPES.ADDRESS,
direction: DIRECTIONS.DESTINATION,
condition: CONDITION.EQUAL,
value:
subscriptionsCache?.data?.subscriptions[0]
?.address,
},
])
}
>
Destination
</MenuItem>
</MenuList>
</Menu>
</DrawerBody>
<DrawerFooter pb={16} placeContent="center">
<Button
colorScheme="green"
variant="solid"
// type="submit"
onClick={() => handleFilterSubmit()}
>
Apply selected filters
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>
<Flex h="3rem" w="100%" bgColor="gray.100" alignItems="center">
<Flex maxW="90%">
{filterState.map((filter, idx) => {
if (filter.type === FILTER_TYPES.DISABLED) return "";
return (
<Tag
key={`filter-badge-display-${idx}`}
mx={1}
size="lg"
variant="solid"
colorScheme="orange"
>
{filter?.type === FILTER_TYPES.ADDRESS && (
<TagLabel>
{filter.condition === CONDITION.NOT_EQUAL && "Not "}
{filter.direction === DIRECTIONS.SOURCE
? "From: "
: "To: "}
{subscriptionsCache?.data?.subscriptions.find(
(subscription) =>
subscription.address === filter.value
)?.label ?? filter.value}
</TagLabel>
)}
<TagCloseButton onClick={() => dropFilterArrayItem(idx)} />
</Tag>
);
})}
</Flex>
<Spacer />
<IconButton
mr={4}
onClick={onOpen}
colorScheme="blue"
variant="ghost"
icon={<FaFilter />}
/>
</Flex>
<Flex
className="ScrollableWrapper"
w="100%"
overflowY="hidden"
h="calc(100% - 3rem)"
>
<Flex
className="Scrollable"
id="StreamEntry"
overflowY="scroll"
direction="column"
w="100%"
//onScroll={(e) => handleScroll(e)}
>
<Stack direction="row" justifyContent="space-between">
{!loadNewerEventsIsFetching && !nextEventIsFetching ? (
<Button
onClick={() => {
loadNewesEventHandler();
}}
variant="outline"
colorScheme="green"
>
Load newer events
</Button>
) : (
<Button
isLoading
loadingText="Loading"
variant="outline"
colorScheme="green"
></Button>
)}
</Stack>
{streamCache
.slice(
cursor,
streamCache.length <= cursor + PAGE_SIZE
? streamCache.length
: cursor + PAGE_SIZE
)
.map((entry, idx) => (
<StreamEntry
showOnboardingTooltips={false}
key={`entry-list-${idx}`}
entry={entry}
disableDelete={!canDelete}
disableCopy={!canCreate}
filterCallback={handleFilterStateCallback}
filterConstants={{ DIRECTIONS, CONDITION, FILTER_TYPES }}
/>
))}
{previousEvent &&
!loadOlderEventsIsFetching &&
!previousEventIsFetching ? (
<Center>
<Button
onClick={() => {
loadPreviousEventHandler();
}}
variant="outline"
colorScheme="green"
>
Load older events
</Button>
</Center>
) : (
<Center>
{!previousEventIsFetching && !loadOlderEventsIsFetching ? (
"Тransactions not found. You can subscribe to more addresses in Subscriptions menu."
) : (
<Button
isLoading
loadingText="Loading"
variant="outline"
colorScheme="green"
></Button>
)}
</Center>
)}
</Flex>
</Flex>
</>
) : (
<Center>
<Spinner
mt="50%"
size="lg"
color="blue.500"
thickness="4px"
speed="1.5s"
/>
</Center>
)}
</Flex>
);
};
export default EntriesNavigation;

Wyświetl plik

@ -1,133 +1,140 @@
import React from "react";
import { Flex, Text, Link, Heading } from "@chakra-ui/react";
import CustomIcon from "../components/CustomIcon";
import {
Text,
Link,
Box,
Container,
SimpleGrid,
Stack,
Image as ChakraImage,
useColorModeValue,
VisuallyHidden,
chakra,
} from "@chakra-ui/react";
import RouterLink from "next/link";
const ICONS = [
{
social: "discord",
link: "https://discord.gg/K56VNUQGvA",
},
{ social: "twit", link: "https://twitter.com/moonstreamto" },
];
const SITEMAP_FLEX_PROPS = {
px: 2,
alignItems: "flex-start",
flexGrow: 1,
pb: 4,
color: "white.300",
fontWeight: "600",
direction: "column",
mr: 12,
};
import {
WHITE_LOGO_W_TEXT_URL,
ALL_NAV_PATHES,
FOOTER_COLUMNS,
} from "../core/constants";
import { FaGithub, FaTwitter, FaDiscord } from "react-icons/fa";
import { v4 } from "uuid";
const LINKS_SIZES = {
fontWeight: "300",
fontSize: "lg",
};
const Footer = () => (
<Flex
bg="brand.200"
flexGrow="1"
px="7%"
width="100%"
align="center"
alignItems="self-end"
justify={["center", "center", null, "space-between"]}
direction={["column", "column", "row", null, "row"]}
py="2.5rem"
>
<Flex
p={0}
direction={["column", "row", null, "row"]}
flexGrow="2"
flexWrap="wrap"
maxW="40rem"
const ListHeader = ({ children }) => {
return (
<Text
fontWeight={"500"}
fontSize={"lg"}
mb={2}
borderBottom="1px"
borderColor="blue.700"
textColor="blue.500"
>
<Flex {...SITEMAP_FLEX_PROPS}>
<Heading pb={8} size="md">
About
</Heading>{" "}
<RouterLink passHref href="/team">
<Link {...LINKS_SIZES}>Team</Link>
</RouterLink>
<RouterLink passHref href="/product">
<Link {...LINKS_SIZES}>Product</Link>
</RouterLink>
</Flex>
{children}
</Text>
);
};
<Flex {...SITEMAP_FLEX_PROPS}>
<Heading pb={8} size="md">
News
</Heading>
<RouterLink passHref href="http://blog.moonstream.to">
<Link {...LINKS_SIZES}>Blog</Link>
</RouterLink>
{/* <RouterLink passHref href="/privacy-policy">
<Link {...LINKS_SIZES}>Privacy policy</Link>
</RouterLink> */}
</Flex>
{/* <Flex {...SITEMAP_FLEX_PROPS}>
<Heading pb={8} size="md">
Product
</Heading>
<RouterLink href="/pricing" passHref>
<Link {...LINKS_SIZES}>Pricing</Link>
</RouterLink>
<RouterLink passHref href={"/case-studies/activeloop"}>
<Link {...LINKS_SIZES}>Case studies</Link>
</RouterLink>
</Flex> */}
</Flex>
<Flex
direction="column"
flexGrow="1"
w="100%"
maxW="40rem"
alignItems={["flex-end", "flex-end", null, "flex-end"]}
pr={[0, null, 8]}
const SocialButton = ({ children, label, href }) => {
return (
<chakra.button
bg={useColorModeValue("blackAlpha.100", "whiteAlpha.100")}
rounded={"full"}
w={8}
h={8}
cursor={"pointer"}
as={"a"}
href={href}
display={"inline-flex"}
alignItems={"center"}
justifyContent={"center"}
transition={"background 0.3s ease"}
_hover={{
bg: useColorModeValue("blackAlpha.200", "whiteAlpha.200"),
}}
>
<Text
color="white"
pt={[24, 24, null, 0]}
pb={8}
fontSize="xl"
fontWeight="500"
<VisuallyHidden>{label}</VisuallyHidden>
{children}
</chakra.button>
);
};
const Footer = () => (
<Box
bg={useColorModeValue("blue.900", "gray.900")}
color={useColorModeValue("gray.700", "gray.200")}
>
<Container as={Stack} maxW={"6xl"} py={10}>
<SimpleGrid
templateColumns={{ sm: "1fr 1fr", md: "2fr 1fr 1fr 2fr" }}
spacing={8}
>
All the crypto data you care about{` `}
<span role="img" aria-label="heart">
💙
</span>
</Text>
<Flex px={2} width="100%" maxW="30rem" justifyContent="flex-end">
{ICONS.map((icon, index) => (
<Link
key={`social-footer-icons-${index}`}
display="flex"
mx={2}
mb={2}
borderRadius="13px"
bg="blue.800"
boxSize={["3rem", "4rem", "6rem", null, "6rem"]}
_hover={{ bg: "blue.600" }}
alignItems="center"
justifyContent="center"
href={icon.link}
isExternal
p={4}
>
<CustomIcon icon={icon.social} />
</Link>
))}
</Flex>
<Text pt={24} alignSelf="flex-end" textColor="blue.500">
All rights reserved.2021
</Text>
</Flex>
</Flex>
<Stack spacing={6}>
<Box>
<Link href="/" alignSelf="center">
<ChakraImage
alignSelf="center"
// as={Link}
// to="/"
h="2.5rem"
minW="2.5rem"
src={WHITE_LOGO_W_TEXT_URL}
alt="Go to app root"
/>
</Link>
</Box>
<Text fontSize={"sm"}>© 2021 Moonstream.to All rights reserved</Text>
<Stack direction={"row"} spacing={6}>
<SocialButton
label={"Twitter"}
href={"https://twitter.com/moonstreamto"}
>
<FaTwitter />
</SocialButton>
<SocialButton
label={"Github"}
href={"https://github.com/bugout-dev/moonstream"}
>
<FaGithub />
</SocialButton>
<SocialButton
label={"Discord"}
href={"https://discord.gg/K56VNUQGvA"}
>
<FaDiscord />
</SocialButton>
</Stack>
</Stack>
{Object.values(FOOTER_COLUMNS).map((columnEnum) => {
return (
<Stack align={"flex-start"} key={v4()}>
{ALL_NAV_PATHES.filter(
(navPath) => navPath.footerCategory === columnEnum
).length > 0 && (
<>
<ListHeader>{columnEnum}</ListHeader>
{ALL_NAV_PATHES.filter(
(navPath) => navPath.footerCategory === columnEnum
).map((linkItem) => {
return (
<RouterLink passHref href={linkItem.path} key={v4()}>
<Link {...LINKS_SIZES}>{linkItem.title}</Link>
</RouterLink>
);
})}
</>
)}
</Stack>
);
})}
</SimpleGrid>
</Container>
</Box>
);
export default Footer;

Wyświetl plik

@ -85,18 +85,18 @@ const LandingNavbar = () => {
</Button>
</RouterLink>
)}
{/* {!ui.isLoggedIn && (
{!ui.isLoggedIn && (
<Button
colorScheme="whiteAlpha"
variant="outline"
colorScheme="orange"
variant="solid"
onClick={() => toggleModal("register")}
size="sm"
fontWeight="400"
borderRadius="2xl"
>
Get started
Sign Up
</Button>
)} */}
)}
{!ui.isLoggedIn && (
<Button
color="white"

Wyświetl plik

@ -39,9 +39,21 @@ const NewDashboard = (props) => {
subscriptions: [
{
label: "",
id: null,
abi: false,
subscription_id: null,
isMethods: false,
isEvents: false,
generic: {
transactions: {
in: false,
out: false,
},
value: {
in: false,
out: false,
balance: false,
},
},
},
],
}
@ -99,20 +111,54 @@ const NewDashboard = (props) => {
// borderWidth="1px"
variant="simple"
colorScheme="blue"
justifyContent="center"
// justifyContent="center"
textAlign="center"
borderBottomRadius="xl"
alignItems="baseline"
// alignItems="baseline"
h="auto"
size="sm"
mt={0}
>
<Thead>
<Tr>
<Th>Address</Th>
<Th w="90px">ABI State</Th>
<Th w="90px">Methods</Th>
<Th w="90px">Events</Th>
<Th w="60px"></Th>
<Th textAlign="center">Address</Th>
<Th textAlign="center" colSpan="3">
ABI
</Th>
<Th textAlign="center" colSpan="2">
Transactions
</Th>
<Th textAlign="center" colSpan="3">
Value
</Th>
</Tr>
<Tr>
<Th></Th>
<Th p={1} textAlign="center">
ABI
</Th>
<Th p={1} textAlign="center">
Methods
</Th>
<Th p={1} textAlign="center">
Events
</Th>
<Th p={1} textAlign="center">
In
</Th>
<Th p={1} textAlign="center">
Out
</Th>
<Th p={1} textAlign="center">
In
</Th>
<Th p={1} textAlign="center">
Out
</Th>
<Th p={1} textAlign="center">
Balance
</Th>
</Tr>
</Thead>
<Tbody>
@ -127,7 +173,25 @@ const NewDashboard = (props) => {
<Downshift
onSelect={(selectedItem) => {
const newState = { ...newDashboardForm };
newState.subscriptions[idx] = selectedItem;
newState.subscriptions[idx] = {
label: selectedItem.label,
address: selectedItem.address,
subscription_id: selectedItem.id,
abi: selectedItem.abi,
isMethods: false,
isEvents: false,
generic: {
transactions: {
in: false,
out: false,
},
value: {
in: false,
out: false,
balance: false,
},
},
};
setNewDashboardForm(newState);
}}
// isOpen={showSuggestions}
@ -382,7 +446,7 @@ const NewDashboard = (props) => {
// </AutoComplete>
)}
</Td>
<Td w="90px">
<Td p={1} textAlign="center">
{subscibedItem.abi && subscibedItem.address && (
<CheckCircleIcon color="green" />
)}
@ -404,24 +468,100 @@ const NewDashboard = (props) => {
)}
</Td>
<Td w="60px">
<Td p={1} textAlign="center">
<Checkbox
isDisabled={
!subscibedItem.address || !subscibedItem.abi
}
isDisabled={!subscibedItem.abi}
onChange={() => {
const newState = { ...newDashboardForm };
newState.subscriptions[idx] = {
...newState.subscriptions[idx],
isMethods: !newState.subscriptions[idx].isMethods,
};
setNewDashboardForm(newState);
}}
isChecked={subscibedItem.isMethods}
></Checkbox>
</Td>
<Td w="60px">
<Td p={1} textAlign="center">
<Checkbox
isDisabled={
!subscibedItem.address || !subscibedItem.abi
}
onChange={() => {
const newState = { ...newDashboardForm };
newState.subscriptions[idx] = {
...newState.subscriptions[idx],
isEvents: !newState.subscriptions[idx].isEvents,
};
setNewDashboardForm(newState);
}}
isChecked={subscibedItem.isEvents}
></Checkbox>
</Td>
<Td p={1} textAlign="center">
<Checkbox
isDisabled={!subscibedItem.address}
onChange={() => {
const newState = { ...newDashboardForm };
newState.subscriptions[idx].generic.transactions.in =
!newState.subscriptions[idx].generic.transactions
.in;
setNewDashboardForm(newState);
}}
isChecked={subscibedItem.generic.transactions.in}
></Checkbox>
</Td>
<Td p={1} textAlign="center">
<Checkbox
isDisabled={!subscibedItem.address}
onChange={() => {
const newState = { ...newDashboardForm };
newState.subscriptions[idx].generic.transactions.out =
!newState.subscriptions[idx].generic.transactions
.out;
setNewDashboardForm(newState);
}}
isChecked={subscibedItem.generic.transactions.out}
></Checkbox>
</Td>
<Td p={1} textAlign="center">
<Checkbox
isDisabled={!subscibedItem.address}
onChange={() => {
const newState = { ...newDashboardForm };
newState.subscriptions[idx].generic.value.in =
!newState.subscriptions[idx].generic.value.in;
setNewDashboardForm(newState);
}}
isChecked={subscibedItem.generic.value.in}
></Checkbox>
</Td>
<Td p={1} textAlign="center">
<Checkbox
isDisabled={!subscibedItem.address}
onChange={() => {
const newState = { ...newDashboardForm };
newState.subscriptions[idx].generic.value.out =
!newState.subscriptions[idx].generic.value.out;
setNewDashboardForm(newState);
}}
isChecked={subscibedItem.generic.value.out}
></Checkbox>
</Td>
<Td p={1} textAlign="center">
<Checkbox
isDisabled={!subscibedItem.address}
onChange={() => {
const newState = { ...newDashboardForm };
newState.subscriptions[idx].generic.balance =
!newState.subscriptions[idx].generic.balance;
setNewDashboardForm(newState);
}}
isChecked={subscibedItem.generic.balance}
></Checkbox>
</Td>
<Td>
<Td p={1} textAlign="center">
{idx > 0 && (
<CloseButton
onClick={() => {
@ -456,8 +596,21 @@ const NewDashboard = (props) => {
const newState = { ...newDashboardForm };
newState.subscriptions.push({
label: "",
id: null,
abi: false,
subscription_id: null,
isMethods: false,
isEvents: false,
generic: {
transactions: {
in: false,
out: false,
},
value: {
in: false,
out: false,
balance: false,
},
},
});
setNewDashboardForm(newState);
}}

Wyświetl plik

@ -54,7 +54,6 @@ const _NewSubscription = ({ onClose, setIsLoading, isModal, initialValue }) => {
useEffect(() => {
if (initialValue && initialValue !== "") {
console.log("iv:", initialValue, isAddress(initialValue));
if (isAddress(initialValue)) {
setAddress(initialValue);
} else {
@ -111,11 +110,7 @@ const _NewSubscription = ({ onClose, setIsLoading, isModal, initialValue }) => {
if (!errors) return "";
console.log("selected type", type);
console.log("pickerItems", pickerItems);
const filterFn = (item, inputValue) => {
console.log("filterFN", item.name, inputValue);
return (
!inputValue || item.name.toUpperCase().includes(inputValue.toUpperCase())
);
@ -157,7 +152,6 @@ const _NewSubscription = ({ onClose, setIsLoading, isModal, initialValue }) => {
selectedItem,
getRootProps,
}) => {
console.log("selected item,", selectedItem?.name);
return (
<Box pos="relative" w="100%">
<Box
@ -179,8 +173,6 @@ const _NewSubscription = ({ onClose, setIsLoading, isModal, initialValue }) => {
isTruncated
fontSize="sm"
{...getInputProps()}
// defaultValue={selectedItem.name ?? undefined}
// value={selectedItem.name}
></Input>
<InputRightAddon p={0}>
<Button
@ -189,13 +181,7 @@ const _NewSubscription = ({ onClose, setIsLoading, isModal, initialValue }) => {
m={0}
p={0}
colorScheme="gray"
{...getToggleButtonProps({
// onClick: () =>
// console.log(
// "ref: ",
// downshiftRef.current.clearSelection()
// ),
})}
{...getToggleButtonProps({})}
aria-label={"toggle menu"}
>
&#8595;
@ -203,21 +189,8 @@ const _NewSubscription = ({ onClose, setIsLoading, isModal, initialValue }) => {
</InputRightAddon>
</InputGroup>
</Box>
{/* <Menu
isOpen={isOpen}
// style={menuStyles}
// position="absolute"
colorScheme="blue"
bgColor="gray.300"
inset="unset"
// spacing={2}
// p={2}
> */}
{isOpen ? (
<Stack
// display="flex"
direction="column"
className="menuListTim"
{...getMenuProps()}
@ -290,34 +263,6 @@ const _NewSubscription = ({ onClose, setIsLoading, isModal, initialValue }) => {
</Downshift>
</>
)}
{/* {typesCache.data
.sort((a, b) =>
a?.name > b?.name ? 1 : b?.name > a?.name ? -1 : 0
)
.map((type) => {
const radio = getRadioProps({
value: type.id,
isDisabled:
(initialAddress && initialType) ||
!type.active ||
(isFreeOption && type.id !== "ethereum_blockchain"),
});
return (
<RadioCard
px="8px"
py="4px"
mt="2px"
w="190px"
{...radio}
key={`subscription_type_${type.id}`}
label={type.description}
iconURL={type.icon_url}
>
{type.name.slice(9, type.name.length)}
</RadioCard>
);
})} */}
</Stack>
</Stack>

Wyświetl plik

@ -0,0 +1,52 @@
import React, { useEffect, useState, useRef } from "react";
import { Stack, Container, chakra } from "@chakra-ui/react";
const RangeSelector_ = ({
className,
ranges,
onChange,
initialRange,
size,
}) => {
const [range, setRange] = useState(initialRange ?? ranges[0]);
const isFirstRun = useRef(true);
useEffect(() => {
if (isFirstRun.current) {
isFirstRun.current = false;
} else {
onChange(range);
}
}, [range, onChange]);
return (
<Stack direction="row" className={className} h="min-content">
{ranges.map((item, idx) => {
const isActive = item === range ? true : false;
return (
<Container
key={`date-range-${className}-${idx}`}
bgColor={isActive ? "secondary.900" : "primary.50"}
color={!isActive ? "primary.900" : "primary.50"}
boxShadow="sm"
borderRadius="md"
fontSize={size}
fontWeight="600"
onClick={() => setRange(item)}
_hover={{
bgColor: isActive ? "secondary.900" : "secondary.50",
}}
cursor="pointer"
py="2px"
>
{item}
</Container>
);
})}
</Stack>
);
};
const RangeSelector = chakra(RangeSelector_);
export default RangeSelector;

Wyświetl plik

@ -0,0 +1,323 @@
import React, { useRef } from "react";
import { Box, Button, useBreakpointValue } from "@chakra-ui/react";
import DragOnGrid from "./DragOnGrid";
import Xarrow from "react-xarrows";
const SchematicPlayground = () => {
const gridCellSize = useBreakpointValue({
base: 24,
sm: 32,
md: 64,
lg: 64,
xl: 64,
"2xl": 64,
});
const ref1 = useRef(null);
const ref2 = useRef(null);
const ref3 = useRef(null);
const ref4 = useRef(null);
const ref5 = useRef(null);
const ref6 = useRef(null);
const ref7 = useRef(null);
const ref8 = useRef(null);
if (!gridCellSize) return "";
return (
<>
<Box
h={`${(gridCellSize * 5 + 1).toString()}` + `px`}
// h={`301px`}
w="100%"
bgColor="white"
bgSize={`${(gridCellSize / 6).toString() + "px"} ${
(gridCellSize / 6).toString() + "px"
}, ${gridCellSize.toString() + "px"} ${gridCellSize.toString() + "px"}`}
bgImage={`linear-gradient(to bottom, transparent ${
(gridCellSize / 10).toString() + "px"
}, white ${(gridCellSize / 10).toString() + "px"}),
linear-gradient(to right, #dee3ea 1px, transparent 1px),
linear-gradient(to right, transparent ${
(gridCellSize / 10).toString() + "px"
}, white ${(gridCellSize / 10).toString() + "px"}),
linear-gradient(to bottom, #dee3ea 1px, transparent 1px)`}
maxW={`${(gridCellSize * 11 + 1).toString()}` + `px`}
placeSelf="center"
>
<DragOnGrid
ref={ref4}
gridStep={gridCellSize}
defaultPosition={{ x: 5, y: 2 }}
>
<Button
m={0}
p={0}
borderRadius="sm"
ref={ref4}
className="handle"
minW={`${gridCellSize.toString}` + `px`}
fontSize={(gridCellSize / 4).toString() + `px`}
boxSize={`${gridCellSize.toString()}` + "px"}
borderStyle="inset"
bg="green.900"
color="white"
textAlign="center"
zIndex={10}
>
MSTR
</Button>
</DragOnGrid>
<DragOnGrid
ref={ref2}
gridStep={gridCellSize}
defaultPosition={{ x: 4, y: 0 }}
>
<Button
m={0}
ref={ref2}
p={0}
borderRadius="sm"
className="handle"
minW={`${gridCellSize.toString}` + `px`}
minH={`${gridCellSize.toString}` + `px`}
fontSize={(gridCellSize / 4).toString() + `px`}
boxSize={`${gridCellSize.toString()}` + "px"}
bg="blue.900"
color="white"
textAlign="center"
zIndex={10}
>
DEX
</Button>
</DragOnGrid>
<DragOnGrid
ref={ref3}
gridStep={gridCellSize}
defaultPosition={{ x: 3, y: 4 }}
>
<Button
m={0}
ref={ref3}
p={0}
borderRadius="sm"
className="handle"
minW={`${gridCellSize.toString}` + `px`}
fontSize={(gridCellSize / 4).toString() + `px`}
boxSize={`${gridCellSize.toString()}` + "px"}
bg="orange.900"
color="white"
textAlign="center"
zIndex={10}
>
NFT
</Button>
</DragOnGrid>
<DragOnGrid
ref={ref1}
gridStep={gridCellSize}
defaultPosition={{ x: -2, y: 1 }}
>
<Button
m={0}
ref={ref1}
p={0}
borderRadius="sm"
className="handle"
minW={`${gridCellSize.toString}` + `px`}
fontSize={(gridCellSize / 4).toString() + `px`}
boxSize={`${gridCellSize.toString()}` + "px"}
bg="red.900"
color="white"
textAlign="center"
zIndex={10}
>
ERC20
</Button>
</DragOnGrid>
<DragOnGrid
ref={ref4}
gridStep={gridCellSize}
defaultPosition={{ x: -3, y: 4 }}
>
<Button
m={0}
ref={ref5}
p={0}
borderRadius="sm"
className="handle"
minW={`${gridCellSize.toString}` + `px`}
fontSize={(gridCellSize / 4).toString() + `px`}
boxSize={`${gridCellSize.toString()}` + "px"}
bg="red.900"
color="white"
textAlign="center"
zIndex={10}
>
EIP1155
</Button>
</DragOnGrid>
<DragOnGrid
ref={ref4}
gridStep={gridCellSize}
defaultPosition={{ x: 3, y: 3 }}
>
<Button
m={0}
ref={ref6}
p={0}
borderRadius="sm"
className="handle"
minW={`${gridCellSize.toString}` + `px`}
fontSize={(gridCellSize / 4).toString() + `px`}
boxSize={`${gridCellSize.toString()}` + "px"}
bg="blue.900"
color="white"
textAlign="center"
zIndex={10}
>
ERC721
</Button>
</DragOnGrid>
<DragOnGrid
ref={ref4}
gridStep={gridCellSize}
defaultPosition={{ x: 4, y: 4 }}
>
<Button
m={0}
ref={ref7}
p={0}
borderRadius="sm"
className="handle"
minW={`${gridCellSize.toString}` + `px`}
fontSize={(gridCellSize / 4).toString() + `px`}
boxSize={`${gridCellSize.toString()}` + "px"}
bg="green.900"
color="white"
textAlign="center"
zIndex={10}
>
DAO
</Button>
</DragOnGrid>
<DragOnGrid
ref={ref4}
gridStep={gridCellSize}
defaultPosition={{ x: 2, y: 0 }}
>
<Button
m={0}
ref={ref8}
p={0}
borderRadius="sm"
className="handle"
minW={`${gridCellSize.toString}` + `px`}
fontSize={(gridCellSize / 4).toString() + `px`}
boxSize={`${gridCellSize.toString()}` + "px"}
bg="orange.900"
color="white"
textAlign="center"
zIndex={10}
>
Oracle
</Button>
</DragOnGrid>
</Box>
<Xarrow
dashness={{
strokeLen: 10,
nonStrokeLen: 15,
animation: 1 * 1,
}}
color="#920050"
path="grid"
gridBreak={(gridCellSize * 0.5).toString() + "px"}
// startAnchor={"top"}
showHead={false}
start={ref3} //can be react ref
end={ref4} //or an id
/>
<Xarrow
dashness={{
strokeLen: 10,
nonStrokeLen: 15,
animation: 1 * 1,
}}
color="#113350"
path="grid"
gridBreak={(gridCellSize * 0.5).toString() + "px"}
showHead={false}
start={ref2} //can be react ref
end={ref4} //or an id
/>
<Xarrow
dashness={{
strokeLen: 10,
nonStrokeLen: 15,
animation: 1 * 1,
}}
color="#92D0F0"
gridBreak={(gridCellSize * 0.5).toString() + "px"}
path="grid"
// startAnchor={"top"}
showHead={false}
start={ref1} //can be react ref
end={ref4} //or an id
/>
<Xarrow
dashness={{
strokeLen: 10,
nonStrokeLen: 15,
animation: 1 * 1,
}}
color="#92D0F0"
gridBreak={(gridCellSize * 0.5).toString() + "px"}
path="grid"
// startAnchor={"top"}
showHead={false}
start={ref5} //can be react ref
end={ref4} //or an id
/>
<Xarrow
dashness={{
strokeLen: 10,
nonStrokeLen: 15,
animation: 1 * 1,
}}
color="#92D0F0"
gridBreak={(gridCellSize * 0.5).toString() + "px"}
path="grid"
// startAnchor={"top"}
showHead={false}
start={ref6} //can be react ref
end={ref4} //or an id
/>
<Xarrow
dashness={{
strokeLen: 10,
nonStrokeLen: 15,
animation: 1 * 1,
}}
color="#92D0F0"
gridBreak={(gridCellSize * 0.5).toString() + "px"}
path="grid"
showHead={false}
start={ref7} //can be react ref
end={ref4} //or an id
/>
<Xarrow
dashness={{
strokeLen: 10,
nonStrokeLen: 15,
animation: 1 * 1,
}}
color="#92D0F0"
gridBreak={(gridCellSize * 0.5).toString() + "px"}
path="grid"
showHead={false}
start={ref8} //can be react ref
end={ref4} //or an id
/>
</>
);
};
export default SchematicPlayground;

Wyświetl plik

@ -2,10 +2,12 @@ import { Flex, Box } from "@chakra-ui/react";
import React, { useEffect, useRef, useState } from "react";
import { useRouter } from "../core/hooks";
import mixpanel from "mixpanel-browser";
import { useXarrow, Xwrapper } from "react-xarrows";
const Scrollable = (props) => {
const scrollerRef = useRef();
const router = useRouter();
const [path, setPath] = useState();
const updateXarrow = useXarrow();
const [scrollDepth, setScrollDepth] = useState(0);
@ -17,10 +19,11 @@ const Scrollable = (props) => {
};
const handleScroll = (e) => {
updateXarrow();
const currentScroll = Math.ceil(getScrollPrecent(e) / 10);
if (currentScroll > scrollDepth) {
setScrollDepth(currentScroll);
mixpanel.get_distinct_id() &&
mixpanel?.get_distinct_id() &&
mixpanel.people.increment({
[`Scroll depth at: ${router.nextRouter.pathname}`]: currentScroll,
});
@ -48,15 +51,17 @@ const Scrollable = (props) => {
overflowY="hidden"
maxH="100%"
>
<Box
className="Scrollable"
direction="column"
ref={scrollerRef}
overflowY="scroll"
onScroll={(e) => handleScroll(e)}
>
{props.children}
</Box>
<Xwrapper>
<Box
className="Scrollable"
direction="column"
ref={scrollerRef}
overflowY="scroll"
onScroll={(e) => handleScroll(e)}
>
{props.children}
</Box>
</Xwrapper>
</Flex>
);
};

Wyświetl plik

@ -8,7 +8,14 @@ import {
} from "react-pro-sidebar";
import { useContext } from "react";
import RouterLink from "next/link";
import { Flex, Image, IconButton, Divider } from "@chakra-ui/react";
import {
Flex,
Image,
IconButton,
Divider,
Text,
Button,
} from "@chakra-ui/react";
import UIContext from "../core/providers/UIProvider/context";
import React from "react";
import {
@ -17,13 +24,20 @@ import {
ArrowRightIcon,
LockIcon,
} from "@chakra-ui/icons";
import { MdSettings } from "react-icons/md";
import { HiAcademicCap } from "react-icons/hi";
import { WHITE_LOGO_W_TEXT_URL } from "../core/constants";
import { MODAL_TYPES } from "../core/providers/OverlayProvider/constants";
import { MdSettings, MdDashboard } from "react-icons/md";
import { WHITE_LOGO_W_TEXT_URL, ALL_NAV_PATHES } from "../core/constants";
import { v4 } from "uuid";
import useDashboard from "../core/hooks/useDashboard";
import {
DRAWER_TYPES,
MODAL_TYPES,
} from "../core/providers/OverlayProvider/constants";
import OverlayContext from "../core/providers/OverlayProvider/context";
const Sidebar = () => {
const ui = useContext(UIContext);
const { dashboardsListCache } = useDashboard();
const overlay = useContext(OverlayContext);
return (
<ProSidebar
width="240px"
@ -55,64 +69,103 @@ const Sidebar = () => {
: ui.setSidebarCollapsed(!ui.sidebarCollapsed);
}}
/>
<Image
// h="full"
// maxH="100%"
maxW="120px"
py="0.75rem"
pl={5}
src={WHITE_LOGO_W_TEXT_URL}
alt="bugout.dev"
/>
<RouterLink href="/" passHref>
<Image
// h="full"
// maxH="100%"
maxW="120px"
py="0.75rem"
pl={5}
src={WHITE_LOGO_W_TEXT_URL}
alt="Moonstream To"
/>
</RouterLink>
</Flex>
</SidebarHeader>
{ui.isLoggedIn && (
<SidebarContent>
<Menu iconShape="square"></Menu>
<Menu iconShape="square"></Menu>
{ui.isMobileView && (
<Menu iconShape="square">
<MenuItem icon={<HiAcademicCap />}>
<RouterLink href="/welcome">
Learn how to use Moonstream
</RouterLink>
<SidebarContent>
<Divider borderColor="blue.600" />
<Menu iconShape="square">
{!ui.isLoggedIn && (
<>
<MenuItem
onClick={() => {
ui.toggleModal("register");
ui.setSidebarToggled(false);
}}
>
Sign up
</MenuItem>
</Menu>
<MenuItem
onClick={() => {
ui.toggleModal({ type: MODAL_TYPES.LOGIN });
ui.setSidebarToggled(false);
}}
>
Login
</MenuItem>
{ui.isMobileView &&
ALL_NAV_PATHES.map((pathToLink) => {
return (
<MenuItem key={v4()}>
<RouterLink href={pathToLink.path}>
{pathToLink.title}
</RouterLink>
</MenuItem>
);
})}
</>
)}
</SidebarContent>
)}
{!ui.isLoggedIn && (
<SidebarContent>
{/* <Menu iconShape="square">
<MenuItem
onClick={() => {
ui.toggleModal("register");
ui.setSidebarToggled(false);
}}
>
Sign up
</MenuItem>
</Menu> */}
<Menu iconShape="square">
<MenuItem
onClick={() => {
ui.toggleModal({ type: MODAL_TYPES.LOGIN });
ui.setSidebarToggled(false);
}}
>
Login
</MenuItem>
<MenuItem>
{" "}
<RouterLink href="/product">Product </RouterLink>
</MenuItem>
<MenuItem>
{" "}
<RouterLink href="/team">Team </RouterLink>
</MenuItem>
</Menu>
</SidebarContent>
)}
{ui.isLoggedIn && (
<>
<Text
textColor="gray.300"
size="sm"
justifyContent="center"
fontWeight="600"
pl={8}
pt={3}
>
Dashboards
</Text>
<Menu iconShape="square">
<>
{dashboardsListCache.data &&
dashboardsListCache.data.data.resources.map((dashboard) => {
return (
<MenuItem icon={<MdDashboard />} key={v4()}>
<RouterLink href={`/dashboard/${dashboard?.id}`}>
{dashboard.resource_data.name}
</RouterLink>
</MenuItem>
);
})}
</>
<MenuItem>
<Button
variant="solid"
colorScheme="orange"
size="sm"
onClick={() =>
overlay.toggleDrawer(DRAWER_TYPES.NEW_DASHBOARD)
}
// w="100%"
// borderRadius={0}
>
New dashboard
</Button>
</MenuItem>
</Menu>
</>
)}
<Divider
colorScheme="blue"
bgColor="gray.300"
color="blue.700"
borderColor="blue.700"
/>
</Menu>
</SidebarContent>
<SidebarFooter style={{ paddingBottom: "3rem" }}>
<Divider color="gray.300" w="100%" />
@ -122,9 +175,17 @@ const Sidebar = () => {
<RouterLink href="/account/tokens">API Tokens</RouterLink>
</MenuItem>
<MenuItem icon={<MdSettings />}>
{" "}
<RouterLink href="/subscriptions">Subscriptions </RouterLink>
</MenuItem>
<Divider />
<Text
pt={4}
fontSize={"sm"}
textColor="gray.700"
textAlign="center"
>
© 2021 Moonstream.to
</Text>
</Menu>
)}
</SidebarFooter>

Wyświetl plik

@ -9,7 +9,6 @@ import {
InputGroup,
Button,
Input,
Link,
InputRightElement,
} from "@chakra-ui/react";
import CustomIcon from "./CustomIcon";
@ -91,19 +90,15 @@ const SignIn = ({ toggleModal }) => {
<Box height="1px" width="100%" background="#eaebf8" mb="1.875rem" />
</Text>
<Text textAlign="center" fontSize="md" color="gray.1200">
{/* Don`t have an account?{" "} */}
We are in early access. If you would like to use Moonstream,{" "}
<Link href={"https://discord.gg/V3tWaP36"} color="orange.900">
contact us on Discord.
</Link>
{/* <Box
Don`t have an account?{" "}
<Box
cursor="pointer"
color="blue.800"
as="span"
onClick={() => toggleModal("register")}
>
Register
</Box> */}
</Box>
</Text>
</>
);

Wyświetl plik

@ -127,12 +127,7 @@ const SplitWithImage = ({
)}
<Stack spacing={[2, 4]} justifyContent="center">
{badge && (
<Stack
direction="row"
placeContent={
mirror && !ui.isMobileView ? "flex-end" : "flex-start"
}
>
<Stack direction="row" placeContent={"flex-start"}>
<Text
id={`MoonBadge ${elementName}`}
textTransform={"uppercase"}

Wyświetl plik

@ -69,7 +69,7 @@ const List = ({ data, revoke, isLoading, update, filter }) => {
const filteredTokens = sortedTokens.filter((item) => {
if (filter === null || filter === undefined || filter === "") {
return true;
} else return item.note.includes(filter);
} else return item.note?.includes(filter);
});
setStateData({ ...data, token: [...filteredTokens] });

Wyświetl plik

@ -0,0 +1,35 @@
import { React } from "react";
import { Flex, Image, Link } from "@chakra-ui/react";
const TrustedBadge = ({ name, caseURL, ImgURL }) => {
return (
<Flex
m={1}
justifyContent="center"
alignItems="center"
alignSelf="center"
wrap="nowrap"
p={8}
direction="column"
>
<Image
sx={{ filter: "grayscale(100%)" }}
h={["2.25rem", null, "3rem", "3rem", "4rem", "6rem"]}
src={ImgURL}
alt={name}
></Image>
{caseURL && (
// <RouterLink href={caseURL} passHref scroll={true}>
<Link
fontSize={["sm", null, "md", "lg"]}
textColor="orange.900"
href={caseURL}
>
{`Read more >`}
</Link>
// </RouterLink>
)}
</Flex>
);
};
export default TrustedBadge;

Wyświetl plik

@ -1,8 +1,6 @@
import React, { useContext, useEffect, useState } from "react";
import { Flex, ButtonGroup, Button, useToast, Spinner } from "@chakra-ui/react";
import { useSubscriptions } from "../core/hooks";
// import hljs from "highlight.js";
// import ReactQuill from "react-quill"; // ES6
import AceEditor from "react-ace";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/mode-json";
@ -21,7 +19,6 @@ const ABIUPLoad = (props) => {
try {
return JSON.parse(raw);
} catch (err) {
console.log("e", err.message);
if (!toast.isActive("upload_json_fails")) {
toast({
id: "upload_json_fails",
@ -47,7 +44,7 @@ const ABIUPLoad = (props) => {
status: "success",
variant: "subtle",
});
}, [updateSubscription.isSuccess, toggleModal]);
}, [updateSubscription.isSuccess, toggleModal, toast]);
useEffect(() => {
if (json === false) {
@ -62,7 +59,6 @@ const ABIUPLoad = (props) => {
};
const handleSubmit = () => {
if (json) {
console.log("id:", props.id);
updateSubscription.mutate({
id: props.id,
abi: JSON.stringify(json),

Wyświetl plik

@ -6,23 +6,51 @@ export const BUGOUT_ENDPOINTS = {
};
export const DEFAULT_METATAGS = {
title: "Moonstream.to: All your crypto data in one stream",
title: "Moonstream: Open source blockchain analytics",
description:
"From the Ethereum transaction pool to Elon Musks latest tweets get all the crypto data you care about in one stream.",
"Product analytics for Web3. Moonstream helps you understand exactly how people are using your smart contracts",
keywords:
"blockchain, crypto, data, trading, smart contracts, ethereum, solana, transactions, defi, finance, decentralized",
"analytics, blockchain analytics, protocol, protocols, blockchain, crypto, data, trading, smart contracts, web3, smart contract, ethereum, solana, polygon, matic, transactions, defi, finance, decentralized, mempool, NFT, NFTs, DAO, DAOs, DEX, DEXes, DEXs, cryptocurrency, cryptocurrencies, bitcoin",
url: "https://www.moonstream.to",
image: `https://s3.amazonaws.com/static.simiotics.com/moonstream/assets/crypto+traders.png`,
};
export const FOOTER_COLUMNS = {
NEWS: "News",
COMPANY: "Company",
PRODUCT: "Product",
};
export const ALL_NAV_PATHES = [
{
title: "Product",
path: "/product",
footerCategory: FOOTER_COLUMNS.PRODUCT,
},
{
title: "Team",
path: "/team",
footerCategory: FOOTER_COLUMNS.COMPANY,
},
{
title: "Docs",
path: "/docs",
footerCategory: FOOTER_COLUMNS.PRODUCT,
},
{
title: "Whitepapers",
path: "/whitepapers",
footerCategory: FOOTER_COLUMNS.PRODUCT,
},
{
title: "Blog",
path: "https://blog.moonstream.to",
footerCategory: FOOTER_COLUMNS.NEWS,
},
{
title: "Status",
path: "/status",
footerCategory: FOOTER_COLUMNS.PRODUCT,
},
];
@ -36,4 +64,10 @@ export const USER_NAV_PATHES = [
export const PAGE_SIZE = 20;
export const AWS_ASSETS_PATH = `https://s3.amazonaws.com/static.simiotics.com/moonstream/assets`;
export const WHITE_LOGO_W_TEXT_URL = `https://s3.amazonaws.com/static.simiotics.com/moonstream/assets/moon-logo%2Btext-white.svg`;
export const WHITE_LOGO_W_TEXT_URL = `https://s3.amazonaws.com/static.simiotics.com/moonstream/assets/moon-logo%2Btext-white.png`;
export const TIME_RANGE_SECONDS = {
day: 86400,
week: 86400 * 7,
month: 86400 * 28,
};

Wyświetl plik

@ -0,0 +1,72 @@
import { useMutation, useQuery } from "react-query";
import { useToast } from ".";
import { queryCacheProps } from "./hookCommon";
import { DashboardService } from "../services";
import { useContext } from "react";
import UserContext from "../providers/UserProvider/context";
const useDashboard = (dashboardId) => {
const toast = useToast();
const { user } = useContext(UserContext);
const dashboardsListCache = useQuery(
["dashboards-list"],
DashboardService.getDashboardsList,
{
...queryCacheProps,
onError: (error) => {
toast(error, "error");
},
enabled: !!user,
}
);
const createDashboard = useMutation(DashboardService.createDashboard, {
onSuccess: () => {
toast("Created new dashboard", "success");
},
onError: (error) => {
toast(error.error, "error", "Fail");
},
onSettled: () => {
dashboardsListCache.refetch();
},
});
const deleteDashboard = useMutation(
() => DashboardService.deleteDashboard(dashboardId),
{
onSuccess: () => {
toast("Deleted dashboard", "success");
},
onError: (error) => {
toast(error.error, "error", "Fail");
},
onSettled: () => {
dashboardsListCache.refetch();
},
}
);
console.log("dashboardId in hook:", dashboardId, !!user && !!dashboardId);
const dashboardCache = useQuery(
["dashboards", { dashboardId }],
() => DashboardService.getDashboard(dashboardId),
{
...queryCacheProps,
onError: (error) => {
toast(error, "error");
},
enabled: !!user && !!dashboardId,
}
);
return {
createDashboard,
dashboardsListCache,
dashboardCache,
deleteDashboard,
};
};
export default useDashboard;

Wyświetl plik

@ -0,0 +1,26 @@
import { useState, useEffect } from "react";
const useWindowSize = () => {
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
// Add event listener
window.addEventListener("resize", handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowSize;
};
export default useWindowSize;

Wyświetl plik

@ -1,4 +1,10 @@
import React, { useState, useLayoutEffect, useContext, Suspense } from "react";
import React, {
useState,
useLayoutEffect,
useContext,
Suspense,
useEffect,
} from "react";
import OverlayContext from "./context";
import { MODAL_TYPES, DRAWER_TYPES } from "./constants";
import {
@ -28,6 +34,7 @@ import {
} from "@chakra-ui/react";
import UserContext from "../UserProvider/context";
import UIContext from "../UIProvider/context";
import useDashboard from "../../hooks/useDashboard";
const ForgotPassword = React.lazy(() =>
import("../../../components/ForgotPassword")
);
@ -43,6 +50,7 @@ const NewSubscription = React.lazy(() =>
const UploadABI = React.lazy(() => import("../../../components/UploadABI"));
const OverlayProvider = ({ children }) => {
const { createDashboard } = useDashboard();
const ui = useContext(UIContext);
const { user } = useContext(UserContext);
const [modal, toggleModal] = useState({
@ -107,9 +115,19 @@ const OverlayProvider = ({ children }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ui.isAppView, ui.isAppReady, user, ui.isLoggingOut, modal.type]);
const finishNewDashboard = () => {
toggleDrawer(DRAWER_TYPES.OFF);
window.sessionStorage.removeItem("new_dashboard");
};
useEffect(() => {
if (createDashboard.isSuccess) {
finishNewDashboard();
}
}, [createDashboard.isSuccess]);
return (
<OverlayContext.Provider
value={{ modal, toggleModal, drawer, toggleDrawer }}
value={{ modal, toggleModal, drawer, toggleDrawer, toggleAlert }}
>
<AlertDialog
isOpen={alertDisclosure.isOpen}
@ -227,15 +245,45 @@ const OverlayProvider = ({ children }) => {
<Button
variant="outline"
mr={3}
onClick={() => toggleAlert(() => toggleDrawer(DRAWER_TYPES.OFF))}
onClick={() => toggleAlert(() => finishNewDashboard())}
>
Cancel
</Button>
<Button
colorScheme="blue"
isLoading={createDashboard.isLoading}
onClick={() => {
//TODO: @Peersky Implement logic part
console.log("submit clicked");
const dashboardState = JSON.parse(
sessionStorage.getItem("new_dashboard")
);
createDashboard.mutate({
name: dashboardState.name,
subscriptions: dashboardState.subscriptions.map(
(pickedSubscription) => {
const retval = {
subscription_id: pickedSubscription.subscription_id,
generic: [],
all_methods: !!pickedSubscription.isMethods,
all_events: !!pickedSubscription.isEvents,
};
pickedSubscription.generic.transactions.in &&
retval.generic.push({ name: "transactions_in" });
pickedSubscription.generic.transactions.out &&
retval.generic.push({ name: "transactions_out" });
pickedSubscription.generic.value.in &&
retval.generic.push({ name: "value_in" });
pickedSubscription.generic.value.out &&
retval.generic.push({ name: "value_out" });
pickedSubscription.generic.balance &&
retval.generic.push({ name: "balance" });
retval["methods"] = [];
retval["events"] = [];
return retval;
}
),
});
}}
>
Submit

Wyświetl plik

@ -0,0 +1,40 @@
import { http } from "../utils";
const API_URL = process.env.NEXT_PUBLIC_MOONSTREAM_API_URL;
export const createDashboard = (data) => {
return http({
method: "POST",
url: `${API_URL}/dashboards`,
data,
});
};
export const getDashboardsList = () => {
return http({
method: "GET",
url: `${API_URL}/dashboards`,
});
};
export const deleteDashboard = (id) => {
console.log("delete:", id);
return http({
method: "DELETE",
url: `${API_URL}/dashboards/${id}/`,
});
};
export const getDashboard = (dashboardId) => {
console.log("get dashboard");
// const dashboardId = query.queryKey[2].dashboardId;
// console.assert(
// dashboardId,
// "No dashboard ID found in query object that was passed to service"
// );
console.log("service", dashboardId);
return http({
method: "GET",
url: `${API_URL}/dashboards/${dashboardId}/data_links`,
});
};

Wyświetl plik

@ -10,6 +10,7 @@ import * as StatusService from "./status.service";
import * as SubscriptionsService from "./subscriptions.service";
import * as StreamService from "./stream.service";
import * as TxInfoService from "./txinfo.service";
import * as DashboardService from "./dashboard.service";
export {
AuthService,
JournalService,
@ -23,4 +24,5 @@ export {
SubscriptionsService,
StreamService,
TxInfoService,
DashboardService,
};

Wyświetl plik

@ -1,5 +1,4 @@
import { http } from "../utils";
// import axios from "axios";
const API = process.env.NEXT_PUBLIC_MOONSTREAM_API_URL;

Wyświetl plik

@ -1,7 +1,7 @@
import enableMockupRequests from "./mockupRequests";
let axios = require("axios");
enableMockupRequests(axios);
process.env.NODE_ENV !== "production" && enableMockupRequests(axios);
const http = (config) => {
const token = localStorage.getItem("MOONSTREAM_ACCESS_TOKEN");

Wyświetl plik

@ -27,7 +27,7 @@ const randDate = () => {
new Date(+new Date() - Math.floor(Math.random() * 10000000000))
).format("MM/DD/YYYY");
};
export let MockSubscriptions = [
let MockSubscriptions = [
{
label: "Bobs wallet",
address: `0x` + makeid(24),
@ -234,5 +234,28 @@ const enableMockupRequests = (axiosInstance) => {
offset: 0,
},
});
mock.onGet(`${MOCK_API}/subscriptions/`).reply(200, {
data: {
is_free_subscription_availible: true,
subscriptions: MockSubscriptions,
},
});
mock.onPost(`${MOCK_API}/subscriptions/`).reply((config) => {
const params = config.data; // FormData of {name: ..., file: ...}
const id = params.get("id");
const label = params.get("label");
const address = params.get("address");
const subscription_type = params.get("subscription_type");
return new Promise(function (resolve) {
setTimeout(function () {
const data = { id, label, address, subscription_type };
MockSubscriptions.push({ ...data });
resolve([200, { message: "OK", result: true }]);
}, 1000);
});
});
};
export default enableMockupRequests;

Wyświetl plik

@ -1,82 +0,0 @@
import React from "react";
import { useBreakpointValue, Flex } from "@chakra-ui/react";
import SplitPane, { Pane } from "react-split-pane";
import { getLayout as getSiteLayout } from "./AppLayout";
import EntriesNavigation from "../components/EntriesNavigation";
import { useContext } from "react";
import UIContext from "../core/providers/UIProvider/context";
const EntriesLayout = (props) => {
const ui = useContext(UIContext);
const defaultWidth = useBreakpointValue({
base: "14rem",
sm: "16rem",
md: "18rem",
lg: "20rem",
xl: "22rem",
"2xl": "24rem",
});
return (
<>
<Flex id="Entries" flexGrow={1} maxW="100%">
<SplitPane
allowResize={false}
split="vertical"
defaultSize={defaultWidth}
primary="first"
minSize={defaultWidth}
pane1Style={
ui.entriesViewMode === "list"
? { transition: "1s", width: "100%" }
: ui.entriesViewMode === "entry"
? { transition: "1s", width: "0%" }
: {
overflowX: "hidden",
height: "100%",
width: ui.isMobileView ? "100%" : "55%",
}
}
pane2Style={
ui.entriesViewMode === "entry"
? { transition: "1s", width: "0%" }
: ui.entriesViewMode === "list"
? {
transition: "1s",
width: "100%",
}
: { overflowX: "hidden", height: "100%" }
}
style={{
position: "relative",
height: "100%",
flexBasis: "100px",
overflowX: "hidden",
}}
>
<Pane
className="EntriesNavigation"
style={{
height: "100%",
}}
>
<EntriesNavigation />
</Pane>
<Pane
className="EntryScreen"
style={{
height: "100%",
}}
>
{props.children}
</Pane>
</SplitPane>
</Flex>
</>
);
};
export const getLayout = (page) =>
getSiteLayout(<EntriesLayout>{page}</EntriesLayout>);
export default EntriesLayout;

Wyświetl plik

@ -0,0 +1,9 @@
import React from "react";
const EntryPointLayout = (props) => {
return props.children;
};
export const getLayout = (page) => <EntryPointLayout>{page}</EntryPointLayout>;
export default EntryPointLayout;

Wyświetl plik

@ -0,0 +1,137 @@
import React, { useContext, useState, useEffect, useLayoutEffect } from "react";
import { Flex, useMediaQuery, Stack } from "@chakra-ui/react";
import UIContext from "../core/providers/UIProvider/context";
import { DEFAULT_METATAGS, AWS_ASSETS_PATH } from "../core/constants";
import { getLayout as getSiteLayout } from "./index";
const assets = {
background720: `${AWS_ASSETS_PATH}/blog-background-720x405.png`,
background1920: `${AWS_ASSETS_PATH}/blog-background-720x405.png`,
background2880: `${AWS_ASSETS_PATH}/blog-background-720x405.png`,
background3840: `${AWS_ASSETS_PATH}/blog-background-720x405.png`,
};
const InfoPageLayout = ({ children }) => {
const ui = useContext(UIContext);
const [background, setBackground] = useState("background720");
const [backgroundLoaded720, setBackgroundLoaded720] = useState(false);
const [backgroundLoaded1920, setBackgroundLoaded1920] = useState(false);
const [backgroundLoaded2880, setBackgroundLoaded2880] = useState(false);
const [backgroundLoaded3840, setBackgroundLoaded3840] = useState(false);
const [
isLargerThan720px,
isLargerThan1920px,
isLargerThan2880px,
isLargerThan3840px,
] = useMediaQuery([
"(min-width: 720px)",
"(min-width: 1920px)",
"(min-width: 2880px)",
"(min-width: 3840px)",
]);
useEffect(() => {
assets["background720"] = `${AWS_ASSETS_PATH}/blog-background-720x405.png`;
assets[
"background1920"
] = `${AWS_ASSETS_PATH}/blog-background-1920x1080.png`;
assets[
"background2880"
] = `${AWS_ASSETS_PATH}/blog-background-2880x1620.png`;
assets[
"background3840"
] = `${AWS_ASSETS_PATH}/blog-background-3840x2160.png`;
}, []);
useLayoutEffect(() => {
if (backgroundLoaded3840) {
setBackground("background3840");
} else if (backgroundLoaded2880) {
setBackground("background2880");
} else if (backgroundLoaded1920) {
setBackground("background1920");
} else {
setBackground("background720");
}
}, [
isLargerThan720px,
isLargerThan1920px,
isLargerThan2880px,
isLargerThan3840px,
backgroundLoaded720,
backgroundLoaded1920,
backgroundLoaded2880,
backgroundLoaded3840,
]);
useLayoutEffect(() => {
const imageLoader720 = new Image();
imageLoader720.src = `${AWS_ASSETS_PATH}/blog-background-720x405.png`;
imageLoader720.onload = () => {
setBackgroundLoaded720(true);
};
}, []);
useLayoutEffect(() => {
const imageLoader1920 = new Image();
imageLoader1920.src = `${AWS_ASSETS_PATH}/blog-background-1920x1080.png`;
imageLoader1920.onload = () => {
setBackgroundLoaded1920(true);
};
}, []);
useLayoutEffect(() => {
const imageLoader2880 = new Image();
imageLoader2880.src = `${AWS_ASSETS_PATH}/blog-background-2880x1620.png`;
imageLoader2880.onload = () => {
setBackgroundLoaded2880(true);
};
}, []);
useLayoutEffect(() => {
const imageLoader3840 = new Image();
imageLoader3840.src = `${AWS_ASSETS_PATH}/blog-background-3840x2160.png`;
imageLoader3840.onload = () => {
setBackgroundLoaded3840(true);
};
}, []);
const margin = ui.isMobileView ? "6%" : "22%";
return (
<Flex
bgPos="bottom"
bgColor="transparent"
bgSize="cover"
backgroundImage={`url(${assets[`${background}`]})`}
minH="100vh"
direction="column"
alignItems="center"
w="100%"
>
<Stack mx={margin} my={[4, 6, 12]} maxW="1700px" textAlign="justify">
{children}
</Stack>
</Flex>
);
};
export const getLayout = (page) =>
getSiteLayout(<InfoPageLayout>{page}</InfoPageLayout>);
export const getLayoutProps = () => {
const assetPreload = Object.keys(assets).map((key) => {
return {
rel: "preload",
href: assets[key],
as: "image",
};
});
const preconnects = [{ rel: "preconnect", href: "https://s3.amazonaws.com" }];
const preloads = assetPreload.concat(preconnects);
return {
props: { metaTags: { ...DEFAULT_METATAGS }, preloads },
};
};
export default InfoPageLayout;

Wyświetl plik

@ -1,17 +0,0 @@
import React from "react";
import { Scrollable, Footer } from "../components";
import { getLayout as getSiteLayout } from "./index";
const DefaultLayout = (props) => {
return (
<Scrollable bgImg={""}>
{props.children}
<Footer />
</Scrollable>
);
};
export const getLayout = (page) =>
getSiteLayout(<DefaultLayout>{page}</DefaultLayout>);
export default DefaultLayout;

Wyświetl plik

@ -1,3 +1,6 @@
@import "~slick-carousel/slick/slick.css";
@import "~slick-carousel/slick/slick-theme.css";
.Resizer {
background: #000;
opacity: 0.2;
@ -198,3 +201,69 @@ code {
transform: none;
visibility: visible;
}
.slide img {
width: 20rem;
margin: 0 auto;
}
.slide {
transform: scale(0.6) translate3D(0, -200px, 0px);
transition: transform 6000ms;
/* opacity: 0.6; */
padding-bottom: 200px;
animation: off 2s ease-in-out forwards;
overflow: visible;
}
.activeSlide {
transform: scale(1.1);
transition: transform 2000ms;
animation: change 2s ease-in-out forwards;
/* opacity: 1; */
}
@keyframes change {
to {
opacity: 1
}
}
@keyframes off {
to {
opacity: 0.6
}
}
.arrow {
background-color: #fff;
position: absolute;
cursor: pointer;
z-index: 10;
}
.arrow svg {
transition: color 300ms;
}
.arrow svg:hover {
color: #68edff;
}
.next {
right: 0%;
top: 50%;
}
.prev {
left: 0%;
top: 50%;
}
.bgGrid {
background-color: white;
background-size: 10px 10px, 60px 60px;
background-image: linear-gradient(to bottom, transparent 6px, white 6px),
linear-gradient(to right, #444 1px, transparent 1px),
linear-gradient(to right, transparent 6px, white 6px),
linear-gradient(to bottom, #444 1px, transparent 1px);
}

Plik diff jest za duży Load Diff