https://github.com/omgnetwork/boba-faucet
Boba faucet using Turing
https://github.com/omgnetwork/boba-faucet
Last synced: 4 months ago
JSON representation
Boba faucet using Turing
- Host: GitHub
- URL: https://github.com/omgnetwork/boba-faucet
- Owner: omgnetwork
- Created: 2022-02-10T18:43:19.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2022-02-27T03:39:20.000Z (over 4 years ago)
- Last Synced: 2025-06-01T04:48:28.277Z (about 1 year ago)
- Language: JavaScript
- Size: 1.14 MB
- Stars: 0
- Watchers: 2
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Boba Faucet
- [Overview](#Overview)
- [Directory Structure](#Directory-Structure)
- [Specification](#Specification)
- [Impementation](#Implementaion)
* [Step 1: Creating API endpoints](#Step1--Creating-API-endpoints)
* [Step2: Creating Boba Faucet Contract](#Step2--Creating-Boba-Faucet-Contract)
* [Step3: Funding Turing Helper Contract](#Step3--Funding-Turing-Helper-Contract)
## Overview
Boba Faucet is a system for getting Boba Rinkeby ETH and Boba Rinkeby Boba Token. It's implemented using the Turing. Turing is a system for interacting with the outside world from within solidity smart contracts.
Before claiming the token, users have to answer the CAPTCHA. The answer is hashed and compared off-chain via Turing. Once the answer is verified, the smart contract releases the funds.
## Directory Structure
* [`packages`](./packages): Contains all the typescript packages and contracts
* [`contracts`](./packages/contracts): Solidity smart contracts implementing the Boba Faucet
* [`gateway`](./packages/gate): The Boba Web faucet
* [`deployment`](./packages/deployment): Boba faucet contract addresses
* [`api`](./packages/api): Boba faucet backend API
## Specification
This procedure takes place in five steps:
1. User gets the CAPTCHA image and UUID of the image
The API GET request is sent to `https://api-turing.boba.network/get.catcha` on the frontend. The returned payload is
```js
{
"UUID": "BYTES32",
"imageBase64": "CAPTCHA IMAGE"
}
```
> The UUID and hashed CAPTCHA answer are stored in the AWS Redis.
2. User sends the transaction to the `Boba Faucet` contract with UUID and CAPTCHA answer
```javascript
const BobaFaucet = new ethers.Contract(
BOBA_FAUCET_CONTRACE_ADDRESS,
BobaFaucetJson.abi,
this.provider.getSigner()
)
const tx = await BobaFaucet.getBobaFaucet(uuid, answer)
await tx.wait()
```
3. Geth sends the request to the backend and retrieves the result
> The answer is hashed in the `Boba Faucet` contract first before sending it to backend API.
>
> ```
> bytes32 hashedKey = keccak256(abi.encodePacked(_key));
> bytes memory encRequest = abi.encodePacked(_uuid, hashedKey);
> bytes memory encResponse = turing.TuringTx(turingUrl, encRequest);
> ```
The POST request is sent to `https://api-turing.boba.network/verify.captcha` . It decodes the input and verifies the UUID with the hashed answer.
```python
paramsHexString = body['params'][0]
# Select uuid and answer
# paramsHexString[0: 66] is the length of input data
uuid = '0x' + paramsHexString[66: 130]
answer = '0x' + paramsHexString[130:]
# Get answer from Redis
keyInRedis = db.get(uuid)
# Return the payload
return returnPayload(keyInRedis.decode('utf-8') == key)
# Payload is built based on the result
def returnPayload(result):
# We return 0 or 1 using uint256
payload = '0x' + '{0:0{1}x}'.format(int(32), 64)
if result:
# Add UINT256 1 if the result is correct
payload += '{0:0{1}x}'.format(int(1), 64)
else:
# Add UINT256 0 if the result is wrong
payload += '{0:0{1}x}'.format(int(0), 64)
returnPayload = {
'statusCode': 200,
'body': json.dumps({ 'result': payload })
}
return returnPayload
```
4. Geth atomically revises the calldata
On the contract level, we only need to decode the result from the Turing request and release the funds if the answer is correct.
```solidity
// Decode the response from outside API
bytes memory encResponse = turing.TuringTx(turingUrl, encRequest);
uint256 result = abi.decode(encResponse,(uint256));
// Release the funds if it is correct
require(result == 1, 'Invalid key and UUID');
IERC20(BobaAddress).safeTransfer(msg.sender, BobaFaucetAmount);
```
5. User gets the funds if the answer is correct or the error message

## Implementation
### Step1: Creating API endpoints
Two simple API endpoints are created.
#### get.captcha
```python
image = ImageCaptcha(width=280, height=90)
# For image
uuid1 = uuid.uuid4()
imageStr = str(uuid1).split('-')[0]
imageStrBytes = Web3.solidityKeccak(['string'], [str(imageStr)]).hex()
# For key
uuid2 = uuid.uuid4()
keyBytes = Web3.solidityKeccak(['string'], [str(uuid2)]).hex()
# Use the first random string
imageData = image.generate(imageStr)
# Get base64
imageBase64 = base64.b64encode(imageData.getvalue())
# Read YML
with open("env.yml", 'r') as ymlfile:
config = yaml.load(ymlfile)
# Connect to Redis Elasticache
db = redis.StrictRedis(host=config.get('REDIS_URL'),
port=config.get('REDIS_PORT'))
# Store and set the expire time as 10 minutes
print('keyBytes: ', keyBytes, 'imageStrBytes: ', imageStrBytes, 'imageStr: ', imageStr)
db.set(keyBytes, imageStrBytes, ex=600)
payload = {'uuid': keyBytes, 'imageBase64': imageBase64.decode('utf-8') }
```
#### verify.captcha
```python
paramsHexString = body['params'][0]
uuid = '0x' + paramsHexString[66: 130]
key = '0x' + paramsHexString[130:]
# Read YML
with open("env.yml", 'r') as ymlfile:
config = yaml.load(ymlfile)
# Connect to Redis Elasticache
db = redis.StrictRedis(host=config.get('REDIS_URL'),
port=config.get('REDIS_PORT'))
# Store and set the expire time as 10 minutes
keyInRedis = db.get(uuid)
if keyInRedis:
print('keyInRedis: ', keyInRedis.decode('utf-8'), 'key: ', key)
return returnPayload(keyInRedis.decode('utf-8') == key)
else:
return returnPayload(False)
```
### Step2: Creating Boba Faucet Contract
The smart contract imports [Turing Helper Contract](https://github.com/omgnetwork/optimism-v2/blob/develop/packages/boba/turing/contracts/TuringHelper.sol), so it can interact with outside API endpoints.
```solidity
import './TuringHelper.sol';
contract BobaFaucet is Ownable {
// Turing
address public turingHelperAddress;
string public turingUrl;
TuringHelper public turing;
constructor(
address _turingHelperAddress,
string memory _turingUrl
) {
turingHelperAddress = _turingHelperAddress;
turing = TuringHelper(_turingHelperAddress);
turingUrl = _turingUrl;
}
function getBobaFaucet(
bytes32 _uuid,
string memory _key
) external {
require(BobaClaimRecords[msg.sender] + waitingPeriod < block.timestamp, 'Invalid request');
// The key is hashed
bytes32 hashedKey = keccak256(abi.encodePacked(_key));
uint256 result = _verifyKey(_uuid, hashedKey);
require(result == 1, 'Invalid key and UUID');
BobaClaimRecords[msg.sender] = block.timestamp;
IERC20(BobaAddress).safeTransfer(msg.sender, BobaFaucetAmount);
emit GetBobaFaucet(_uuid, hashedKey, msg.sender, BobaFaucetAmount, block.timestamp);
}
// Call Turing contract to get the result from the outside API endpoint
function _verifyKey(bytes32 _uuid, bytes32 _key) private returns (uint256) {
bytes memory encRequest = abi.encodePacked(_uuid, _key);
bytes memory encResponse = turing.TuringTx(turingUrl, encRequest);
uint256 result = abi.decode(encResponse,(uint256));
emit VerifyKey(turingUrl, _uuid, _key, result);
return result;
}
}
```
### Step3: Funding Turing Helper Contract
We charge 0.01 BOBA for each Turing request and it's based on the Turing Helper Contract.
```js
// Deploy Turing Helper Contract
const Factory__TuringHelperr = new ContractFactory(
TuringHelperJson.abi,
TuringHelperJson.bytecode,
(hre as any).deployConfig.deployer_l2
)
const TuringHelper = await Factory__TuringHelperr.deploy()
await TuringHelper.deployTransaction.wait()
console.log(`TuringHelper was deployed at: ${TuringHelper.address}`)
// Approve Boba Token to add funds to Turing Credit Contract
const BobaTuringCredit = new Contract(
(hre as any).deployConfig.BobaTuringCreditAddress,
BobaTuringCreditJson.abi,
(hre as any).deployConfig.deployer_l2
)
const approveTx = await L2BobaToken.approve(
BobaTuringCredit.address,
utils.parseEther('100')
)
await approveTx.wait()
const addCreditTx = await BobaTuringCredit.addBalanceTo(
utils.parseEther('100'),
TuringHelper.address
)
await addCreditTx.wait()
// Add permission for Boba Faucet contract
const addPermissionTx = await TuringHelper.addPermittedCaller(
BobaFaucet.address
)
await addPermissionTx.wait()
```