From 962ce58aaf0a8ec7b1a769b8f4e1bb62dc72d3a5 Mon Sep 17 00:00:00 2001 From: Giacomo Licari Date: Mon, 3 Jun 2024 17:42:05 +0200 Subject: [PATCH] Add waiting period to funds claiming --- README.md | 12 +++++++- api/api/routes.py | 3 +- api/api/services/csrf.py | 30 ++++++++++++++----- api/api/services/validator.py | 7 ++++- api/scripts/local_test_run.sh | 8 ++--- api/test.db | Bin 0 -> 53248 bytes api/tests/conftest.py | 13 +++++--- api/tests/test_api.py | 36 +++++++++++++++-------- api/tests/test_api_claim_rate_limit.py | 15 ++++++---- api/tests/test_csrf.py | 21 +++++++++---- app/src/App.tsx | 15 ++++++---- app/src/components/FaucetForm/Faucet.tsx | 25 +++++++++++----- 12 files changed, 132 insertions(+), 53 deletions(-) create mode 100644 api/test.db diff --git a/README.md b/README.md index 71e9820..0a754ba 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,17 @@ flask -A api create_enabled_token GNO 10200 0x19C653Da7c37c66208fbfbE8908A5051B5 flask -A api create_enabled_token xDAI 10200 0x0000000000000000000000000000000000000000 0.01 native ``` -Once enabled, the token wil appear in the list of enabled tokens on the endpoint `api/v1/info`. +Once enabled, the token will appear in the list of enabled tokens on the endpoint `api/v1/info`. + +#### Change maximum daily amounts per user + +If you want to change the amount you are giving out for a specific token, make sure you have sqlite +installed on the server, e.g. apk update && apk add sqlite. + +Enter the database: `sqlite path/to/database` + +Search for the token to update: `select chain_id, max_amount_day from tokens where name = 'xDAI'` +Update amount: `update tokens set max_amount_day = 0.00015 where chain_id = 100;` ## ReactJS Frontend diff --git a/api/api/routes.py b/api/api/routes.py index ed8f566..1f6f927 100644 --- a/api/api/routes.py +++ b/api/api/routes.py @@ -38,7 +38,8 @@ def info(): chainName=current_app.config['FAUCET_CHAIN_NAME'], faucetAddress=current_app.config['FAUCET_ADDRESS'], csrfToken=csrf_item.token, - csrfRequestId=csrf_item.request_id + csrfRequestId=csrf_item.request_id, + csrfTimestamp=csrf_item.timestamp ), 200 diff --git a/api/api/services/csrf.py b/api/api/services/csrf.py index d6811f1..6a20167 100644 --- a/api/api/services/csrf.py +++ b/api/api/services/csrf.py @@ -1,14 +1,21 @@ +# from api.settings import CSRF_TIMESTAMP_MAX_SECONDS import random +from datetime import datetime from Crypto.Cipher import PKCS1_OAEP from Crypto.PublicKey import RSA +# Waiting period: the minimum time interval between UI asks for the CSFR token +# and the time it asks for funds. +CSRF_TIMESTAMP_MIN_SECONDS = 15 + class CSRFTokenItem: - def __init__(self, request_id, token): + def __init__(self, request_id, token, timestamp): self.request_id = request_id self.token = token + self.timestamp = timestamp class CSRFToken: @@ -17,23 +24,30 @@ def __init__(self, privkey, salt): self._pubkey = self._privkey.publickey() self._salt = salt - def generate_token(self): + def generate_token(self, timestamp=None): request_id = '%d' % random.randint(0, 1000) - data_to_encrypt = '%s%s' % (request_id, self._salt) + if not timestamp: + timestamp = datetime.now().timestamp() + data_to_encrypt = '{"requestId":"%s","salt":"%s","timestamp":"%f"}' % (request_id, self._salt, timestamp) cipher_rsa = PKCS1_OAEP.new(self._pubkey) token = cipher_rsa.encrypt(data_to_encrypt.encode()) - return CSRFTokenItem(request_id, token.hex()) + return CSRFTokenItem(request_id, token.hex(), timestamp) - def validate_token(self, request_id, token): + def validate_token(self, request_id, token, timestamp): try: cipher_rsa = PKCS1_OAEP.new(self._privkey) decrypted_text = cipher_rsa.decrypt(bytes.fromhex(token)).decode() - - expected_text = '%s%s' % (request_id, self._salt) + expected_text = '{"requestId":"%s","salt":"%s","timestamp":"%f"}' % (request_id, self._salt, timestamp) if decrypted_text == expected_text: - return True + # Check that timestamp is OK, the diff between now() and creation time in seconds + # must be greater than min. waiting period. + # Waiting period: the minimum time interval between UI asks for the CSFR token and the time it asks for funds. + seconds_diff = (datetime.now()-datetime.fromtimestamp(timestamp)).total_seconds() + if seconds_diff > CSRF_TIMESTAMP_MIN_SECONDS: + return True + return False return False except Exception: return False diff --git a/api/api/services/validator.py b/api/api/services/validator.py index 9347a5b..8972c4f 100644 --- a/api/api/services/validator.py +++ b/api/api/services/validator.py @@ -86,7 +86,12 @@ def csrf_validation(self): self.errors.append('Bad request') self.http_return_code = 400 - csrf_valid = self.csrf.validate_token(request_id, token) + timestamp = self.request_data.get('timestamp', None) + if not timestamp: + self.errors.append('Bad request') + self.http_return_code = 400 + + csrf_valid = self.csrf.validate_token(request_id, token, timestamp) if not csrf_valid: self.errors.append('Bad request') self.http_return_code = 400 diff --git a/api/scripts/local_test_run.sh b/api/scripts/local_test_run.sh index 34e9d33..46d3312 100644 --- a/api/scripts/local_test_run.sh +++ b/api/scripts/local_test_run.sh @@ -1,6 +1,6 @@ -FLASK_APP=api FAUCET_DATABASE_URI=sqlite:///test.db python3 -m flask db upgrade -FLASK_APP=api FAUCET_DATABASE_URI=sqlite:///test.db python3 -m flask create_enabled_token xDAI 10200 0x0000000000000000000000000000000000000000 10 native -FLASK_APP=api FAUCET_DATABASE_URI=sqlite:///test.db python3 -m flask create_access_keys +FLASK_APP=api FAUCET_DATABASE_URI=sqlite:///$(pwd)/test.db python3 -m flask db upgrade +FLASK_APP=api FAUCET_DATABASE_URI=sqlite:///$(pwd)/test.db python3 -m flask create_enabled_token xDAI 10200 0x0000000000000000000000000000000000000000 0.0001 native +FLASK_APP=api FAUCET_DATABASE_URI=sqlite:///$(pwd)/test.db python3 -m flask create_access_keys # Take note of the access keys # Run API on port 3000 -FLASK_APP=api FAUCET_DATABASE_URI=sqlite:///test.db python3 -m flask run -p 3000 \ No newline at end of file +FLASK_APP=api FAUCET_DATABASE_URI=sqlite:///$(pwd)/test.db python3 -m flask run -p 8000 \ No newline at end of file diff --git a/api/test.db b/api/test.db new file mode 100644 index 0000000000000000000000000000000000000000..40764bf8796deea942278fe4324e4f0032a7df83 GIT binary patch literal 53248 zcmeI*&rjP{00(e8p$SPzO|@jvX=1-0e*V0l zPQaO;o36KrSX*tbXf1J^8{~MNdq)&Gjyp|%V)Vy(2=qmtvq7JE*K5C*r@8aDKacgt zxPiXsoDdTp4SW~-A@r<&qrcSmd|)Z^MP%{R+kT0$|78=nf9_N`J~6>RxY5$8%S6?d z$x5}ZtF4tawbj%brlz;*s}0jPpntNWlok|mp>%Ot5q+bHiD;y5h*RYS<%&`f%d-n& zd2xFBoEVMV)XbaW`=!d{yQNBEJe_0$n?$d#)k&kZ9V6q3p{=a0)8Na~vn86RE8o`Y z5@|TetOK>~tPy8BAxrFmG+igAsV(tfW{`E-MwKJgLl!-4IjsMBC%Ywz6vf&mPR)#T=v)DWMrglUjSv zY5f*MpxW(Xu{<@msE7$`BN{G!|2%-H^SS4Xs>Pbe_|?mZ_uF0}eL&qu=X;bHz> zPxW`xm{H#fw`;l^(>kUz?#MZII4}wA)-6{7w_|!oDAKS7PD8vnJ3FnE%1rO~tu?l~ zm?G`aExO^R+FLM(ttW(n^?%D{^b0&!Zy035ytPbM2CBB+TD7-TX0+BqgCTo%%AO3x zr-uE~xXY4uZ46|i`%NahlARoQo%@0fLoyBg$)4`n}rcQg}cXjxK9|OIU_WwI|#{TB! z$%J1MW`+WH^HXKtpx9(A6#qc>%hJ`$o$=n?^E>-aY@R+5jt>p-Tc5Ohh_z}n+e?KI z+v)8PtGhR}75{~tGhOUBjdqqE+k?4xF}d4dzK>M^v%>VuDC}I&Sqm}CJZD`LO^$^^ z@rfZn*mhbv(AXeh;U6w*O^RD5y=x*@4X~>qO}}dlhU257{8rdbR9#-xmx!URo1|$n z8|=W#1nn~N?&W+?Yi71Qj3!;GM3Z-ud^kQf#&1U2Hl3@R+w?5yRgH78)7cC#&CL^N zoopwb+E`!NZb9DVfUBB4IjCz(Iy>*X@}p(F5D3NR#{4SXo0;s{5u|f>zcqhb7x=vJ z7f1i$0Rad=00Izz00bZa0SG_<0uX?}F%dY$2S#doZrmuOGwGUSMCp0q`u~_zE=~gi z5P$##AOHafKmY;|fB*y_&=C-VBN6xYzxDZlP}t;zpM|gK6CMzN00bZa0SG_<0uX=z z1Rwwb2poz)A{gSr;Z$Ja-iN_y5-Q|BDs`=^y|B2tWV=5P$## zAOHafKmY;|I5Yu;55F4FBH^KpOQosNr_zSB&&R|Yx!?YHb28Pw2X*`J|DSNelS3;h zGKK&IAOHafKmY;|fB*y_009U<;J^h^K`pZXcLVG%_!~U%LLfs3KmY;|fB*y_009U< z00Izz00dql0bKvTM3sS}KmY;|fB*y_009U<00Izz00a(6pnd=U>zweT@TGA5kPd=u zApijgKmY;|fB*y_009U<00PHaAQS9M%|yI+>C0ygS(4>Kttu7tf+ooYDV@>unwHOJ zWi6v;0-aRMJ^Z9%p`c4rwP=hJl9Nc4rZdt~l}5@Hh@Q>xb}G*u{&M4T(U9`B@hma) ze6gq%NPavo$;F%`R}H<|zW@JYPIxT*BK&!*D+4D70SG_<0uX=z1Rwwb2tWV=5IDjD zXM(Ao?*RyQb~0}u1YQZIdcFamef|HC6TTOo2@j7j1Bwm-2tWV=5P$##AOHafKmY;| zI8p*{1!wrZenTK=McV5Z0s_OqnaF-V|L1W3za!N`P*?~+00Izz00bZa0SG_<0uX?} He--!_m?b;h literal 0 HcmV?d00001 diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 142a533..c02621b 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -1,5 +1,6 @@ import os -from unittest import TestCase, TestResult, mock +from unittest import TestCase, mock +from datetime import datetime from api.services import CSRF, Strategy from api.services.database import Token, db @@ -13,6 +14,7 @@ class BaseTest(TestCase): + valid_csrf_timestamp = datetime(2020, 1, 18, 9, 30, 0).timestamp() def mock_claim_native(self, *args): tx_hash = '0x0' + '%d' % self.native_tx_counter * 63 @@ -73,7 +75,8 @@ def setUp(self): self.csrf = CSRF.instance # use same token for the whole test - self.csrf_token = self.csrf.generate_token() + # use a timestamp that would be actually validated by the CSRF class. + self.csrf_token = self.csrf.generate_token(timestamp=self.valid_csrf_timestamp) def tearDown(self): ''' @@ -105,7 +108,8 @@ def setUp(self): self.csrf = CSRF.instance # use same token for the whole test - self.csrf_token = self.csrf.generate_token() + # use a timestamp that would be actually validated by the CSRF class. + self.csrf_token = self.csrf.generate_token(timestamp=self.valid_csrf_timestamp) class RateLimitIPorAddressBaseTest(BaseTest): @@ -128,4 +132,5 @@ def setUp(self): self.csrf = CSRF.instance # use same token for the whole test - self.csrf_token = self.csrf.generate_token() \ No newline at end of file + # use a timestamp that would be actually validated by the CSRF class. + self.csrf_token = self.csrf.generate_token(timestamp=self.valid_csrf_timestamp) diff --git a/api/tests/test_api.py b/api/tests/test_api.py index 4df9f36..04d30b1 100644 --- a/api/tests/test_api.py +++ b/api/tests/test_api.py @@ -35,7 +35,8 @@ def test_ask_route_parameters(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': NATIVE_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -48,7 +49,8 @@ def test_ask_route_parameters(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY + 1, 'recipient': ZERO_ADDRESS, 'tokenAddress': NATIVE_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -60,7 +62,8 @@ def test_ask_route_parameters(self): 'chainId': FAUCET_CHAIN_ID, 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY + 1, 'tokenAddress': NATIVE_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -73,7 +76,8 @@ def test_ask_route_parameters(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY + 1, 'recipient': 'not an address', 'tokenAddress': NATIVE_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -85,7 +89,8 @@ def test_ask_route_parameters(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': '0x00000123', 'tokenAddress': ERC20_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -97,7 +102,8 @@ def test_ask_route_parameters(self): 'chainId': FAUCET_CHAIN_ID, 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY + 1, 'recipient': ZERO_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -110,7 +116,8 @@ def test_ask_route_parameters(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY + 1, 'recipient': ZERO_ADDRESS, 'tokenAddress': 'non existing token address', - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -123,7 +130,8 @@ def test_ask_route_native_transaction(self): 'amount': DEFAULT_NATIVE_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': NATIVE_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -140,7 +148,8 @@ def test_ask_route_token_transaction(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': '0x' + '1234' * 10, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -152,7 +161,8 @@ def test_ask_route_token_transaction(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': ERC20_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -168,7 +178,8 @@ def test_ask_route_blocked_users(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': ERC20_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -184,7 +195,8 @@ def test_ask_route_blocked_users(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': ERC20_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.csrf_token.timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) diff --git a/api/tests/test_api_claim_rate_limit.py b/api/tests/test_api_claim_rate_limit.py index df7bb5f..fc3143f 100644 --- a/api/tests/test_api_claim_rate_limit.py +++ b/api/tests/test_api_claim_rate_limit.py @@ -20,7 +20,8 @@ def test_ask_route_limit_by_ip(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': ERC20_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.valid_csrf_timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -33,7 +34,8 @@ def test_ask_route_limit_by_ip(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': ERC20_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.valid_csrf_timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -49,7 +51,8 @@ def test_ask_route_limit_by_ip_or_address(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': ERC20_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.valid_csrf_timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -66,7 +69,8 @@ def test_ask_route_limit_by_ip_or_address(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': ERC20_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.valid_csrf_timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) @@ -84,7 +88,8 @@ def test_ask_route_limit_by_ip_or_address(self): 'amount': DEFAULT_ERC20_MAX_AMOUNT_PER_DAY, 'recipient': ZERO_ADDRESS, 'tokenAddress': ERC20_TOKEN_ADDRESS, - 'requestId': self.csrf_token.request_id + 'requestId': self.csrf_token.request_id, + 'timestamp': self.valid_csrf_timestamp }, headers={ 'X-CSRFToken': self.csrf_token.token }) diff --git a/api/tests/test_csrf.py b/api/tests/test_csrf.py index f40fea2..c7c92f4 100644 --- a/api/tests/test_csrf.py +++ b/api/tests/test_csrf.py @@ -1,19 +1,30 @@ from .conftest import BaseTest +from datetime import datetime + class TestCSRF(BaseTest): def test_values(self): - token_obj = self.csrf.generate_token() + timestamp = datetime(2020, 1, 18, 9, 30, 0).timestamp() + token_obj = self.csrf.generate_token(timestamp=timestamp) self.assertTrue( - self.csrf.validate_token(token_obj.request_id, token_obj.token) + self.csrf.validate_token(token_obj.request_id, token_obj.token, token_obj.timestamp) + ) + self.assertFalse( + self.csrf.validate_token('myfakeid', token_obj.token, token_obj.timestamp) ) self.assertFalse( - self.csrf.validate_token('myfakeid', token_obj.token) + self.csrf.validate_token('myfakeid', 'myfaketoken', token_obj.timestamp) ) self.assertFalse( - self.csrf.validate_token('myfakeid', 'myfaketoken') + self.csrf.validate_token(token_obj.request_id, 'myfaketoken', token_obj.timestamp) ) + # test with timestamp for which diff between now() and creation time in seconds + # is lower than min. waiting period. + # Validation must return False since time interval is lower than mimimum waiting period. + timestamp = datetime.now().timestamp() + token_obj = self.csrf.generate_token(timestamp=timestamp) self.assertFalse( - self.csrf.validate_token(token_obj.request_id, 'myfaketoken') + self.csrf.validate_token(token_obj.request_id, token_obj.token, token_obj.timestamp) ) diff --git a/app/src/App.tsx b/app/src/App.tsx index db02437..b89391a 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -10,6 +10,7 @@ import Faucet from "./components/FaucetForm/Faucet" interface CSRFInfo { csrfToken: string requestId: string + timestamp: number } const chainName:{ [key: string]: string }= { @@ -22,7 +23,7 @@ function App(): JSX.Element { const [loading, setLoading] = useState(true) const [enabledTokens, setEnabledTokens] = useState([]) const [faucetLoading, setFaucetLoading] = useState(true) - const [csrfInfo, setCSRFInfo] = useState({csrfToken: '', requestId: ''}) + const [csrfInfo, setCSRFInfo] = useState({csrfToken: '', requestId: '', timestamp: 0}) const getFaucetInfo = async () => { return axios.get(`${process.env.REACT_APP_FAUCET_API_URL}/info`) @@ -40,21 +41,24 @@ function App(): JSX.Element { csrfInfo.csrfToken = response.data.csrfToken, csrfInfo.requestId = response.data.csrfRequestId + csrfInfo.timestamp = response.data.csrfTimestamp setCSRFInfo(csrfInfo) - }) .catch(() => { toast.error("Network error") }) .finally(() => { - setFaucetLoading(false) - setLoading(false) + // 5 seconds waiting period + setTimeout(function () { + setFaucetLoading(false) + setLoading(false) + }, 5000) }) }, []) const title = faucetLoading ? "FAUCET" : `${chainName[chainId]} CHAIN` const subtitle = faucetLoading - ? "Loading..." + ? "Application loading..." : (chainId === 100 ? "Faucet" : "Testnet Faucet") return ( @@ -80,6 +84,7 @@ function App(): JSX.Element { setLoading={setLoading} csrfToken={csrfInfo.csrfToken} requestId={csrfInfo.requestId} + timestamp={csrfInfo.timestamp} />

Want more{chainId === 100 ? '?' : ' on Gnosis Chain?'}

    diff --git a/app/src/components/FaucetForm/Faucet.tsx b/app/src/components/FaucetForm/Faucet.tsx index 994d7cb..0d7e038 100644 --- a/app/src/components/FaucetForm/Faucet.tsx +++ b/app/src/components/FaucetForm/Faucet.tsx @@ -12,7 +12,8 @@ interface FaucetProps { chainId: number, setLoading: Dispatch>, csrfToken: string, - requestId: string + requestId: string, + timestamp: number } const blockscanner:{ [key: string]: string }= { @@ -20,12 +21,13 @@ const blockscanner:{ [key: string]: string }= { 10200: "https://gnosis-chiado.blockscout.com/tx/" } -function Faucet({ enabledTokens, chainId, setLoading, csrfToken, requestId }: FaucetProps): JSX.Element { +function Faucet({ enabledTokens, chainId, setLoading, csrfToken, requestId, timestamp }: FaucetProps): JSX.Element { const [walletAddress, setWalletAddress] = useState("") const [token, setToken] = useState(null) const [captchaToken, setCaptchaToken] = useState(null) const [txHash, setTxHash] = useState(null) const [windowWidth, setWindowWidth] = useState(window.innerWidth) + const [requestOngoing, setRequestOngoing] = useState(false) const captchaRef = useRef(null) @@ -88,6 +90,7 @@ function Faucet({ enabledTokens, chainId, setLoading, csrfToken, requestId }: Fa if (token) { setLoading(true) + setRequestOngoing(true) try { const requestData = { recipient: walletAddress, @@ -95,14 +98,16 @@ function Faucet({ enabledTokens, chainId, setLoading, csrfToken, requestId }: Fa tokenAddress: token.address, chainId: chainId, amount: token.maximumAmount, - requestId: requestId + requestId: requestId, + timestamp: timestamp } const headers = { 'X-CSRFToken': csrfToken } - axios + setTimeout(function() { + axios .post(apiURL, requestData, { headers: headers }) @@ -117,18 +122,24 @@ function Faucet({ enabledTokens, chainId, setLoading, csrfToken, requestId }: Fa setCaptchaToken("") captchaRef.current?.resetCaptcha() + setLoading(false) + setRequestOngoing(false) + toast.success("Token sent to your wallet address") setTxHash(`${response.data.transactionHash}`) }) .catch((error) => { toast.error(formatErrors(error.response.data.errors)) + setLoading(false) + setRequestOngoing(false) }) + }, 10000) // 10 seconds delay } catch (error) { if (error instanceof Error) { toast.error(error.message) } - } finally { setLoading(false) + setRequestOngoing(false) } } } @@ -177,8 +188,8 @@ function Faucet({ enabledTokens, chainId, setLoading, csrfToken, requestId }: Fa captchaRef={captchaRef} /> - {txHash &&