Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update x app template #23

Merged
merged 9 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 41 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ A blazing-fast React application powered by Vite:
An Express server crafted with TypeScript for robust API development:

- **Vechain SDK:** Seamlessly fetch and perform transactions with the VechainThor blockchain. [Learn more](https://docs.vechain.org/developer-resources/sdks-and-providers/sdk)
- **OpenAI GPT-Vision-Preview:** Integrate image analysis capabilities. [Explore here](https://platform.openai.com/docs/guides/vision)
- **OpenAI GPT-4o:** Integrate image analysis capabilities. [Explore here](https://platform.openai.com/docs/guides/vision)

### Contracts (apps/contracts) 📜

Expand All @@ -69,18 +69,11 @@ Shared configurations and utility functions to unify and simplify your developme

Configure your environment variables for seamless integration:

### Frontend

Place your `.env` files in `apps/frontend`:

- **VITE_RECAPTCHA_V3_SITE_KEY:** [Request your RecaptchaV3 site keys](https://developers.google.com/recaptcha/docs/v3)

### Backend

Store your environment-specific `.env` files in `apps/backend`. `.env.development.local` & `.env.production.local` allow for custom environment variables based on the environment:

- **OPENAI_API_KEY:** [Get your GPT-4 OpenAI key](https://platform.openai.com/api-keys) (Enable GPT-4 [here](https://help.openai.com/en/articles/7102672-how-can-i-access-gpt-4-gpt-4-turbo-and-gpt-4o))
- **RECAPTCHA_SECRET_KEY:** [Request your RecaptchaV3 site keys](https://developers.google.com/recaptcha/docs/v3)

### Contracts

Expand All @@ -96,41 +89,62 @@ Clone the repository and install dependencies with ease:
yarn install # Run this at the root level of the project
```

To distribute rewards this contract necesitates of a valid APP_ID provided by VeBetterDAO when joining the ecosystem.
In testnet you can generate the APP_ID by using the [VeBetterDAO sandbox](https://dev.testnet.governance.vebetterdao.org/).
This contract can be initially deployed without this information and DEFAULT_ADMIN_ROLE can update it later through {EcoEarn-setAppId}.
### Run locally

This contract must me set as a `rewardDistributor` inside the X2EarnApps contract to be able to send rewards to users and withdraw.
#### Deploy contracts

## Deploying Contracts 🚀
To start deploy contract locally you must run a local instance of the Vechain Thor blockchain. You can do this by running the following command:

Deploy your contracts effortlessly on either the Testnet or Solo networks.
If you are deploying on the Testnet, ensure you have the correct addresses in the `config-contracts` package (generated on the [VeBetterDAO sandbox](https://dev.testnet.governance.vebetterdao.org/)).
When deploying on the SOLO network the script will deploy for you the mocked VeBetterDAO contracts and generate an APP_ID.
```bash
yarn contracts:solo-up
```

This command will also start a local instance of the tools Insight (http://localhost:8080/) and Inspector (http://localhost:8081/).

### Deploying on Solo Network
At this point you can deploy the contracts to the local network by running:

```bash
yarn contracts:deploy:solo
```

### Deploying on Testnet
Once the deployment is completed successfully you can go ahead and start the frontend and backend:

> ⚠️ **Warning:**
> Remeber to set the OPENAI_API_KEY env variable in the backend .env.development.local file. Refer to the [Environment Variables](#environment-variables) section for more information.

```bash
yarn contracts:deploy:testnet
yarn dev
```

## Running on Solo Network 🔧
At this point you can access the frontend at [http://localhost:8082/](http://localhost:8082/).

Start uploading a receipt!

## Deploying on Testnet 🚀

Run the Frontend and Backend, connected to the Solo network and pointing to your deployed contracts. Ensure all environment variables are properly configured:
To distribute rewards this contract needs of a valid APP_ID provided by VeBetterDAO when joining the ecosystem.
In testnet you can generate the APP_ID by using the [VeBetterDAO sandbox](https://dev.testnet.governance.vebetterdao.org/).
This contract can be initially deployed without this information and DEFAULT_ADMIN_ROLE can update it later through {EcoEarn-setAppId}.

This contract must be set as a `rewardDistributor` inside the X2EarnApps contract to be able to send rewards to users and withdraw.

For deploying on Testnet you should check that you have the correct addresses in the `config-contracts` package (generated on the [VeBetterDAO sandbox](https://dev.testnet.governance.vebetterdao.org/)).

When deploying on the SOLO network the script will deploy for you the mocked VeBetterDAO contracts and generate an APP_ID.

Once everything is set up you can deploy the contracts to the testnet by running:

```bash
yarn dev
yarn contracts:deploy:testnet
```

### Setting up rewards
## Triggering Cycles and Setting the Rewards

The deployment scripts will configure the 1st cycle for you with a reward of 1000 tokens.

To start a new cycle and set the rewards you can follow the steps below:

## Testnet
### Testnet

Read the [VeBetterDAO documentation](https://docs.vebetterdao.org/developer-guides/test-environmnet) to learn how to set up rewards for your users and use the Testnet environment.

Expand All @@ -153,22 +167,18 @@ Now you just need to trigger cycles and set amount of rewards per cycle on your
4. Trigger a cycle:
![image](https://i.ibb.co/47V2Zjb/SCR-20240723-kkxx.png)

## Solo Network
### Solo Network

Since the Solo network is a local network with mocked VeBetterDAO contracts you can use the following steps to set up available rewards to distribute to users:

0. Ensure you are using a wallet with imported pre-funded accounts mnemonic into your wallet. Mnemoninc:
0. Ensure you are using a wallet with imported pre-funded accounts mnemonic into your wallet. Mnemonic:

```
denial kitchen pet squirrel other broom bar gas better priority spoil cross
```

1. Copy the `APP_ID` generated by the `contracts:deploy:solo` script and logged in the console.
2. Run `devpal`, a frontend tool to interact with your contracts:

```bash
npx [email protected] http://localhost:8669
```
2. Open the [inspector app](http://localhost:8081/#/contracts), running on localhost, that you can use to interact with your contracts.

3. Open the `Inspector` tab and perform the following actions:
4. Add the B3TR_Mock contract (get the address from the console logs and ABI from the `apps/contracts/artifacts/contracts/mock/B3TR_Mock.sol/B3TR_Mock.json` file)
Expand Down
30 changes: 0 additions & 30 deletions apps/backend/.env.development.local.example

This file was deleted.

4 changes: 2 additions & 2 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@
"@types/swagger-ui-express": "^4.1.3",
"@typescript-eslint/eslint-plugin": "^5.29.0",
"@typescript-eslint/parser": "^5.29.0",
"@vechain/sdk-core": "^1.0.0-beta.7",
"@vechain/sdk-network": "^1.0.0-beta.7",
"@vechain/sdk-core": "^1.0.0-beta.30",
"@vechain/sdk-network": "^1.0.0-beta.30",
"axios": "^1.6.7",
"cross-env": "^7.0.3",
"crypto": "^1.0.1",
Expand Down
1 change: 0 additions & 1 deletion apps/backend/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export const { OPENAI_API_KEY } = process.env;
export const { MAX_FILE_SIZE } = process.env;
export const { ADMIN_MNEMONIC, ADMIN_ADDRESS } = process.env;
export const { NETWORK_URL, NETWORK_TYPE } = process.env;
export const { RECAPTCHA_SECRET_KEY } = process.env;
export const { REWARD_AMOUNT } = process.env;

export const ADMIN_PRIVATE_KEY = mnemonic.derivePrivateKey(ADMIN_MNEMONIC.split(' '));
14 changes: 6 additions & 8 deletions apps/backend/src/controllers/submission.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,19 @@ import { OpenaiService } from '@/services/openai.service';
import { Submission } from '@/interfaces/submission.interface';
import { HttpException } from '@/exceptions/HttpException';
import { ContractsService } from '@/services/contracts.service';
import { CaptchaService } from '@/services/captcha.service';

export class SubmissionController {
public openai = Container.get(OpenaiService);
public contracts = Container.get(ContractsService);
public captcha = Container.get(CaptchaService);

public submitReceipt = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const body: Omit<Submission, 'timestamp'> = req.body;

if (!(await this.captcha.validateCaptcha(body.captcha))) {
throw new HttpException(400, 'Invalid captcha. Please try again.');
}

const submissionRequest: Submission = {
...body,
timestamp: Date.now(),
};

// Submission validation with smart contract
await this.contracts.validateSubmission(submissionRequest);

Expand All @@ -35,11 +28,16 @@ export class SubmissionController {

const validityFactor = validationResult['validityFactor'];

if (validityFactor === 1) await this.contracts.registerSubmission(submissionRequest);
if (validityFactor > 0.5) {
if (!(await this.contracts.registerSubmission(submissionRequest))) {
throw new HttpException(500, 'Error registering submission and sending rewards');
}
}

res.status(200).json({ validation: validationResult });
} catch (error) {
next(error);
return;
}
};
}
4 changes: 0 additions & 4 deletions apps/backend/src/dtos/submission.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,4 @@ export class SubmitDto {
@IsString()
@IsNotEmpty()
public deviceID: string;

@IsString()
@IsNotEmpty()
public captcha: string;
}
1 change: 0 additions & 1 deletion apps/backend/src/interfaces/submission.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export interface Submission {
_id?: string;
round?: number;
address: string;
captcha: string;
timestamp: number;
image?: string;
deviceID?: string;
Expand Down
24 changes: 0 additions & 24 deletions apps/backend/src/services/captcha.service.ts

This file was deleted.

45 changes: 17 additions & 28 deletions apps/backend/src/services/contracts.service.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
import { ADMIN_ADDRESS, ADMIN_PRIVATE_KEY, REWARD_AMOUNT } from '@/config';
import { HttpException } from '@/exceptions/HttpException';
import { Submission } from '@/interfaces/submission.interface';
import { thor } from '@/utils/thor';
import { ecoEarnContract } from '@/utils/thor';
import { Service } from 'typedi';
import { EcoEarnABI } from '@/utils/const';
import { ethers } from 'ethers';
import { config } from '@repo/config-contract';
import { TransactionHandler, clauseBuilder, coder } from '@vechain/sdk-core';
import * as console from 'node:console';
import { unitsUtils } from '@vechain/sdk-core';
import { REWARD_AMOUNT } from '@config';
@Service()
export class ContractsService {
public async registerSubmission(submission: Submission): Promise<void> {
const clause = clauseBuilder.functionInteraction(
config.CONTRACT_ADDRESS,
coder.createInterface(EcoEarnABI).getFunction('registerValidSubmission'),
[submission.address, `0x${ethers.parseEther(REWARD_AMOUNT).toString(16)}`],
);
public async registerSubmission(submission: Submission): Promise<boolean> {
let isSuccess = false;
try {
const result = await (
await ecoEarnContract.transact.registerValidSubmission(submission.address, unitsUtils.parseUnits(REWARD_AMOUNT, 'ether'))
).wait();
isSuccess = !result.reverted;
} catch (error) {
console.log('Error', error);
}

const gasResult = await thor.gas.estimateGas([clause], ADMIN_ADDRESS);

if (gasResult.reverted === true) throw new HttpException(500, `EcoEarn: Internal server error: ${gasResult.revertReasons}`);

const txBody = await thor.transactions.buildTransactionBody([clause], gasResult.totalGas);

const signedTx = TransactionHandler.sign(txBody, Buffer.from(ADMIN_PRIVATE_KEY));

await thor.transactions.sendTransaction(signedTx);
return isSuccess;
}

public async validateSubmission(submission: Submission): Promise<void> {
const isMaxSubmissionsReached = await thor.contracts.executeCall(
config.CONTRACT_ADDRESS,
coder.createInterface(EcoEarnABI).getFunction('isUserMaxSubmissionsReached'),
[submission.address],
);

if (Boolean(isMaxSubmissionsReached[0]) === true) throw new HttpException(409, `EcoEarn: Max submissions reached for this cycle`);
const isMaxSubmissionsReached = (await ecoEarnContract.read.isUserMaxSubmissionsReached(submission.address))[0];
if (Boolean(isMaxSubmissionsReached) === true) throw new HttpException(409, `EcoEarn: Max submissions reached for this cycle`);
}
}
3 changes: 0 additions & 3 deletions apps/backend/src/services/helpers/captcha.ts

This file was deleted.

1 change: 0 additions & 1 deletion apps/backend/src/services/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './captcha';
export * from './openai';
2 changes: 1 addition & 1 deletion apps/backend/src/services/helpers/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class OpenAIHelper {

public askChatGPTAboutImage = async ({ base64Image, maxTokens = 350, prompt }: { base64Image: string; prompt: string; maxTokens?: number }) =>
this.openai.chat.completions.create({
model: 'gpt-4-vision-preview',
model: 'gpt-4o',
max_tokens: maxTokens,
messages: [
{
Expand Down
6 changes: 3 additions & 3 deletions apps/backend/src/services/openai.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ export class OpenaiService {
2. It must not be a screenshot.
3. It must include the date of the purchase.
4. It must include the name of the store where the purchase was made.
Please respond using a JSON object without comments and do not add any other descriptions and comments:
Please respond always and uniquely with the following JSON object as you are REST API that returns the following object:
{
'validityFactor': number, // 0-1, 1 if it satisfies all the criteria, 0 otherwise
'descriptionOfAnalysis': string, // indicate your analysis of the image and why it satisfies or not the criteria. The analysis will be shown to the user so make him understand why the image doesn't satisfy the criteria if it doesn't without going into detail on exact criteria. Remember we are rewarding users that drink coffee in a sustainable way.
"validityFactor": {validityFactorNumber}, // 0-1, 1 if it satisfies all the criteria, 0 otherwise
"descriptionOfAnalysis": "{analysis}", // indicate your analysis of the image and why it satisfies or not the criteria. The analysis will be shown to the user so make him understand why the image doesn't satisfy the criteria if it doesn't without going into detail on exact criteria. Remember we are rewarding users that drink coffee in a sustainable way.
}
`;

Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/utils/const/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,4 +595,4 @@ export const EcoEarnABI = [
stateMutability: 'nonpayable',
type: 'function',
},
];
] as const;
12 changes: 10 additions & 2 deletions apps/backend/src/utils/thor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { NETWORK_URL } from '../config';
import { HttpClient, ThorClient } from '@vechain/sdk-network';
import { ADMIN_PRIVATE_KEY, NETWORK_URL } from '../config';
import { HttpClient, ThorClient, VeChainPrivateKeySigner, VeChainProvider } from '@vechain/sdk-network';
import { EcoEarnABI } from '@utils/const';

Check warning on line 3 in apps/backend/src/utils/thor.ts

View workflow job for this annotation

GitHub Actions / Build and Test

'EcoEarnABI' is defined but never used
import { ECO_SOL_ABI, config } from '@repo/config-contract';

export const thor = new ThorClient(new HttpClient(NETWORK_URL), {
isPollingEnabled: false,
});

export const ecoEarnContract = thor.contracts.load(
config.CONTRACT_ADDRESS,
ECO_SOL_ABI,
new VeChainPrivateKeySigner(Buffer.from(ADMIN_PRIVATE_KEY), new VeChainProvider(thor)),
);
5 changes: 0 additions & 5 deletions apps/backend/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ paths:
500:
description: 'Server Error'



# definitions
definitions:
submitReceipt:
Expand All @@ -41,9 +39,6 @@ definitions:
address:
type: string
description: User's address
captcha:
type: string
description: Captcha code
deviceID:
type: string
description: User's device ID
Expand Down
Loading
Loading