Commit eb9bf93c authored by 贾浩@五瓣科技's avatar 贾浩@五瓣科技

init

parents
Pipeline #819 canceled with stages
.idea
*.iml
out
gen
*.sol
*.txt
.DS_Store
*.exe
build
\ No newline at end of file
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go mod tidy && go build -v -o /tmp/sync ./cmd/sync
FROM alpine:latest
WORKDIR /app
COPY ./config.toml .
COPY --from=builder /tmp/sync /usr/bin/sync
EXPOSE 8080
\ No newline at end of file
.PHONY: default all clean dev messenger
GOBIN = $(shell pwd)/build/bin
default: all
all: api
api:
go build $(BUILD_FLAGS) -v -o=${GOBIN}/$@ ./cmd/api
docker:
docker build -t caduceus/pump:latest -f Dockerfile .
push:
docker push caduceus/pump:latest
\ No newline at end of file
package chain
import (
"pump/constant"
"pump/contract/bonding"
dbModel "pump/model/db"
"pump/util"
"strings"
"github.com/ethereum/go-ethereum/core/types"
"github.com/shopspring/decimal"
log "github.com/sirupsen/logrus"
)
func (s *Sync) FilterLaunch(txLog types.Log) {
if len(txLog.Topics) == 0 {
return
}
abi, _ := bonding.BondingMetaData.GetAbi()
if txLog.Topics[0].Hex() != abi.Events["Launched"].ID.Hex() {
return
}
launchEvent, err := s.bondingCa.ParseLaunched(txLog)
if err != nil {
log.WithError(err).Error("parse launch log")
return
}
newToken := launchEvent.Token.Hex()
tokenInfo, err := s.d.GetTokenInfo(newToken)
if err != nil {
log.WithField("token", newToken).WithError(err).Error("get token info")
return
}
token := &dbModel.Token{
Creator: strings.ToLower(tokenInfo.Creator.Hex()),
Token: strings.ToLower(tokenInfo.Token.Hex()),
NFTAddress: strings.ToLower(tokenInfo.NftAddress.Hex()),
Pair: strings.ToLower(tokenInfo.Pair.Hex()),
Ticker: tokenInfo.Data.Ticker,
Name: tokenInfo.Data.Name,
Supply: decimal.NewFromBigInt(tokenInfo.Data.Supply, 0),
Description: tokenInfo.Description,
Image: tokenInfo.Image,
Twitter: tokenInfo.Twitter,
Website: tokenInfo.Website,
Telegram: tokenInfo.Telegram,
Graduated: false,
Price: decimal.NewFromBigInt(tokenInfo.Data.Price, 0),
MarketCap: decimal.NewFromBigInt(tokenInfo.Data.MarketCap, 0),
Volume: decimal.NewFromBigInt(tokenInfo.Data.Volume, 0),
Volume24H: decimal.NewFromBigInt(tokenInfo.Data.Volume24H, 0),
Reserve0: decimal.NewFromBigInt(constant.TokenSupply, 0),
Reserve1: decimal.NewFromBigInt(constant.AssetSupply, 0),
TxHash: txLog.TxHash.Hex(),
BlockNumber: int(txLog.BlockNumber),
BlockTime: util.BlockToTimestamp(int(txLog.BlockNumber)),
}
err = s.d.CreateToken(token)
if err != nil {
log.WithError(err).Error("create agent token")
return
}
log.WithFields(log.Fields{
"creator": tokenInfo.Creator.Hex(),
"token": tokenInfo.Token.Hex(),
"tx": txLog.TxHash.Hex(),
}).Info("launch success")
}
func (s *Sync) FilterGraduate(txLog types.Log) {
if len(txLog.Topics) == 0 {
return
}
abi, _ := bonding.BondingMetaData.GetAbi()
if txLog.Topics[0].Hex() != abi.Events["Graduated"].ID.Hex() {
return
}
graduateEvent, err := s.bondingCa.ParseGraduated(txLog)
if err != nil {
log.WithError(err).Error("parse graduate log")
return
}
err = s.d.GraduateToken(graduateEvent.Token.Hex(), txLog.TxHash.Hex(), graduateEvent.Univ2Pair.Hex())
if err != nil {
log.WithError(err).Error("graduate agent token")
return
}
log.WithFields(log.Fields{
"token": graduateEvent.Token.Hex(),
"tx": txLog.TxHash.Hex(),
"univ2pair": graduateEvent.Univ2Pair.Hex(),
}).Info("graduate success")
}
func (s *Sync) FilterSwap(txLog types.Log) {
if len(txLog.Topics) == 0 {
return
}
abi, _ := bonding.BondingMetaData.GetAbi()
if txLog.Topics[0].Hex() != abi.Events["Swap"].ID.Hex() {
return
}
swapEvent, err := s.bondingCa.ParseSwap(txLog)
if err != nil {
log.WithError(err).Error("parse swap log")
return
}
buy := true
amountIn, amountOut := swapEvent.Amount1In, swapEvent.Amount0Out // eth增加, token减少
assetAmount, TokenAmount := decimal.NewFromBigInt(amountIn, 0), decimal.NewFromBigInt(amountOut, 0)
if swapEvent.Amount1In.String() == "0" {
buy = false
amountIn, amountOut = swapEvent.Amount0In, swapEvent.Amount1Out // eth减少, token增加
assetAmount, TokenAmount = decimal.NewFromBigInt(amountOut, 0), decimal.NewFromBigInt(amountIn, 0)
}
price := assetAmount.DivRound(TokenAmount, 18)
// priceWei := assetAmount.Div(TokenAmount).Mul(decimal.NewFromInt(1000000000000000000))
// eth单位
amountInETH := decimal.NewFromBigInt(amountIn, 0).DivRound(decimal.NewFromInt(1000000000000000000), 18)
amountOutETH := decimal.NewFromBigInt(amountOut, 0).DivRound(decimal.NewFromInt(1000000000000000000), 18)
tradeHistory := &dbModel.TradeHistory{
FromAddress: strings.ToLower(swapEvent.Sender.Hex()),
Token: strings.ToLower(swapEvent.Token.Hex()),
AmountIn: amountInETH,
AmountOut: amountOutETH,
Price: price,
IsBuy: buy,
TxHash: txLog.TxHash.Hex(),
LogIndex: int(txLog.Index),
BlockTime: util.BlockToTimestamp(int(txLog.BlockNumber)),
BlockNumber: int(txLog.BlockNumber),
}
err = s.d.CreateTradeHistory(tradeHistory)
if err != nil {
log.WithError(err).Error("create trade history")
return
}
supplyDec := decimal.NewFromBigInt(constant.TokenSupply, 0)
reserve0Dec := decimal.NewFromBigInt(swapEvent.Reserve0, 0)
reserve1Dec := decimal.NewFromBigInt(swapEvent.Reserve1, 0)
volumeDec := decimal.NewFromBigInt(swapEvent.Volume, 0)
volume24hDec := decimal.NewFromBigInt(swapEvent.Volume24H, 0)
mc := supplyDec.Mul(reserve1Dec).DivRound(reserve0Dec, 0)
err = s.d.UpdateToken(swapEvent.Token.String(), tradeHistory.Price, mc, volumeDec, volume24hDec, reserve0Dec, reserve1Dec)
if err != nil {
log.WithError(err).Error("update agent token")
return
}
log.WithFields(log.Fields{
"sender": swapEvent.Sender.Hex(),
"token": swapEvent.Token.Hex(),
"amountIn": amountIn,
"amountOut": amountOut,
"isBuy": buy,
"tx": txLog.TxHash.Hex(),
}).Info("swap success")
}
package chain
import (
"flag"
"os"
"pump/config"
"pump/contract/bonding"
"pump/contract/pair"
"pump/contract/token"
"pump/dao"
"strconv"
"time"
"github.com/ethereum/go-ethereum/common"
log "github.com/sirupsen/logrus"
)
type Sync struct {
c *config.Config
d *dao.Dao
heightKey string
bondingCa *bonding.Bonding
pairCa *pair.Pair
tokenCa *token.Token
}
var manualSync = flag.Int("sync", 0, "sync block height")
func NewSync(_c *config.Config, _d *dao.Dao) (sync *Sync) {
if *manualSync == 0 && os.Getenv("SYNC") != "" {
*manualSync, _ = strconv.Atoi(os.Getenv("SYNC"))
}
bondingCa, err := bonding.NewBonding(common.HexToAddress(_c.Chain.BondingContract), nil)
if err != nil {
panic(err)
}
pairCa, err := pair.NewPair(common.HexToAddress(_c.Chain.BondingContract), nil)
if err != nil {
panic(err)
}
tokenCa, err := token.NewToken(common.HexToAddress(_c.Chain.BondingContract), nil)
if err != nil {
panic(err)
}
sync = &Sync{
c: _c,
d: _d,
heightKey: "height",
bondingCa: bondingCa,
pairCa: pairCa,
tokenCa: tokenCa,
}
return sync
}
func (s *Sync) Start() {
lastHeight, err := s.d.GetStorageHeight(s.heightKey)
if err != nil {
log.WithError(err).Error("get last block height")
return
}
if lastHeight != 1 {
// 数据库里保存的是已完成的区块, 再次同步时+1
lastHeight++
}
if *manualSync > 0 {
lastHeight = *manualSync
}
log.WithField("height", lastHeight).Info("last sync block height")
var latestHeight int
var beginHeight = lastHeight
var endHeight = beginHeight + s.c.Chain.BatchBlock
for {
latestHeight, err = s.d.GetBlockHeight(s.c.Chain.BehindBlock)
if err != nil {
log.WithError(err).Error("get latest block height")
time.Sleep(time.Second)
continue
}
if (latestHeight-s.c.Chain.BatchBlock)-beginHeight < s.c.Chain.BatchBlock {
time.Sleep(4 * time.Second)
continue
}
s.SyncLogs(beginHeight-s.c.Chain.BatchBlock, endHeight-s.c.Chain.BatchBlock)
if err = s.d.SetStorageHeight(s.heightKey, endHeight); err != nil {
log.WithError(err).Error("set last block height")
}
log.WithFields(log.Fields{
"begin height": beginHeight,
"end height": endHeight,
"latest height": latestHeight,
"diff height": latestHeight - endHeight,
}).Info("sync block")
beginHeight = endHeight + 1
endHeight = beginHeight + s.c.Chain.BatchBlock
}
}
func (s *Sync) SyncLogs(beginHeight, endHeight int) {
if endHeight < 0 {
return
}
if beginHeight < 0 {
beginHeight = 0
}
abi, _ := bonding.BondingMetaData.GetAbi()
topics := []string{
abi.Events["Launched"].ID.Hex(),
abi.Events["Graduated"].ID.Hex(),
abi.Events["Swap"].ID.Hex(),
}
logs, err := s.d.GetLogs(beginHeight, endHeight, topics, []string{s.c.Chain.BondingContract})
if err != nil {
log.WithFields(log.Fields{"begin": beginHeight, "end": endHeight}).WithError(err).Error("rpc: get logs")
return
}
for _, txLog := range logs {
// s.Filterpump(txLog)
s.FilterLaunch(txLog)
s.FilterGraduate(txLog)
s.FilterSwap(txLog)
}
}
package main
import (
"flag"
"pump/chain"
"pump/config"
"pump/dao"
log "github.com/sirupsen/logrus"
)
func init() {
log.SetFormatter(&log.TextFormatter{
FullTimestamp: true,
})
}
func main() {
flag.Parse()
conf, err := config.New()
if err != nil {
panic(err)
}
d, err := dao.New(conf)
if err != nil {
panic(err)
}
if conf.Debug {
log.SetLevel(log.DebugLevel)
}
syncer := chain.NewSync(conf, d)
go syncer.Start()
select {}
}
debug = true
[chain]
rpc = 'https://sepolia.base.org'
batch_block = 4
behind_block = 0
bonding_contract = "0xa6dB4C4cC9fa53c749e4339D81b4b003EfF74500"
[pgsql]
host = 'aws-0-ap-northeast-1.pooler.supabase.com'
port = 5432
user = 'postgres.xjlxljoqbenbvslttrfu'
password = 'bitcointest3'
database = 'postgres'
max_conn = 5
max_idle_conn = 2
package config
import (
"flag"
"github.com/BurntSushi/toml"
)
type Config struct {
Debug bool `toml:"debug"`
PGSQL PGSQLConfig `toml:"pgsql"`
Chain ChainConfig `toml:"chain"`
}
type ChainConfig struct {
RPC string `toml:"rpc"`
ChainId int `toml:"chain_id"`
BondingContract string `toml:"bonding_contract"`
BatchBlock int `toml:"batch_block"`
BehindBlock int `toml:"behind_block"`
}
type PGSQLConfig struct {
Host string `toml:"host"`
Port int `toml:"port"`
User string `toml:"user"`
Password string `toml:"password"`
Database string `toml:"database"`
MaxConn int `toml:"max_conn"`
MaxIdleConn int `toml:"max_idle_conn"`
EnableLog bool `toml:"enable_log"`
CertFile string `toml:"cert_file"`
}
var confPath = flag.String("c", "config.toml", "config file path")
func New() (config *Config, err error) {
config = new(Config)
_, err = toml.DecodeFile(*confPath, config)
if err != nil {
return nil, err
}
return
}
package constant
import (
"math/big"
)
const JwtSecret = "uEj7AgDNCREwsvnTaCEtzDXt0I5eFDl8"
const (
InvalidParam = "invalid param"
UnsupportedPlatform = "unsupported platform"
InternalError = "internal error"
)
var TokenSupply, _ = new(big.Int).SetString("1000000000000000000000000000", 10)
var AssetSupply, _ = new(big.Int).SetString("1000000000000000000", 10) // 1 ether
This diff is collapsed.
This diff is collapsed.
[
{
"inputs": [
{
"internalType": "address",
"name": "router_",
"type": "address"
},
{
"internalType": "address",
"name": "token0",
"type": "address"
},
{
"internalType": "address",
"name": "token1",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
}
],
"name": "SafeERC20FailedOperation",
"type": "error"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "reserve0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "reserve1",
"type": "uint256"
}
],
"name": "Mint",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "amount0In",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0Out",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1In",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1Out",
"type": "uint256"
}
],
"name": "Swap",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "_user",
"type": "address"
},
{
"internalType": "address",
"name": "_token",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approval",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "assetBalance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "balance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getReserves",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "kLast",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "reserve0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "reserve1",
"type": "uint256"
}
],
"name": "mint",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "priceALast",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "priceBLast",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "router",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount0In",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount0Out",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1In",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1Out",
"type": "uint256"
}
],
"name": "swap",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "tokenA",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenB",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferAsset",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
\ No newline at end of file
This diff is collapsed.
[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_upgradedAddress","type":"address"}],"name":"deprecate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"deprecated","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_evilUser","type":"address"}],"name":"addBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply2","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"upgradedAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balances","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maximumFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_maker","type":"address"}],"name":"getBlackListStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowed","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newBasisPoints","type":"uint256"},{"name":"newMaxFee","type":"uint256"}],"name":"setParams","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"redeem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"basisPointsRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBlackListed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_clearedUser","type":"address"}],"name":"removeBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_UINT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_blackListedUser","type":"address"}],"name":"destroyBlackFunds","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_initialSupply","type":"uint256"},{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_decimals","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newAddress","type":"address"}],"name":"Deprecate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint256"},{"indexed":false,"name":"maxFee","type":"uint256"}],"name":"Params","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_blackListedUser","type":"address"},{"indexed":false,"name":"_balance","type":"uint256"}],"name":"DestroyedBlackFunds","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"AddedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"RemovedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"}]
\ No newline at end of file
This diff is collapsed.
package dao
import (
"context"
"fmt"
"pump/config"
dbModel "pump/model/db"
"time"
"github.com/ethereum/go-ethereum/ethclient"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
type Dao struct {
c *config.Config
db *gorm.DB
ethClient *ethclient.Client
}
func New(_c *config.Config) (dao *Dao, err error) {
dao = &Dao{
c: _c,
}
dao.ethClient, err = ethclient.Dial(_c.Chain.RPC)
if err != nil {
return
}
chainId, err := dao.ethClient.ChainID(context.Background())
if err != nil {
panic(fmt.Sprintf("failed to get l1 chain id %+v", err))
}
_c.Chain.ChainId = int(chainId.Int64())
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d",
_c.PGSQL.Host, _c.PGSQL.User, _c.PGSQL.Password, _c.PGSQL.Database, _c.PGSQL.Port,
)
if _c.PGSQL.CertFile != "" {
dsn = fmt.Sprintf("%s sslmode=require sslrootcert=%s", dsn, _c.PGSQL.CertFile)
}
lgr := logger.Default
if _c.PGSQL.EnableLog {
lgr = logger.Default.LogMode(logger.Info)
}
dao.db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
DisableForeignKeyConstraintWhenMigrating: true, // 停用外键约束
Logger: lgr,
})
if err != nil {
return
}
sqlDB, err := dao.db.DB()
if err != nil {
return
}
sqlDB.SetMaxOpenConns(_c.PGSQL.MaxConn)
sqlDB.SetMaxIdleConns(_c.PGSQL.MaxIdleConn)
sqlDB.SetConnMaxIdleTime(time.Hour)
err = dao.db.AutoMigrate(&dbModel.Height{}, &dbModel.Token{}, &dbModel.TradeHistory{})
if err != nil {
panic(err)
}
return dao, nil
}
package dao
import (
"errors"
dbModel "pump/model/db"
"strings"
"github.com/shopspring/decimal"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/logger"
)
// GetStorageHeight 获取上次缓存的高度
func (d *Dao) GetStorageHeight(key string) (value int, err error) {
storage := new(dbModel.Height)
err = d.db.Model(storage).Where("key = ?", key).First(storage).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
// 返回一个默认的起始高度
if strings.Contains(key, "l1") {
return 6202096, nil
}
return 306880, nil
}
return storage.IntValue, err
}
// SetStorageHeight 设置上次缓存的高度
func (d *Dao) SetStorageHeight(key string, intValue int) (err error) {
tx := d.db.Session(&gorm.Session{Logger: d.db.Logger.LogMode(logger.Error)})
return tx.Model(&dbModel.Height{}).Clauses(clause.OnConflict{UpdateAll: true}).Create(&dbModel.Height{
Key: key,
IntValue: intValue,
}).Error
}
func (d *Dao) CreateToken(token *dbModel.Token) (err error) {
return d.db.Clauses(clause.OnConflict{DoNothing: true}).Create(token).Error
}
func (d *Dao) UpdateToken(token string, newPrice, newMarketCap, newVolume, newVolume24H, newReserve0, newReserve1 decimal.Decimal) (err error) {
token = strings.ToLower(token)
return d.db.Model(&dbModel.Token{}).Where("token = ?", token).Updates(map[string]interface{}{
"price": newPrice,
"market_cap": newMarketCap,
"volume": newVolume,
"volume24h": newVolume24H,
"reserve0": newReserve0,
"reserve1": newReserve1,
}).Error
}
func (d *Dao) GraduateToken(token, txHash, uniV2Pair string) (err error) {
token = strings.ToLower(token)
uniV2Pair = strings.ToLower(uniV2Pair)
return d.db.Model(&dbModel.Token{}).Where("token = ?", token).Updates(map[string]interface{}{
"graduated": true,
"graduated_tx_hash": txHash,
"uniswap_v2_pair": uniV2Pair,
}).Error
}
func (d *Dao) CreateTradeHistory(tradeHistory *dbModel.TradeHistory) (err error) {
return d.db.Clauses(clause.OnConflict{DoNothing: true}).Create(tradeHistory).Error
}
package dao
import (
"context"
"math/big"
"pump/contract/bonding"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
func (d *Dao) GetBlockHeight(behindBlock ...int) (height int, err error) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
n, err := d.ethClient.BlockNumber(ctx)
if len(behindBlock) > 0 {
n -= uint64(behindBlock[0])
if n < 0 {
n = 0
}
}
return int(n), err
}
func (d *Dao) GetLogs(beginHeight, endHeight int, topics, addresses []string) (logs []types.Log, err error) {
for i := 0; i < 2; i++ {
// 重试2次
logs, err = d.getLogs(beginHeight, endHeight, topics, addresses)
if err == nil {
return logs, nil
}
}
return
}
func (d *Dao) getLogs(beginHeight, endHeight int, topics []string, addresses []string) (logs []types.Log, err error) {
addrs := make([]common.Address, 0)
for _, addr := range addresses {
addrs = append(addrs, common.HexToAddress(addr))
}
q := ethereum.FilterQuery{
FromBlock: big.NewInt(int64(beginHeight)),
ToBlock: big.NewInt(int64(endHeight)),
Topics: [][]common.Hash{{}},
Addresses: addrs,
}
for _, topic := range topics {
q.Topics[0] = append(q.Topics[0], common.HexToHash(topic))
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return d.ethClient.FilterLogs(ctx, q)
}
func (d *Dao) GetTokenInfo(addr string) (info struct {
Creator common.Address
Token common.Address
Pair common.Address
NftAddress common.Address
Data bonding.BondingData
Description string
Image string
Website string
Twitter string
Telegram string
Trading bool
TradingOnUniswap bool
UniswapV2Pair common.Address
}, err error) {
ca, err := bonding.NewBonding(common.HexToAddress(d.c.Chain.BondingContract), d.ethClient)
if err != nil {
return
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
opts := &bind.CallOpts{Context: ctx}
return ca.TokenInfo(opts, common.HexToAddress(addr))
}
services:
pump-sync:
image: caduceus/pump:latest
pull_policy: always
container_name: pump-sync
volumes:
- ./conf/pump/config.toml:/config.toml
- ./conf/pump/db.crt:/app/db.crt
- ./data/pump-sync/sync-log:/app
command:
- "/bin/sh"
- "-c"
- "/usr/bin/sync -c /config.toml"
restart:
unless-stopped
\ No newline at end of file
module pump
go 1.21.4
require (
github.com/BurntSushi/toml v1.4.0
github.com/btcsuite/btcutil v1.0.2
github.com/ethereum/go-ethereum v1.12.2
github.com/shopspring/decimal v1.4.0
github.com/sirupsen/logrus v1.9.3
golang.org/x/crypto v0.23.0
gorm.io/driver/postgres v1.5.11
gorm.io/gorm v1.25.10
)
require (
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/bits-and-blooms/bitset v1.10.0 // indirect
github.com/btcsuite/btcd v0.20.1-beta // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/consensys/gnark-crypto v0.12.1 // indirect
github.com/crate-crypto/go-kzg-4844 v1.0.0 // indirect
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/ethereum/c-kzg-4844 v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/holiman/uint256 v1.2.4 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.5 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
)
This diff is collapsed.
package dbModel
import (
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
type Token struct {
Id int `gorm:"primaryKey"`
Creator string `gorm:"type:varchar(255);index;not null;comment:创建者"`
Token string `gorm:"type:varchar(255);index;not null;comment:token"`
NFTAddress string `gorm:"type:varchar(255);not null;comment:nft地址"`
Pair string `gorm:"type:varchar(255);not null;comment:pair"`
Ticker string `gorm:"type:varchar(255);not null;comment:token"`
Name string `gorm:"type:varchar(255);not null;comment:名称"`
Supply decimal.Decimal `gorm:"type:decimal(65,0);comment:总量"`
Description string `gorm:"type:varchar(10000);not null;comment:描述"`
Image string `gorm:"type:varchar(255);not null;comment:图片"`
Twitter string `gorm:"type:varchar(255);not null;comment:twitter"`
Website string `gorm:"type:varchar(255);not null;comment:website"`
Telegram string `gorm:"type:varchar(255);not null;comment:telegram"`
UniswapV2Pair string `gorm:"type:varchar(255);not null;comment:uniswapV2Pair"`
Graduated bool `gorm:"type:bool;not null;comment:内盘结束"`
GraduatedTxHash string `gorm:"type:varchar(255);not null;comment:内盘结束交易hash"`
Price decimal.Decimal `gorm:"type:decimal(40,18);comment:价格"`
MarketCap decimal.Decimal `gorm:"type:decimal(65,0);comment:市值"`
Volume decimal.Decimal `gorm:"type:decimal(65,0);comment:成交量"`
Volume24H decimal.Decimal `gorm:"type:decimal(65,0);column:volume24h;comment:24小时成交量"`
Reserve0 decimal.Decimal `gorm:"type:decimal(65,0);comment:储备0"`
Reserve1 decimal.Decimal `gorm:"type:decimal(65,0);comment:储备1"`
TxHash string `gorm:"type:varchar(255);uniqueIndex;comment:tx hash"`
BlockNumber int `gorm:"type:int;comment:区块高度"`
BlockTime int `gorm:"type:int;index;comment:区块时间"`
gorm.Model
}
func (*Token) TableName() string {
return "public.token"
}
type TradeHistory struct {
Id int `gorm:"primaryKey"`
FromAddress string `gorm:"type:varchar(255);index;not null;comment:用户地址"`
Token string `gorm:"type:varchar(255);index;not null;comment:token"`
AmountIn decimal.Decimal `gorm:"type:decimal(40,18);comment:转入数量"`
AmountOut decimal.Decimal `gorm:"type:decimal(40,18);comment:转出数量"`
Price decimal.Decimal `gorm:"type:decimal(40,18);comment:价格"`
IsBuy bool `gorm:"type:bool;not null;comment:是否是买入"`
TxHash string `gorm:"type:varchar(255);not null;uniqueIndex:uidx_hash_idx;comment:tx hash"`
LogIndex int `gorm:"type:int;not null;uniqueIndex:uidx_hash_idx;comment:log index"`
BlockTime int `gorm:"type:int;index;comment:区块时间"`
BlockNumber int `gorm:"type:int;comment:区块高度"`
gorm.Model
}
func (*TradeHistory) TableName() string {
return "public.trade_history"
}
type Height struct {
Key string `gorm:"primaryKey"`
IntValue int `gorm:"type:int;not null"` // 配置value
}
func (*Height) TableName() string {
return "public.height"
}
# pump backend
\ No newline at end of file
package util
func BlockToTimestamp(block int) int {
return 1686789347 + block*2
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment