2017-04-11 02:25:53 +03:00
// Copyright 2017 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
2021-01-07 13:04:20 +02:00
// faucet is an Ether faucet backed by a light client.
2017-04-11 02:25:53 +03:00
package main
import (
"bytes"
"context"
2022-04-25 17:15:14 +08:00
_ "embed"
2017-04-11 02:25:53 +03:00
"encoding/json"
2017-10-16 16:30:13 +03:00
"errors"
2017-04-11 02:25:53 +03:00
"flag"
"fmt"
"html/template"
2022-05-16 11:59:35 +02:00
"io"
2017-05-02 13:52:51 +03:00
"math"
2017-04-11 02:25:53 +03:00
"math/big"
"net/http"
2017-04-16 19:49:40 +03:00
"net/url"
2017-04-11 02:25:53 +03:00
"os"
"path/filepath"
2017-10-16 16:30:13 +03:00
"regexp"
2017-05-02 13:52:51 +03:00
"strconv"
2017-04-11 02:25:53 +03:00
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts"
2020-07-03 15:04:17 +08:00
"github.com/ethereum/go-ethereum/accounts/abi"
2017-04-11 02:25:53 +03:00
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
2019-11-14 09:05:17 +01:00
"github.com/gorilla/websocket"
2017-04-11 02:25:53 +03:00
)
var (
genesisFlag = flag . String ( "genesis" , "" , "Genesis json file to seed the chain with" )
apiPortFlag = flag . Int ( "apiport" , 8080 , "Listener port for the HTTP API connection" )
2023-12-26 04:32:04 +01:00
wsEndpoint = flag . String ( "ws" , "http://127.0.0.1:7777/" , "Url to ws endpoint" )
2017-04-11 02:25:53 +03:00
netnameFlag = flag . String ( "faucet.name" , "" , "Network name to assign to the faucet" )
payoutFlag = flag . Int ( "faucet.amount" , 1 , "Number of Ethers to pay out per user request" )
minutesFlag = flag . Int ( "faucet.minutes" , 1440 , "Number of minutes to wait between funding rounds" )
2017-05-02 13:52:51 +03:00
tiersFlag = flag . Int ( "faucet.tiers" , 3 , "Number of funding tiers to enable (x3 time, x2.5 funds)" )
2017-04-11 02:25:53 +03:00
accJSONFlag = flag . String ( "account.json" , "" , "Key json file to fund user requests with" )
accPassFlag = flag . String ( "account.pass" , "" , "Decryption password to access faucet funds" )
2017-04-16 19:49:40 +03:00
captchaToken = flag . String ( "captcha.token" , "" , "Recaptcha site key to authenticate client side" )
captchaSecret = flag . String ( "captcha.secret" , "" , "Recaptcha secret key to authenticate server side" )
2017-10-23 10:22:23 +03:00
noauthFlag = flag . Bool ( "noauth" , false , "Enables funding requests without authentication" )
logFlag = flag . Int ( "loglevel" , 3 , "Log level to use for Ethereum and the faucet" )
2020-05-20 11:44:06 +08:00
2021-04-16 12:45:26 +08:00
bep2eContracts = flag . String ( "bep2eContracts" , "" , "the list of bep2p contracts" )
bep2eSymbols = flag . String ( "bep2eSymbols" , "" , "the symbol of bep2p tokens" )
bep2eAmounts = flag . String ( "bep2eAmounts" , "" , "the amount of bep2p tokens" )
fixGasPrice = flag . Int64 ( "faucet.fixedprice" , 0 , "Will use fixed gas price if specified" )
2021-01-04 13:58:46 +02:00
twitterTokenFlag = flag . String ( "twitter.token" , "" , "Bearer token to authenticate with the v2 Twitter API" )
twitterTokenV1Flag = flag . String ( "twitter.token.v1" , "" , "Bearer token to authenticate with the v1.1 Twitter API" )
2017-04-11 02:25:53 +03:00
)
var (
2020-06-30 16:16:26 +08:00
ether = new ( big . Int ) . Exp ( big . NewInt ( 10 ) , big . NewInt ( 18 ) , nil )
bep2eAbiJson = ` [ { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" }, { "inputs": [], "name": "totalSupply", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "decimals", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "symbol", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getOwner", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "name": "balanceOf", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transfer", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "_owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" } ], "name": "allowance", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "approve", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" } ] `
2017-04-11 02:25:53 +03:00
)
2022-04-25 17:15:14 +08:00
//go:embed faucet.html
var websiteTmpl string
2019-05-08 17:11:33 +03:00
2017-04-11 02:25:53 +03:00
func main ( ) {
// Parse the flags and set up the logger to print everything requested
flag . Parse ( )
2024-02-02 15:43:33 +08:00
log . SetDefault ( log . NewLogger ( log . NewTerminalHandlerWithLevel ( os . Stderr , log . FromLegacyLevel ( * logFlag ) , true ) ) )
2017-04-11 02:25:53 +03:00
2017-05-02 13:52:51 +03:00
// Construct the payout tiers
amounts := make ( [ ] string , * tiersFlag )
for i := 0 ; i < * tiersFlag ; i ++ {
// Calculate the amount for the next tier and format it
amount := float64 ( * payoutFlag ) * math . Pow ( 2.5 , float64 ( i ) )
2023-04-06 11:17:53 +08:00
amounts [ i ] = fmt . Sprintf ( "0.%s BNBs" , strconv . FormatFloat ( amount , 'f' , - 1 , 64 ) )
2017-05-02 13:52:51 +03:00
if amount == 1 {
amounts [ i ] = strings . TrimSuffix ( amounts [ i ] , "s" )
}
}
2020-06-30 16:16:26 +08:00
bep2eNumAmounts := make ( [ ] string , 0 )
if bep2eAmounts != nil && len ( * bep2eAmounts ) > 0 {
bep2eNumAmounts = strings . Split ( * bep2eAmounts , "," )
}
symbols := make ( [ ] string , 0 )
if bep2eSymbols != nil && len ( * bep2eSymbols ) > 0 {
symbols = strings . Split ( * bep2eSymbols , "," )
}
contracts := make ( [ ] string , 0 )
if bep2eContracts != nil && len ( * bep2eContracts ) > 0 {
contracts = strings . Split ( * bep2eContracts , "," )
}
2020-05-20 11:44:06 +08:00
2020-06-30 16:16:26 +08:00
if len ( bep2eNumAmounts ) != len ( symbols ) || len ( symbols ) != len ( contracts ) {
log . Crit ( "Length of bep2eContracts, bep2eSymbols, bep2eAmounts mismatch" )
}
2022-07-05 11:14:21 +08:00
bep2eInfos := make ( map [ string ] bep2eInfo , len ( symbols ) )
2020-06-30 16:16:26 +08:00
for idx , s := range symbols {
n , ok := big . NewInt ( 0 ) . SetString ( bep2eNumAmounts [ idx ] , 10 )
if ! ok {
log . Crit ( "failed to parse bep2eAmounts" )
}
amountStr := big . NewFloat ( 0 ) . Quo ( big . NewFloat ( 0 ) . SetInt ( n ) , big . NewFloat ( 0 ) . SetInt64 ( params . Ether ) ) . String ( )
bep2eInfos [ s ] = bep2eInfo {
Contract : common . HexToAddress ( contracts [ idx ] ) ,
Amount : * n ,
AmountStr : amountStr ,
}
}
2017-04-11 02:25:53 +03:00
website := new ( bytes . Buffer )
2023-09-08 16:36:16 +08:00
err := template . Must ( template . New ( "" ) . Parse ( websiteTmpl ) ) . Execute ( website , map [ string ] interface { } {
2020-06-30 16:16:26 +08:00
"Network" : * netnameFlag ,
"Amounts" : amounts ,
"Recaptcha" : * captchaToken ,
"NoAuth" : * noauthFlag ,
"Bep2eInfos" : bep2eInfos ,
2017-04-11 02:25:53 +03:00
} )
2017-05-02 13:52:51 +03:00
if err != nil {
log . Crit ( "Failed to render the faucet template" , "err" , err )
}
2017-04-11 02:25:53 +03:00
// Load and parse the genesis block requested by the user
2023-08-23 17:46:08 +08:00
genesis , err := getGenesis ( * genesisFlag , false , false )
2017-04-11 02:25:53 +03:00
if err != nil {
2023-04-06 11:17:53 +08:00
log . Crit ( "Failed to read genesis block contents" , "genesis" , * genesisFlag , "err" , err )
}
2017-04-11 02:25:53 +03:00
// Load up the account key and decrypt its password
2022-05-16 11:59:35 +02:00
blob , err := os . ReadFile ( * accPassFlag )
2021-04-14 03:21:46 +05:30
if err != nil {
2017-04-11 02:25:53 +03:00
log . Crit ( "Failed to read account password contents" , "file" , * accPassFlag , "err" , err )
}
2018-09-04 19:16:49 +08:00
pass := strings . TrimSuffix ( string ( blob ) , "\n" )
2017-04-11 02:25:53 +03:00
2023-04-06 11:17:53 +08:00
ks := keystore . NewKeyStore ( filepath . Join ( os . Getenv ( "HOME" ) , ".faucet" , "keys_2" ) , keystore . StandardScryptN , keystore . StandardScryptP )
2022-05-16 11:59:35 +02:00
if blob , err = os . ReadFile ( * accJSONFlag ) ; err != nil {
2017-04-11 02:25:53 +03:00
log . Crit ( "Failed to read account key contents" , "file" , * accJSONFlag , "err" , err )
}
acc , err := ks . Import ( blob , pass , pass )
2020-06-04 08:59:26 +03:00
if err != nil && err != keystore . ErrAccountAlreadyExists {
2017-04-11 02:25:53 +03:00
log . Crit ( "Failed to import faucet signer account" , "err" , err )
}
2020-06-04 10:22:11 +03:00
if err := ks . Unlock ( acc , pass ) ; err != nil {
log . Crit ( "Failed to unlock faucet signer account" , "err" , err )
}
2017-04-11 02:25:53 +03:00
// Assemble and start the faucet light service
2023-12-26 04:32:04 +01:00
faucet , err := newFaucet ( genesis , * wsEndpoint , ks , website . Bytes ( ) , bep2eInfos )
2017-04-11 02:25:53 +03:00
if err != nil {
log . Crit ( "Failed to start faucet" , "err" , err )
}
defer faucet . close ( )
if err := faucet . listenAndServe ( * apiPortFlag ) ; err != nil {
log . Crit ( "Failed to launch faucet API" , "err" , err )
}
}
// request represents an accepted funding request.
type request struct {
2017-10-16 16:30:13 +03:00
Avatar string ` json:"avatar" ` // Avatar URL to make the UI nicer
Account common . Address ` json:"account" ` // Ethereum address being funded
2017-10-17 14:55:21 +03:00
Time time . Time ` json:"time" ` // Timestamp when the request was accepted
2017-10-16 16:30:13 +03:00
Tx * types . Transaction ` json:"tx" ` // Transaction funding the account
2017-04-11 02:25:53 +03:00
}
2020-06-30 16:16:26 +08:00
type bep2eInfo struct {
Contract common . Address
Amount big . Int
AmountStr string
}
2017-04-11 02:25:53 +03:00
// faucet represents a crypto faucet backed by an Ethereum light client.
type faucet struct {
config * params . ChainConfig // Chain configurations for signing
client * ethclient . Client // Client connection to the Ethereum chain
index [ ] byte // Index page to serve up on the web
keystore * keystore . KeyStore // Keystore containing the single signer
account accounts . Account // Account funding user faucet requests
2018-09-21 13:15:09 +03:00
head * types . Header // Current head header of the faucet
balance * big . Int // Current balance of the faucet
2017-04-11 02:25:53 +03:00
nonce uint64 // Current pending nonce of the faucet
price * big . Int // Current gas price to issue funds with
2021-01-07 10:23:50 +02:00
conns [ ] * wsConn // Currently live websocket connections
2017-05-02 13:52:51 +03:00
timeouts map [ string ] time . Time // History of users and their funding timeouts
reqs [ ] * request // Currently pending funding requests
update chan struct { } // Channel to signal request updates
2017-04-11 02:25:53 +03:00
lock sync . RWMutex // Lock protecting the faucet's internals
2020-06-30 16:16:26 +08:00
bep2eInfos map [ string ] bep2eInfo
bep2eAbi abi . ABI
2017-04-11 02:25:53 +03:00
}
2021-01-07 10:23:50 +02:00
// wsConn wraps a websocket connection with a write mutex as the underlying
// websocket library does not synchronize access to the stream.
type wsConn struct {
conn * websocket . Conn
wlock sync . Mutex
}
2023-12-26 04:32:04 +01:00
func newFaucet ( genesis * core . Genesis , url string , ks * keystore . KeyStore , index [ ] byte , bep2eInfos map [ string ] bep2eInfo ) ( * faucet , error ) {
2020-06-30 16:16:26 +08:00
bep2eAbi , err := abi . JSON ( strings . NewReader ( bep2eAbiJson ) )
if err != nil {
return nil , err
}
2023-12-26 04:32:04 +01:00
client , err := ethclient . Dial ( url )
2020-08-03 19:40:46 +02:00
if err != nil {
2017-04-11 02:25:53 +03:00
return nil , err
}
return & faucet {
2020-06-30 16:16:26 +08:00
config : genesis . Config ,
client : client ,
index : index ,
keystore : ks ,
account : ks . Accounts ( ) [ 0 ] ,
timeouts : make ( map [ string ] time . Time ) ,
update : make ( chan struct { } , 1 ) ,
bep2eInfos : bep2eInfos ,
bep2eAbi : bep2eAbi ,
2017-04-11 02:25:53 +03:00
} , nil
}
// close terminates the Ethereum connection and tears down the faucet.
2023-12-26 04:32:04 +01:00
func ( f * faucet ) close ( ) {
f . client . Close ( )
2017-04-11 02:25:53 +03:00
}
// listenAndServe registers the HTTP handlers for the faucet and boots it up
// for service user funding requests.
func ( f * faucet ) listenAndServe ( port int ) error {
go f . loop ( )
http . HandleFunc ( "/" , f . webHandler )
2019-11-14 09:05:17 +01:00
http . HandleFunc ( "/api" , f . apiHandler )
2020-05-20 11:44:06 +08:00
http . HandleFunc ( "/faucet-smart/api" , f . apiHandler )
2017-04-11 02:25:53 +03:00
return http . ListenAndServe ( fmt . Sprintf ( ":%d" , port ) , nil )
}
// webHandler handles all non-api requests, simply flattening and returning the
// faucet website.
func ( f * faucet ) webHandler ( w http . ResponseWriter , r * http . Request ) {
w . Write ( f . index )
}
// apiHandler handles requests for Ether grants and transaction statuses.
2019-11-14 09:05:17 +01:00
func ( f * faucet ) apiHandler ( w http . ResponseWriter , r * http . Request ) {
2023-12-26 04:32:04 +01:00
upgrader := websocket . Upgrader { CheckOrigin : func ( r * http . Request ) bool { return true } }
2019-11-14 09:05:17 +01:00
conn , err := upgrader . Upgrade ( w , r , nil )
if err != nil {
return
}
2017-04-11 02:25:53 +03:00
// Start tracking the connection and drop at the end
2017-10-17 12:08:57 +03:00
defer conn . Close ( )
2023-04-06 11:17:53 +08:00
ipsStr := r . Header . Get ( "X-Forwarded-For" )
ips := strings . Split ( ipsStr , "," )
if len ( ips ) < 2 {
return
}
2017-10-17 12:08:57 +03:00
2017-04-11 02:25:53 +03:00
f . lock . Lock ( )
2021-01-07 10:23:50 +02:00
wsconn := & wsConn { conn : conn }
f . conns = append ( f . conns , wsconn )
2017-04-11 02:25:53 +03:00
f . lock . Unlock ( )
defer func ( ) {
f . lock . Lock ( )
for i , c := range f . conns {
2021-01-07 10:23:50 +02:00
if c . conn == conn {
2017-04-11 02:25:53 +03:00
f . conns = append ( f . conns [ : i ] , f . conns [ i + 1 : ] ... )
break
}
}
f . lock . Unlock ( )
} ( )
2017-10-17 12:08:57 +03:00
// Gather the initial stats from the network to report
var (
head * types . Header
balance * big . Int
nonce uint64
)
2018-09-21 13:15:09 +03:00
for head == nil || balance == nil {
// Retrieve the current stats cached by the faucet
f . lock . RLock ( )
if f . head != nil {
head = types . CopyHeader ( f . head )
2017-10-17 12:08:57 +03:00
}
2018-09-21 13:15:09 +03:00
if f . balance != nil {
balance = new ( big . Int ) . Set ( f . balance )
}
nonce = f . nonce
f . lock . RUnlock ( )
2017-04-11 02:25:53 +03:00
2018-09-21 13:15:09 +03:00
if head == nil || balance == nil {
// Report the faucet offline until initial stats are ready
2019-11-29 11:38:34 +01:00
//lint:ignore ST1005 This error is to be displayed in the browser
2021-01-07 10:23:50 +02:00
if err = sendError ( wsconn , errors . New ( "Faucet offline" ) ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send faucet error to client" , "err" , err )
return
}
time . Sleep ( 3 * time . Second )
}
}
// Send over the initial stats and the latest header
2020-02-16 02:14:29 +08:00
f . lock . RLock ( )
reqs := f . reqs
f . lock . RUnlock ( )
2021-01-07 10:23:50 +02:00
if err = send ( wsconn , map [ string ] interface { } {
2018-09-21 13:15:09 +03:00
"funds" : new ( big . Int ) . Div ( balance , ether ) ,
2017-04-11 02:25:53 +03:00
"funded" : nonce ,
2020-02-16 02:14:29 +08:00
"requests" : reqs ,
2017-10-17 12:08:57 +03:00
} , 3 * time . Second ) ; err != nil {
log . Warn ( "Failed to send initial stats to client" , "err" , err )
return
}
2021-01-07 10:23:50 +02:00
if err = send ( wsconn , head , 3 * time . Second ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send initial header to client" , "err" , err )
return
2017-05-02 13:52:51 +03:00
}
2017-04-11 02:25:53 +03:00
// Keep reading requests from the websocket until the connection breaks
for {
// Fetch the next funding request and validate against github
var msg struct {
2017-04-16 19:49:40 +03:00
URL string ` json:"url" `
2017-05-02 13:52:51 +03:00
Tier uint ` json:"tier" `
2017-04-16 19:49:40 +03:00
Captcha string ` json:"captcha" `
2020-06-30 16:16:26 +08:00
Symbol string ` json:"symbol" `
2017-04-11 02:25:53 +03:00
}
2019-11-14 09:05:17 +01:00
if err = conn . ReadJSON ( & msg ) ; err != nil {
2017-04-11 02:25:53 +03:00
return
}
2021-01-04 13:58:46 +02:00
if ! * noauthFlag && ! strings . HasPrefix ( msg . URL , "https://twitter.com/" ) && ! strings . HasPrefix ( msg . URL , "https://www.facebook.com/" ) {
2021-01-07 10:23:50 +02:00
if err = sendError ( wsconn , errors . New ( "URL doesn't link to supported services" ) ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send URL error to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
continue
}
2017-05-02 13:52:51 +03:00
if msg . Tier >= uint ( * tiersFlag ) {
2019-11-29 11:38:34 +01:00
//lint:ignore ST1005 This error is to be displayed in the browser
2021-01-07 10:23:50 +02:00
if err = sendError ( wsconn , errors . New ( "Invalid funding tier requested" ) ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send tier error to client" , "err" , err )
return
}
2017-05-02 13:52:51 +03:00
continue
}
2017-10-16 16:30:13 +03:00
log . Info ( "Faucet funds requested" , "url" , msg . URL , "tier" , msg . Tier )
2017-04-16 19:49:40 +03:00
// If captcha verifications are enabled, make sure we're not dealing with a robot
if * captchaToken != "" {
form := url . Values { }
form . Add ( "secret" , * captchaSecret )
form . Add ( "response" , msg . Captcha )
2023-04-06 11:17:53 +08:00
res , err := http . PostForm ( "https://hcaptcha.com/siteverify" , form )
2017-04-16 19:49:40 +03:00
if err != nil {
2021-01-07 10:23:50 +02:00
if err = sendError ( wsconn , err ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send captcha post error to client" , "err" , err )
return
}
2017-04-16 19:49:40 +03:00
continue
}
var result struct {
Success bool ` json:"success" `
Errors json . RawMessage ` json:"error-codes" `
}
err = json . NewDecoder ( res . Body ) . Decode ( & result )
res . Body . Close ( )
if err != nil {
2021-01-07 10:23:50 +02:00
if err = sendError ( wsconn , err ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send captcha decode error to client" , "err" , err )
return
}
2017-04-16 19:49:40 +03:00
continue
}
if ! result . Success {
log . Warn ( "Captcha verification failed" , "err" , string ( result . Errors ) )
2019-11-29 11:38:34 +01:00
//lint:ignore ST1005 it's funny and the robot won't mind
2021-01-07 10:23:50 +02:00
if err = sendError ( wsconn , errors . New ( "Beep-bop, you're a robot!" ) ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send captcha failure to client" , "err" , err )
return
}
2017-04-16 19:49:40 +03:00
continue
}
}
2017-10-16 16:30:13 +03:00
// Retrieve the Ethereum address to fund, the requesting user and a profile picture
var (
2020-12-11 15:05:39 +05:30
id string
2017-10-16 16:30:13 +03:00
username string
avatar string
address common . Address
)
switch {
2023-04-06 11:17:53 +08:00
case strings . HasPrefix ( msg . URL , "https://gist.github.com/" ) :
if err = sendError ( wsconn , errors . New ( "GitHub authentication discontinued at the official request of GitHub" ) ) ; err != nil {
log . Warn ( "Failed to send GitHub deprecation to client" , "err" , err )
return
}
continue
case strings . HasPrefix ( msg . URL , "https://plus.google.com/" ) :
//lint:ignore ST1005 Google is a company name and should be capitalized.
if err = sendError ( wsconn , errors . New ( "Google+ authentication discontinued as the service was sunset" ) ) ; err != nil {
log . Warn ( "Failed to send Google+ deprecation to client" , "err" , err )
return
}
continue
2017-10-16 16:30:13 +03:00
case strings . HasPrefix ( msg . URL , "https://twitter.com/" ) :
2021-01-04 13:58:46 +02:00
id , username , avatar , address , err = authTwitter ( msg . URL , * twitterTokenV1Flag , * twitterTokenFlag )
2017-10-16 16:30:13 +03:00
case strings . HasPrefix ( msg . URL , "https://www.facebook.com/" ) :
username , avatar , address , err = authFacebook ( msg . URL )
2020-12-11 15:05:39 +05:30
id = username
2017-10-23 10:22:23 +03:00
case * noauthFlag :
username , avatar , address , err = authNoAuth ( msg . URL )
2020-12-11 15:05:39 +05:30
id = username
2017-10-16 16:30:13 +03:00
default :
2019-11-29 11:38:34 +01:00
//lint:ignore ST1005 This error is to be displayed in the browser
2017-10-16 16:30:13 +03:00
err = errors . New ( "Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues" )
2017-04-11 02:25:53 +03:00
}
if err != nil {
2021-01-07 10:23:50 +02:00
if err = sendError ( wsconn , err ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send prefix error to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
continue
}
2017-10-16 16:30:13 +03:00
log . Info ( "Faucet request valid" , "url" , msg . URL , "tier" , msg . Tier , "user" , username , "address" , address )
2017-04-16 18:49:06 +03:00
2017-04-11 02:25:53 +03:00
// Ensure the user didn't request funds too recently
f . lock . Lock ( )
var (
fund bool
2017-05-02 13:52:51 +03:00
timeout time . Time
2017-04-11 02:25:53 +03:00
)
2023-04-06 11:17:53 +08:00
if ipTimeout := f . timeouts [ ips [ len ( ips ) - 2 ] ] ; time . Now ( ) . Before ( ipTimeout ) {
if err = sendError ( wsconn , fmt . Errorf ( "%s left until next allowance" , common . PrettyDuration ( time . Until ( ipTimeout ) ) ) ) ; err != nil { // nolint: gosimple
log . Warn ( "Failed to send funding error to client" , "err" , err )
}
f . lock . Unlock ( )
continue
}
2020-12-11 15:05:39 +05:30
if timeout = f . timeouts [ id ] ; time . Now ( ) . After ( timeout ) {
2020-06-30 16:16:26 +08:00
var tx * types . Transaction
if msg . Symbol == "BNB" {
// User wasn't funded recently, create the funding transaction
2023-04-06 11:17:53 +08:00
amount := new ( big . Int ) . Div ( new ( big . Int ) . Mul ( big . NewInt ( int64 ( * payoutFlag ) ) , ether ) , big . NewInt ( 10 ) )
2020-06-30 16:16:26 +08:00
amount = new ( big . Int ) . Mul ( amount , new ( big . Int ) . Exp ( big . NewInt ( 5 ) , big . NewInt ( int64 ( msg . Tier ) ) , nil ) )
amount = new ( big . Int ) . Div ( amount , new ( big . Int ) . Exp ( big . NewInt ( 2 ) , big . NewInt ( int64 ( msg . Tier ) ) , nil ) )
tx = types . NewTransaction ( f . nonce + uint64 ( len ( f . reqs ) ) , address , amount , 21000 , f . price , nil )
} else {
tokenInfo , ok := f . bep2eInfos [ msg . Symbol ]
if ! ok {
f . lock . Unlock ( )
log . Warn ( "Failed to find symbol" , "symbol" , msg . Symbol )
continue
}
input , err := f . bep2eAbi . Pack ( "transfer" , address , & tokenInfo . Amount )
if err != nil {
f . lock . Unlock ( )
log . Warn ( "Failed to pack transfer transaction" , "err" , err )
continue
}
tx = types . NewTransaction ( f . nonce + uint64 ( len ( f . reqs ) ) , tokenInfo . Contract , nil , 420000 , f . price , input )
}
2018-06-05 03:31:34 -07:00
signed , err := f . keystore . SignTx ( f . account , tx , f . config . ChainID )
2017-04-11 02:25:53 +03:00
if err != nil {
f . lock . Unlock ( )
2021-01-07 10:23:50 +02:00
if err = sendError ( wsconn , err ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send transaction creation error to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
continue
}
// Submit the transaction and mark as funded if successful
if err := f . client . SendTransaction ( context . Background ( ) , signed ) ; err != nil {
f . lock . Unlock ( )
2021-01-07 10:23:50 +02:00
if err = sendError ( wsconn , err ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send transaction transmission error to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
continue
}
f . reqs = append ( f . reqs , & request {
2017-10-16 16:30:13 +03:00
Avatar : avatar ,
Account : address ,
Time : time . Now ( ) ,
Tx : signed ,
2017-04-11 02:25:53 +03:00
} )
2019-07-23 05:52:41 -04:00
timeout := time . Duration ( * minutesFlag * int ( math . Pow ( 3 , float64 ( msg . Tier ) ) ) ) * time . Minute
grace := timeout / 288 // 24h timeout => 5m grace
2020-12-11 15:05:39 +05:30
f . timeouts [ id ] = time . Now ( ) . Add ( timeout - grace )
2023-04-06 11:17:53 +08:00
f . timeouts [ ips [ len ( ips ) - 2 ] ] = time . Now ( ) . Add ( timeout - grace )
2017-04-11 02:25:53 +03:00
fund = true
}
f . lock . Unlock ( )
2024-04-18 15:43:57 +08:00
// Send an error if too frequent funding, otherwise a success
2017-04-11 02:25:53 +03:00
if ! fund {
2021-01-07 10:23:50 +02:00
if err = sendError ( wsconn , fmt . Errorf ( "%s left until next allowance" , common . PrettyDuration ( time . Until ( timeout ) ) ) ) ; err != nil { // nolint: gosimple
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send funding error to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
continue
}
2021-01-07 10:23:50 +02:00
if err = sendSuccess ( wsconn , fmt . Sprintf ( "Funding request accepted for %s into %s" , username , address . Hex ( ) ) ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send funding success to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
select {
case f . update <- struct { } { } :
default :
}
}
}
2018-09-21 13:15:09 +03:00
// refresh attempts to retrieve the latest header from the chain and extract the
// associated faucet balance and nonce for connectivity caching.
func ( f * faucet ) refresh ( head * types . Header ) error {
// Ensure a state update does not run for too long
ctx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Second )
defer cancel ( )
// If no header was specified, use the current chain head
var err error
if head == nil {
if head , err = f . client . HeaderByNumber ( ctx , nil ) ; err != nil {
return err
}
}
// Retrieve the balance, nonce and gas price from the current head
var (
balance * big . Int
nonce uint64
price * big . Int
)
if balance , err = f . client . BalanceAt ( ctx , f . account . Address , head . Number ) ; err != nil {
return err
}
if nonce , err = f . client . NonceAt ( ctx , f . account . Address , head . Number ) ; err != nil {
return err
}
2020-05-20 11:44:06 +08:00
if fixGasPrice != nil && * fixGasPrice > 0 {
price = big . NewInt ( * fixGasPrice )
} else {
if price , err = f . client . SuggestGasPrice ( ctx ) ; err != nil {
return err
}
2018-09-21 13:15:09 +03:00
}
// Everything succeeded, update the cached stats and eject old requests
f . lock . Lock ( )
f . head , f . balance = head , balance
f . price , f . nonce = price , nonce
2023-04-14 09:52:15 +08:00
if len ( f . reqs ) > 0 && f . reqs [ 0 ] . Tx . Nonce ( ) > f . nonce {
f . reqs = f . reqs [ : 0 ]
}
2018-09-21 13:15:09 +03:00
for len ( f . reqs ) > 0 && f . reqs [ 0 ] . Tx . Nonce ( ) < f . nonce {
f . reqs = f . reqs [ 1 : ]
}
f . lock . Unlock ( )
return nil
}
2017-04-11 02:25:53 +03:00
// loop keeps waiting for interesting events and pushes them out to connected
// websockets.
func ( f * faucet ) loop ( ) {
// Wait for chain events and push them to clients
heads := make ( chan * types . Header , 16 )
sub , err := f . client . SubscribeNewHead ( context . Background ( ) , heads )
if err != nil {
log . Crit ( "Failed to subscribe to head events" , "err" , err )
}
defer sub . Unsubscribe ( )
2018-03-02 11:57:11 +02:00
// Start a goroutine to update the state from head notifications in the background
update := make ( chan * types . Header )
go func ( ) {
for head := range update {
2017-04-11 02:25:53 +03:00
// New chain head arrived, query the current stats and stream to clients
2019-04-02 22:28:48 +02:00
timestamp := time . Unix ( int64 ( head . Time ) , 0 )
2018-09-21 13:15:09 +03:00
if time . Since ( timestamp ) > time . Hour {
log . Warn ( "Skipping faucet refresh, head too old" , "number" , head . Number , "hash" , head . Hash ( ) , "age" , common . PrettyAge ( timestamp ) )
continue
2017-10-17 12:08:57 +03:00
}
2018-09-21 13:15:09 +03:00
if err := f . refresh ( head ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to update faucet state" , "block" , head . Number , "hash" , head . Hash ( ) , "err" , err )
continue
}
// Faucet state retrieved, update locally and send to clients
2018-09-21 13:15:09 +03:00
f . lock . RLock ( )
log . Info ( "Updated faucet state" , "number" , head . Number , "hash" , head . Hash ( ) , "age" , common . PrettyAge ( timestamp ) , "balance" , f . balance , "nonce" , f . nonce , "price" , f . price )
2017-04-11 02:25:53 +03:00
2018-09-21 13:15:09 +03:00
balance := new ( big . Int ) . Div ( f . balance , ether )
2017-04-11 02:25:53 +03:00
for _ , conn := range f . conns {
2017-10-17 12:08:57 +03:00
if err := send ( conn , map [ string ] interface { } {
2017-04-11 02:25:53 +03:00
"funds" : balance ,
"funded" : f . nonce ,
"requests" : f . reqs ,
2017-10-17 12:08:57 +03:00
} , time . Second ) ; err != nil {
2017-04-11 02:25:53 +03:00
log . Warn ( "Failed to send stats to client" , "err" , err )
2021-01-07 10:23:50 +02:00
conn . conn . Close ( )
2017-04-11 02:25:53 +03:00
continue
}
2017-10-17 12:08:57 +03:00
if err := send ( conn , head , time . Second ) ; err != nil {
2017-04-11 02:25:53 +03:00
log . Warn ( "Failed to send header to client" , "err" , err )
2021-01-07 10:23:50 +02:00
conn . conn . Close ( )
2017-04-11 02:25:53 +03:00
}
}
f . lock . RUnlock ( )
2018-03-02 11:57:11 +02:00
}
} ( )
2024-07-16 18:10:45 +09:00
// Wait for various events and assign to the appropriate background threads
2018-03-02 11:57:11 +02:00
for {
select {
case head := <- heads :
// New head arrived, send if for state update if there's none running
select {
case update <- head :
default :
}
2017-04-11 02:25:53 +03:00
case <- f . update :
// Pending requests updated, stream to clients
f . lock . RLock ( )
for _ , conn := range f . conns {
2017-10-17 12:08:57 +03:00
if err := send ( conn , map [ string ] interface { } { "requests" : f . reqs } , time . Second ) ; err != nil {
2017-04-11 02:25:53 +03:00
log . Warn ( "Failed to send requests to client" , "err" , err )
2021-01-07 10:23:50 +02:00
conn . conn . Close ( )
2017-04-11 02:25:53 +03:00
}
}
f . lock . RUnlock ( )
}
}
}
2017-10-16 16:30:13 +03:00
2017-10-17 12:08:57 +03:00
// sends transmits a data packet to the remote end of the websocket, but also
// setting a write deadline to prevent waiting forever on the node.
2021-01-07 10:23:50 +02:00
func send ( conn * wsConn , value interface { } , timeout time . Duration ) error {
2017-10-17 12:08:57 +03:00
if timeout == 0 {
timeout = 60 * time . Second
}
2021-01-07 10:23:50 +02:00
conn . wlock . Lock ( )
defer conn . wlock . Unlock ( )
conn . conn . SetWriteDeadline ( time . Now ( ) . Add ( timeout ) )
return conn . conn . WriteJSON ( value )
2017-10-17 12:08:57 +03:00
}
// sendError transmits an error to the remote end of the websocket, also setting
// the write deadline to 1 second to prevent waiting forever.
2021-01-07 10:23:50 +02:00
func sendError ( conn * wsConn , err error ) error {
2017-10-17 12:08:57 +03:00
return send ( conn , map [ string ] string { "error" : err . Error ( ) } , time . Second )
}
// sendSuccess transmits a success message to the remote end of the websocket, also
// setting the write deadline to 1 second to prevent waiting forever.
2021-01-07 10:23:50 +02:00
func sendSuccess ( conn * wsConn , msg string ) error {
2017-10-17 12:08:57 +03:00
return send ( conn , map [ string ] string { "success" : msg } , time . Second )
}
2017-10-16 16:30:13 +03:00
// authTwitter tries to authenticate a faucet request using Twitter posts, returning
2020-12-11 15:05:39 +05:30
// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success.
2021-01-04 13:58:46 +02:00
func authTwitter ( url string , tokenV1 , tokenV2 string ) ( string , string , string , common . Address , error ) {
2017-10-16 16:30:13 +03:00
// Ensure the user specified a meaningful URL, no fancy nonsense
parts := strings . Split ( url , "/" )
if len ( parts ) < 4 || parts [ len ( parts ) - 2 ] != "status" {
2019-11-29 11:38:34 +01:00
//lint:ignore ST1005 This error is to be displayed in the browser
2020-12-11 15:05:39 +05:30
return "" , "" , "" , common . Address { } , errors . New ( "Invalid Twitter status URL" )
}
2021-01-04 13:58:46 +02:00
// Strip any query parameters from the tweet id and ensure it's numeric
tweetID := strings . Split ( parts [ len ( parts ) - 1 ] , "?" ) [ 0 ]
if ! regexp . MustCompile ( "^[0-9]+$" ) . MatchString ( tweetID ) {
return "" , "" , "" , common . Address { } , errors . New ( "Invalid Tweet URL" )
}
2020-12-11 15:05:39 +05:30
// Twitter's API isn't really friendly with direct links.
// It is restricted to 300 queries / 15 minute with an app api key.
// Anything more will require read only authorization from the users and that we want to avoid.
2021-01-04 13:58:46 +02:00
// If Twitter bearer token is provided, use the API, selecting the version
// the user would prefer (currently there's a limit of 1 v2 app / developer
// but unlimited v1.1 apps).
switch {
case tokenV1 != "" :
return authTwitterWithTokenV1 ( tweetID , tokenV1 )
case tokenV2 != "" :
return authTwitterWithTokenV2 ( tweetID , tokenV2 )
2017-10-16 16:30:13 +03:00
}
2022-08-19 01:00:21 -05:00
// Twitter API token isn't provided so we just load the public posts
2020-06-04 08:59:26 +03:00
// and scrape it for the Ethereum address and profile URL. We need to load
// the mobile page though since the main page loads tweet contents via JS.
url = strings . Replace ( url , "https://twitter.com/" , "https://mobile.twitter.com/" , 1 )
2017-10-16 16:30:13 +03:00
res , err := http . Get ( url )
if err != nil {
2020-12-11 15:05:39 +05:30
return "" , "" , "" , common . Address { } , err
2017-10-16 16:30:13 +03:00
}
defer res . Body . Close ( )
2018-02-22 13:20:36 +02:00
// Resolve the username from the final redirect, no intermediate junk
parts = strings . Split ( res . Request . URL . String ( ) , "/" )
if len ( parts ) < 4 || parts [ len ( parts ) - 2 ] != "status" {
2019-11-29 11:38:34 +01:00
//lint:ignore ST1005 This error is to be displayed in the browser
2020-12-11 15:05:39 +05:30
return "" , "" , "" , common . Address { } , errors . New ( "Invalid Twitter status URL" )
2018-02-22 13:20:36 +02:00
}
username := parts [ len ( parts ) - 3 ]
2022-05-16 11:59:35 +02:00
body , err := io . ReadAll ( res . Body )
2017-10-16 16:30:13 +03:00
if err != nil {
2020-12-11 15:05:39 +05:30
return "" , "" , "" , common . Address { } , err
2017-10-16 16:30:13 +03:00
}
address := common . HexToAddress ( string ( regexp . MustCompile ( "0x[0-9a-fA-F]{40}" ) . Find ( body ) ) )
if address == ( common . Address { } ) {
2019-11-29 11:38:34 +01:00
//lint:ignore ST1005 This error is to be displayed in the browser
2023-04-06 11:17:53 +08:00
return "" , "" , "" , common . Address { } , errors . New ( "No BNB Smart Chain address found to fund" )
2017-10-16 16:30:13 +03:00
}
var avatar string
2021-10-13 18:31:02 +03:00
if parts = regexp . MustCompile ( ` src="([^"]+twimg\.com/profile_images[^"]+)" ` ) . FindStringSubmatch ( string ( body ) ) ; len ( parts ) == 2 {
2017-10-16 16:30:13 +03:00
avatar = parts [ 1 ]
}
2020-12-11 15:05:39 +05:30
return username + "@twitter" , username , avatar , address , nil
}
2021-01-04 13:58:46 +02:00
// authTwitterWithTokenV1 tries to authenticate a faucet request using Twitter's v1
// API, returning the user id, username, avatar URL and Ethereum address to fund on
// success.
func authTwitterWithTokenV1 ( tweetID string , token string ) ( string , string , string , common . Address , error ) {
// Query the tweet details from Twitter
url := fmt . Sprintf ( "https://api.twitter.com/1.1/statuses/show.json?id=%s" , tweetID )
2023-01-24 17:12:25 +08:00
req , err := http . NewRequest ( http . MethodGet , url , nil )
2021-01-04 13:58:46 +02:00
if err != nil {
return "" , "" , "" , common . Address { } , err
}
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , token ) )
res , err := http . DefaultClient . Do ( req )
if err != nil {
return "" , "" , "" , common . Address { } , err
}
defer res . Body . Close ( )
2020-12-11 15:05:39 +05:30
2021-01-04 13:58:46 +02:00
var result struct {
Text string ` json:"text" `
User struct {
ID string ` json:"id_str" `
Username string ` json:"screen_name" `
Avatar string ` json:"profile_image_url" `
} ` json:"user" `
2020-12-11 15:05:39 +05:30
}
2021-01-04 13:58:46 +02:00
err = json . NewDecoder ( res . Body ) . Decode ( & result )
if err != nil {
return "" , "" , "" , common . Address { } , err
}
address := common . HexToAddress ( regexp . MustCompile ( "0x[0-9a-fA-F]{40}" ) . FindString ( result . Text ) )
if address == ( common . Address { } ) {
//lint:ignore ST1005 This error is to be displayed in the browser
return "" , "" , "" , common . Address { } , errors . New ( "No Ethereum address found to fund" )
}
return result . User . ID + "@twitter" , result . User . Username , result . User . Avatar , address , nil
}
2020-12-11 15:05:39 +05:30
2021-01-04 13:58:46 +02:00
// authTwitterWithTokenV2 tries to authenticate a faucet request using Twitter's v2
// API, returning the user id, username, avatar URL and Ethereum address to fund on
// success.
func authTwitterWithTokenV2 ( tweetID string , token string ) ( string , string , string , common . Address , error ) {
2020-12-11 15:05:39 +05:30
// Query the tweet details from Twitter
2021-01-04 13:58:46 +02:00
url := fmt . Sprintf ( "https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url" , tweetID )
2023-01-24 17:12:25 +08:00
req , err := http . NewRequest ( http . MethodGet , url , nil )
2020-12-11 15:05:39 +05:30
if err != nil {
return "" , "" , "" , common . Address { } , err
}
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , token ) )
res , err := http . DefaultClient . Do ( req )
if err != nil {
return "" , "" , "" , common . Address { } , err
}
defer res . Body . Close ( )
var result struct {
Data struct {
AuthorID string ` json:"author_id" `
Text string ` json:"text" `
} ` json:"data" `
Includes struct {
Users [ ] struct {
2021-01-04 13:58:46 +02:00
ID string ` json:"id" `
Username string ` json:"username" `
Avatar string ` json:"profile_image_url" `
2020-12-11 15:05:39 +05:30
} ` json:"users" `
} ` json:"includes" `
}
err = json . NewDecoder ( res . Body ) . Decode ( & result )
if err != nil {
return "" , "" , "" , common . Address { } , err
}
address := common . HexToAddress ( regexp . MustCompile ( "0x[0-9a-fA-F]{40}" ) . FindString ( result . Data . Text ) )
if address == ( common . Address { } ) {
//lint:ignore ST1005 This error is to be displayed in the browser
return "" , "" , "" , common . Address { } , errors . New ( "No Ethereum address found to fund" )
}
2021-01-04 13:58:46 +02:00
return result . Data . AuthorID + "@twitter" , result . Includes . Users [ 0 ] . Username , result . Includes . Users [ 0 ] . Avatar , address , nil
2017-10-16 16:30:13 +03:00
}
// authFacebook tries to authenticate a faucet request using Facebook posts,
// returning the username, avatar URL and Ethereum address to fund on success.
func authFacebook ( url string ) ( string , string , common . Address , error ) {
// Ensure the user specified a meaningful URL, no fancy nonsense
2020-11-24 17:33:58 +08:00
parts := strings . Split ( strings . Split ( url , "?" ) [ 0 ] , "/" )
if parts [ len ( parts ) - 1 ] == "" {
parts = parts [ 0 : len ( parts ) - 1 ]
}
2017-10-16 16:30:13 +03:00
if len ( parts ) < 4 || parts [ len ( parts ) - 2 ] != "posts" {
2019-11-29 11:38:34 +01:00
//lint:ignore ST1005 This error is to be displayed in the browser
2017-10-16 16:30:13 +03:00
return "" , "" , common . Address { } , errors . New ( "Invalid Facebook post URL" )
}
username := parts [ len ( parts ) - 3 ]
// Facebook's Graph API isn't really friendly with direct links. Still, we don't
// want to do ask read permissions from users, so just load the public posts and
// scrape it for the Ethereum address and profile URL.
2021-01-07 13:04:20 +02:00
//
// Facebook recently changed their desktop webpage to use AJAX for loading post
// content, so switch over to the mobile site for now. Will probably end up having
// to use the API eventually.
crawl := strings . Replace ( url , "www.facebook.com" , "m.facebook.com" , 1 )
res , err := http . Get ( crawl )
2017-10-16 16:30:13 +03:00
if err != nil {
return "" , "" , common . Address { } , err
}
defer res . Body . Close ( )
2022-05-16 11:59:35 +02:00
body , err := io . ReadAll ( res . Body )
2017-10-16 16:30:13 +03:00
if err != nil {
return "" , "" , common . Address { } , err
}
address := common . HexToAddress ( string ( regexp . MustCompile ( "0x[0-9a-fA-F]{40}" ) . Find ( body ) ) )
if address == ( common . Address { } ) {
2019-11-29 11:38:34 +01:00
//lint:ignore ST1005 This error is to be displayed in the browser
2023-08-23 17:46:08 +08:00
return "" , "" , common . Address { } , errors . New ( "No BNB Smart Chain address found to fund. Please check the post URL and verify that it can be viewed publicly." )
2017-10-16 16:30:13 +03:00
}
var avatar string
2021-10-13 18:31:02 +03:00
if parts = regexp . MustCompile ( ` src="([^"]+fbcdn\.net[^"]+)" ` ) . FindStringSubmatch ( string ( body ) ) ; len ( parts ) == 2 {
2017-10-16 16:30:13 +03:00
avatar = parts [ 1 ]
}
return username + "@facebook" , avatar , address , nil
}
2017-10-23 10:22:23 +03:00
// authNoAuth tries to interpret a faucet request as a plain Ethereum address,
// without actually performing any remote authentication. This mode is prone to
// Byzantine attack, so only ever use for truly private networks.
func authNoAuth ( url string ) ( string , string , common . Address , error ) {
address := common . HexToAddress ( regexp . MustCompile ( "0x[0-9a-fA-F]{40}" ) . FindString ( url ) )
if address == ( common . Address { } ) {
2019-11-29 11:38:34 +01:00
//lint:ignore ST1005 This error is to be displayed in the browser
2023-04-06 11:17:53 +08:00
return "" , "" , common . Address { } , errors . New ( "No BNB Smart Chain address found to fund" )
2017-10-23 10:22:23 +03:00
}
return address . Hex ( ) + "@noauth" , "" , address , nil
}
2021-04-14 03:21:46 +05:30
// getGenesis returns a genesis based on input args
2023-06-02 14:03:21 +03:00
func getGenesis ( genesisFlag string , goerliFlag bool , sepoliaFlag bool ) ( * core . Genesis , error ) {
2021-04-14 03:21:46 +05:30
switch {
2022-04-23 22:53:21 +08:00
case genesisFlag != "" :
2021-04-14 03:21:46 +05:30
var genesis core . Genesis
2022-04-23 22:53:21 +08:00
err := common . LoadJSON ( genesisFlag , & genesis )
2021-04-14 03:21:46 +05:30
return & genesis , err
default :
2023-05-24 18:21:29 +08:00
return nil , errors . New ( "no genesis flag provided" )
2021-04-14 03:21:46 +05:30
}
}