diff --git a/.kno/chunk_review.txt b/.kno/chunk_review.txt new file mode 100644 index 0000000..b17c37f --- /dev/null +++ b/.kno/chunk_review.txt @@ -0,0 +1,43028 @@ + +=== File: config-task.yml === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/config-task.yml:1-111 +######################## ALL FIELDS ARE REQUIRED UNLESS OTHERWISE NOTED ######################### + +######################################### TASK METADATA ######################################### +############################ Will be displayed in the desktop node ############################## + +## Task Name ## +# Maximum 24 characters. +task_name: "Key Helper Test" + +## Task Author ## +author: "Koii" + +# Task Description Markdown ## +# If you specify a markdown file, the description field will be ignored. +# Markdown is recommended for better formatting. +markdownDescriptionPath: "./TaskDescription.md" + +## Task Description ## +# Ignored if you specify a markdown file. +description: "Task description." + +## Repository URL ## +# Must be public for whitelisted tasks. +repositoryUrl: "https://github.com/koii-network/task-template" + +## Image URL ## +# 230x86 pixels. +imageUrl: "imageUrl" + +## Info URL ## +infoUrl: "infoUrl" + +####################################### TASK CONFIGURATION ###################################### + +## Task Executable Network ## +# IPFS or DEVELOPMENT +# Keep this as IPFS unless you know you need to change it. +task_executable_network: "IPFS" + +## Task Audit Program ## +# Task Executable Network IPFS: Path to your executable. +# Task Executable Network DEVELOPMENT: The value should be 'main'. +# Keep this as-is unless you know you need to change it. +task_audit_program: "dist/main.js" + +## Round Time ## +# Duration of task, measured in slots (with each slot approximately equal to 408ms). Should be at least 800 slots. +# See https://www.koii.network/docs/concepts/what-are-tasks/what-are-tasks/gradual-consensus for more information on how round time, audit window, and submission window work. +round_time: 3000 + +## Audit Window ## +# The audit window should be at least 1/3 of the round time. +audit_window: 1200 + +## Submission Window ## +# The submission window should be at least 1/3 of the round time. +submission_window: 1800 + +# Minimum stake amount: (Required) The minimum amount of KOII that a user must stake in order to participate in the task. +minimum_stake_amount: 1.9 + +# Task Bounty Type: (Required | KOII or KPL) +task_type: 'KOII' + +# Token Mint Address: (ONLY task_type == KPL) Fire Token as an example here. +token_type: "4qayyw53kWz6GzypcejjT1cvwMXS1qYLSMQRE8se3gTv" + +## Total Bounty Amount ## +# The total bounty amount that will be available for distribution over all rounds. +# Does nothing when updating a task. +total_bounty_amount: 10 + +## Bounty Amount per Round ## +# The maximum amount that can be distributed per round. +# If the actual distribution per round exceeds this amount, the distribution list will fail. +bounty_amount_per_round: 0.1 + +## Allowed Failed Distributions ## +# Number of retries allowed for the distribution list if it is fails audit. +# If all retries fail, the task will not distribute anything for the round. +# This is also the number of rounds of submissions it will keep. +allowed_failed_distributions: 3 + +## Space ## +# Expected Task Data Size in MBs for the account size. +# Minimums: 2 for whitelisted tasks, 1 for production, 0.1 for testing. +# See https://www.koii.network/docs/develop/command-line-tool/create-task-cli/create-task#space for calculation details. +space: 0.1 + +## Requirement Tags (Optional) ## +# To add more global variables and task variables, please refer to the type, value, description format shown below. +requirementsTags: + - type: CPU + value: "4-core" + - type: RAM + value: "5 GB" + - type: STORAGE + value: "5 GB" + +# Tags: You can select the tags here via https://www.koii.network/docs/develop/command-line-tool/create-task-cli/create-task#tags +tags: [] +# Environment: (Required | TEST or PRODUCTION) Production mode will expose your task to all the task runners. +environment: "TEST" + +#################################### FOR UPDATING TASKS ONLY #################################### + +## Old Task ID ## +task_id: "" + +## Migration Description ## +migrationDescription: "" + +=== File: nodemon.json === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/nodemon.json:1-5 +{ + "events": { + "crash": "echo 'Application has crashed' && kill $(ps aux | grep '[n]odemon' | awk '{print $2}')" + } +} + +=== File: webpack.config.js === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/webpack.config.js:1-31 +import path from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export default { + entry: "./src/index.js", + target: "node", + // When uploading to arweave use the production mode + // mode:"production", + mode: "development", + devtool: "source-map", + resolve: { + alias: { + "@_koii/namespace-wrapper": path.resolve( + __dirname, + "node_modules/@_koii/namespace-wrapper", + ), + }, + }, + optimization: { + usedExports: false, + }, + stats: { + moduleTrace: false, + }, + node: { + __dirname: true, + }, +}; + +=== File: .gitignore === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/.gitignore:1-17 +dist +build +node_modules +package-lock.json +yarn.lock +migrate.sh +*/dev.js +data/* +executables/* +namespace/* +config/* +.env.local +.env +taskStateInfoKeypair.json +localKOIIDB.db +metadata.json +.npmrc + +=== File: package.json === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/package.json:1-50 +{ + "name": "koii-task-template", + "version": "3.0.0", + "description": "", + "main": "src/index.js", + "type": "module", + "scripts": { + "test": "node tests/testTask.js", + "simulate": "node tests/simulateTask.js", + "jest-test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "start": "node index.js", + "prod-debug": "nodemon --ignore 'dist/*' tests/prod-debug.js", + "webpack": "webpack", + "webpack:test": "webpack --config tests/test.webpack.config.js", + "webpack:prod": "webpack --mode production", + "lint": "eslint . --fix", + "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\"" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@_koii/task-manager": "^1.0.11", + "@_koii/web3.js": "^0.1.11", + "@babel/preset-env": "^7.25.7", + "axios": "^1.7.7", + "babel-jest": "^29.7.0", + "chalk": "^5.3.0", + "cheerio": "^1.0.0-rc.12", + "cross-spawn": "^7.0.3", + "dotenv": "^16.3.0", + "dotenv-webpack": "^8.1.0", + "nodemon": "^3.1.7", + "open": "^10.1.0", + "puppeteer": "npm:rebrowser-puppeteer@^23.10.1", + "puppeteer-chromium-resolver": "^23.0.0", + "puppeteer-core": "npm:rebrowser-puppeteer-core@^23.10.1", + "tail": "^2.2.6" + }, + "devDependencies": { + "@_koii/namespace-wrapper": "1.0.23", + "eslint": "8.4.1", + "globals": "^15.9.0", + "jest": "^29.7.0", + "joi": "^17.9.2", + "prettier": "^3.3.3", + "typescript": "^5.8.2", + "webpack": "^5.28.0", + "webpack-cli": "^4.5.0" + } +} + +=== File: README.md === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/README.md:1-150 +# Koii Task Template + +## Koii Task Development: Step-by-Step Guide + +This guide will help you create, test, and deploy a task on the Koii Network. It's designed for beginners and experts alike. Read through the steps below for a simple, easy-to-follow guide. + +_Want to dive deeper?_ Check out our tutorialized [Development Guide](https://github.com/koii-network/ezsandbox). + +## 1. Prerequisites + +Before you begin, make sure you have the following: + +### Tools to Install + +- **Node.js** _(version >=20.0.0, LTS Versions only)_: [Download here](https://nodejs.org) +- _(Optional, for Python and Docker tasks only)_ **Docker Compose**: [Install here](https://docs.docker.com/get-started/08_using_compose/) + +## 2. Set Up Your Task + +Once you have the required tools, input the following commands: + +1. Clone the Koii Task Template: + + ```sh + git clone https://github.com/koii-network/task-template.git + ``` + +2. Install dependencies: + + ```sh + yarn install + ``` + +3. Navigate to the `src/task/1-task.js` file. + +Now, let's begin writing a task! + +## 3. Write Your Core Task Logic + +The `src/task/1-task.js` file is where you will write all the code. It covers: + +1. Defining task behavior +2. Handling inputs and outputs +3. Core logic error handling + +We suggest you follow our tutorialized [Development Guide](https://github.com/koii-network/ezsandbox) for a more in-depth walkthrough. To keep things short, import the packages you require and write your core logic within the 'try-catch' statement. + +To test your core logic, you can run the following command: + +```sh +yarn test +``` + +This function will run your `src/task/1-task.js` file in a vacuum to quickly get your core logic into a working state. Use this function to test your UI and data postback to ensure your logic works as intended. + +## 4. (Optional) Incentive Engineering + +This step is optional, as nodes can run your task without incentives, but if you intend to distribute rewards for your task, consider adding audits. + +Beyond your core logic in the `1-task.js` file, there are 5 other task files within this template: + +- `src/task/0-setup.js`: For defining steps executed once before your task starts. +- `src/task/2-submission.js`: For defining how your task submits proofs for auditing. +- `src/task/3-audit.js`: For defining a function that audits the work done in your task function. +- `src/task/4-distribution.js`: For defining your incentive distribution logic. +- `src/task/5-routes.js`: For defining custom routes. + +Find more info in our tutorialized [Development Guide](https://github.com/koii-network/ezsandbox). + +To test a [full round cycle](https://docs.koii.network/gradual-consensus), use the following command: + +```sh +yarn simulate +``` + +This command simulates the entire task flow, including performing the task, submitting results, and auditing work. It handles multiple task rounds, tracks step durations, and shows performance results and errors. + +## 5. Production Testing + +Before deploying your task to a production environment, test it in the Desktop Node: + +1. Build your executable: + + ```sh + yarn webpack + ``` + +2. Create your `.env` file by renaming `.env.developer.example` to `.env`. Note: This file is for testing purposes only and does not reflect the env variables in your fully deployed task. + +3. Add the "EZ Sandbox Task" to your desktop node using the EZ Sandbox Task ID (`BXbYKFdXZhQgEaMFbeShaisQBYG1FD4MiSf9gg4n6mVn`) and the Advanced option in the Add Task tab. [Click here for a detailed walkthrough of adding this task to the node.](https://github.com/koii-network/ezsandbox/tree/main/Get%20Started%20-%20Quick%20Intro). + +4. Test your task in the production environment. To test your executable, enter the following command: + ```sh + yarn prod-debug + ``` + The production debugger (prod-debug) launches nodemon, which automatically restarts your task whenever it detects changes in the source files, making production development faster and easier. + +## 6. Production Deployment + +1. Fill in your `config-task.yml`: + The default `config-task.yml` file has placeholders to fill in before deploying your task. This file configures your task with a name, an image, and other settings. Check the comments in the `config-task.yml` file for more information. Set the environment parameter in your config to "PRODUCTION". + +2. Run the Create Task CLI: + The Create-Task-CLI is a command-line tool that helps you easily deploy your task so the Koii Community can host it on their nodes. To get started, copy the command below to your CLI: + + ```sh + npx @_koii/create-task-cli@latest + ``` + + The Create-Task-CLI will ask for a series of inputs to help you deploy your task. + + _Note_: You may be asked for specific paths to your wallets. If you don't have a wallet yet, create one using the [Desktop Node](https://koii.network/node) or the [Koii CLI](https://docs.koii.network/develop/command-line-tool/koii-cli/install-cli). + + If the tool isn't able to grab these automatically, the OS-specific paths are: + + **Windows:** `/Users//AppData/Roaming` + + **Mac:** `/Users//Library/Application Support` + + **Linux:** `/home//.config` + + Once done, it will generate a task-ID, which will look something like "". [Add this task to your node as you did with the EZ Sandbox Task.](https://github.com/koii-network/ezsandbox/tree/main/Get%20Started%20-%20Quick%20Intro) + +_Congrats! You've done it! You're now officially a blockchain developer with a decentralized app/service live in Web3. We couldn't be more proud!_ + +# More Info + +## Task Flow + +Tasks operate within a periodic structure known as 'rounds'. Each round consists of the following steps: + +1. **Perform the Task:** Execute the necessary actions for the round. +2. **Audit Work:** Review the work completed by other nodes. +3. **Rewards and Penalties:** Distribute rewards and apply penalties as needed. + +For more detailed information about the task flow, refer to [the runtime flow documentation](https://docs.koii.network/concepts/what-are-tasks/what-are-tasks/gradual-consensus). + +Looking to bring better structure to your task? Explore our [Task Organizer](https://www.figma.com/community/file/1220194939977550205/Task-Outline) for better organization. + +## Tips + +- Always ensure your secret files, such as `.env` files, are secure! Implement a robust `.gitignore` strategy. + +**Advanced Runtime Options** + +There are two ways to run your task during development: + +1. With `GLOBAL_TIMERS="true"` (refer to `.env.local.example`) - When enabled, IPC calls are made by calculating the average time slots of all tasks running on your node. + +2. With `GLOBAL_TIMERS="false"` - This option allows for manual calls to the K2 and disables automatic triggers for round management on K2. Transactions are only accepted during the correct time period. Instructions for manual calls can be found in [Manual K2 Calls](./Manual%20K2%20Calls.md). + +-- Chunk 2 -- +// /app/repos/repo_3/repos/repo_0/README.md:151-152 + +**If you encounter any issues, don't hesitate to reach out by opening a ticket on [Discord](https://discord.gg/koii-network).** + +=== File: .prettierrc === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/.prettierrc:1-12 +{ + "printWidth": 80, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "bracketSpacing": true, + "jsxBracketSameLine": false, + "arrowParens": "always", + "endOfLine": "auto" +} + +=== File: jest.config.js === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/jest.config.js:1-4 +export default { + transform: { "^.+\\.jsx?$": "babel-jest" }, + transformIgnorePatterns: ["/node_modules/(?!@babel/runtime)"], +}; + +=== File: .env.developer.example === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/.env.developer.example:1-8 +# This File is for prod-debug.js + +TASK_ID='FGzVTXn6iZFhFo9FgWW6zoHfDkJepQkKKKPfMvDdvePv' # Easy Testing Task ID +TEST_KEYWORDS='TEST,EZ TESTING' + +# Set this to use your desktop node staking wallet during testing so IPFS will work +# See https://github.com/koii-network/ezsandbox/blob/main/Lesson%201/PartIV.md#staking-wallet +STAKING_WALLET_PATH="path to your desktop node staking wallet" + +=== File: .eslintrc.js === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/.eslintrc.js:1-16 +export default { + env: { + browser: true, + commonjs: true, + es2021: true, + node: true, + jest: true, + }, + extends: "eslint:recommended", + parserOptions: { + ecmaVersion: 15, + sourceType: "module", + }, + rules: {}, + ignorePatterns: ["dist/", "node_modules/"], +}; + +=== File: babel.config.cjs === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/babel.config.cjs:1-1 +module.exports = { presets: ["@babel/preset-env"] }; + +=== File: TaskDescription.md === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/TaskDescription.md:1-1 +# Task Description Template + +=== File: .gitlab-ci.yml === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/.gitlab-ci.yml:1-15 +stages: + - test + +npm_install: + stage: test + image: node:latest + script: + - npm install + +yarn_jest_tests: + stage: test + image: node:latest + script: + - yarn install + - yarn jest + +=== File: Manual K2 Calls.md === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/Manual K2 Calls.md:1-72 +# GUIDE TO CALLS K2 FUNCTIONS MANUALLY + +If you wish to do the development by avoiding the timers then you can do the intended calls to K2 directly using these function calls. + +To disable timers please set the TIMERS flag in task-node ENV to disable + +NOTE : K2 will still have the windows to accept the submission and audit values, so you are expected to make calls in the intended slots of your round time. + +## Get the task state + +```js +console.log(await namespaceWrapper.getTaskState()); +``` + +## Get round + +```js +const round = await namespaceWrapper.getRound(); +console.log("ROUND", round); +``` + +## Call to do the work for the task + +```js +import { taskRunner } from "@_koii/task-manager"; +await taskRunner.task(); +``` + +## Submission to K2 + +Preferably you should submit the CID received from IPFS. + +```js +import { taskRunner } from "@_koii/task-manager"; +await taskRunner.submitTask(round - 1); +``` + +## Audit submissions + +```js +import { taskRunner } from "@_koii/task-manager"; +await taskRunner.auditTask(round - 1); +``` + +## Upload distribution list to K2 + +```js +import { taskRunner } from "@_koii/task-manager"; +await taskRunner.selectAndGenerateDistributionList(10); +``` + +## Audit distribution list + +```js +import { taskRunner } from "@_koii/task-manager"; +await coreLogic.auditDistribution(round - 2); +``` + +## Payout trigger + +```js +const responsePayout = await namespaceWrapper.payoutTrigger(); +console.log("RESPONSE TRIGGER", responsePayout); +``` + +## Logs to be displayed on desktop-node + +```js +namespaceWrapper.logger("error", "Internet connection lost"); +await namespaceWrapper.logger("warn", "Stakes are running low"); +await namespaceWrapper.logger("log", "Task is running"); +``` + +=== File: docker-compose.yaml === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/docker-compose.yaml:1-21 +version: "3.2" +services: + task_node: + image: public.ecr.aws/koii-network/task_node:latest + command: yarn initialize-start + extra_hosts: + - "host.docker.internal:host-gateway" + + ports: + - 30017:30017 + + env_file: .env.local + + container_name: task_node + + # network_mode: host + volumes: + - ~/.config/koii:/app/config + - ./data:/app/data + - ./namespace:/app/namespace + - ./dist:/app/executables + +=== File: .env.local.example === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/.env.local.example:1-53 +###################################################### +################## DO NOT EDIT BELOW ################# +###################################################### +# Location of main wallet Do not change this, it mounts the ~/.config/koii:/app/config if you want to change, update it in the docker-compose.yml +WALLET_LOCATION="/app/config/id.json" +# Node Mode +NODE_MODE="service" +# The nodes address +SERVICE_URL="http://localhost:8080" +# Intial balance for the distribution wallet which will be used to hold the distribution list. +INITIAL_DISTRIBUTION_WALLET_BALANCE= 2 +# Global timers which track the round time, submission window and audit window and call those functions +GLOBAL_TIMERS="true" +# HAVE_STATIC_IP is flag to indicate you can run tasks that host APIs +# HAVE_STATIC_IP=true +# To be used when developing your tasks locally and don't want them to be whitelisted by koii team yet +RUN_NON_WHITELISTED_TASKS=true +# The address of the main trusted node +# TRUSTED_SERVICE_URL="https://k2-tasknet.koii.live" +###################################################### +################ DO NOT EDIT ABOVE ################### +###################################################### + + +###################################################### +## DO NOT PUT YOUR KEYS ON GITHUB!!! +## To set up your environment variables: +## - Copy `.env-local.example` to a new file named `.env-local`. +## - Fill in the necessary values in `.env-local`. +####################################################### + +# For the purpose of automating the staking wallet creation, the value must be greater +# than the sum of all TASK_STAKES, the wallet will only be created and staking on task +# will be done if it doesn't already exist +INITIAL_STAKING_WALLET_BALANCE=10 + +# environment +ENVIRONMENT="development" + +# If you are running a koii-test-validator use http://127.0.0.1:8899 (linux) otherwise use http://host.docker.internal:8899 for Mac and Windows +# Location of K2 node +K2_NODE_URL="https://testnet.koii.live" + +# Tasks to run and their stakes. This is the varaible you can add your Task ID to after +# registering with the crete-task-cli. This variable supports a comma separated list: +# TASKS="id1,id2,id3" +# TASK_STAKES="1,1,1" +TASKS="AXcd6MctmDUQo3XDeBNa4NBAi4tfBYDpt4Adxyai3Do3" +TASK_STAKES=5 + +# User can enter as many environment variables as they like below. These can be task +# specific variables that are needed for the task to perform it's job. Some examples: +SCRAPING_URL="" + +=== File: src/index.js === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/src/index.js:1-23 +import { initializeTaskManager } from "@_koii/task-manager"; +// any custom setup logic you need +import { setup } from "./task/0-setup.js"; + +// your task, submission, and audit logic +import { task } from "./task/1-task.js"; +import { submission } from "./task/2-submission.js"; +import { audit } from "./task/3-audit.js"; + +// rewards calculation +import { distribution } from "./task/4-distribution.js"; + +// custom REST API routes +import { routes } from "./task/5-routes.js"; + +initializeTaskManager({ + setup, + task, + submission, + audit, + distribution, + routes, +}); + +=== File: tests/config.js === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/tests/config.js:1-12 +import "dotenv/config"; + +export const TASK_ID = + process.env.TASK_ID || "BXbYKFdXZhQgEaMFbeShaisQBYG1FD4MiSf9gg4n6mVn"; +export const WEBPACKED_FILE_PATH = + process.env.WEBPACKED_FILE_PATH || "../dist/main.js"; + +const envKeywords = process.env.TEST_KEYWORDS ?? ""; + +export const TEST_KEYWORDS = envKeywords + ? envKeywords.split(",") + : ["TEST", "EZ TESTING"]; + +=== File: tests/debugger.js === + +-- Chunk 1 -- +// debugger.js:8-103 +class Debugger { + /* + Create .env file with following variables or directly input values to be used in live-debugging mode. + */ + static taskID = TASK_ID; + static webpackedFilePath = WEBPACKED_FILE_PATH; + static keywords = TEST_KEYWORDS; + + static async getConfig() { + Debugger.nodeDir = await this.getNodeDirectory(); + + let destinationPath = + "executables/" + (await this.getAuditProgram()) + ".js"; + let logPath = "namespace/" + Debugger.taskID + "/task.log"; + + console.log("Debugger.nodeDir", Debugger.nodeDir); + + return { + webpackedFilePath: Debugger.webpackedFilePath, + destinationPath: destinationPath, + keywords: Debugger.keywords, + logPath: logPath, + nodeDir: Debugger.nodeDir, + taskID: Debugger.taskID, + }; + } + + static async getNodeDirectory() { + if (Debugger.nodeDir) { + return Debugger.nodeDir; + } + const homeDirectory = os.homedir(); + let nodeDirectory; + + switch (os.platform()) { + case "linux": + nodeDirectory = path.join( + homeDirectory, + ".config", + "KOII-Desktop-Node", + ); + break; + case "darwin": + nodeDirectory = path.join( + homeDirectory, + "Library", + "Application Support", + "KOII-Desktop-Node", + ); + break; + case "win32": + // For Windows, construct the path explicitly as specified + nodeDirectory = path.join( + homeDirectory, + "AppData", + "Roaming", + "KOII-Desktop-Node", + ); + break; + default: + nodeDirectory = path.join( + homeDirectory, + "AppData", + "Roaming", + "KOII-Desktop-Node", + ); + } + + return nodeDirectory; + } + + static async getAuditProgram() { + const connection = new Connection("https://mainnet.koii.network"); + const taskId = Debugger.taskID; + const accountInfo = await connection.getAccountInfo(new PublicKey(taskId)); + if (!accountInfo?.data) { + console.log(`${taskId} doesn't contain any distribution list data`); + return null; + } + let data; + const owner = accountInfo.owner.toBase58(); + if (owner === "Koiitask22222222222222222222222222222222222") { + data = JSON.parse(accountInfo.data.toString()); + } else if (owner === "KPLTRVs6jA7QTthuJH2cEmyCEskFbSV2xpZw46cganN") { + const buffer = accountInfo.data; + data = borsh_bpf_js_deserialize(buffer); + data = parseTaskState(data); + } else { + console.error(`Not a valid Task ID ${taskId}`); + return null; + } + + console.log("data.task_audit_program", data.task_audit_program); + return data.task_audit_program; + } +} + +-- Chunk 2 -- +// debugger.js:105-127 +function parseTaskState(taskState) { + taskState.stake_list = objectify(taskState.stake_list, true); + taskState.ip_address_list = objectify(taskState.ip_address_list, true); + taskState.distributions_audit_record = objectify( + taskState.distributions_audit_record, + true, + ); + taskState.distributions_audit_trigger = objectify( + taskState.distributions_audit_trigger, + true, + ); + taskState.submissions = objectify(taskState.submissions, true); + taskState.submissions_audit_trigger = objectify( + taskState.submissions_audit_trigger, + true, + ); + taskState.distribution_rewards_submission = objectify( + taskState.distribution_rewards_submission, + true, + ); + taskState.available_balances = objectify(taskState.available_balances, true); + return taskState; +} + +-- Chunk 3 -- +// debugger.js:129-144 +function objectify(data, recursive = false) { + if (data instanceof Map) { + const obj = Object.fromEntries(data); + if (recursive) { + for (const key in obj) { + if (obj[key] instanceof Map) { + obj[key] = objectify(obj[key], true); + } else if (typeof obj[key] === "object" && obj[key] !== null) { + obj[key] = objectify(obj[key], true); + } + } + } + return obj; + } + return data; +} + +=== File: tests/prod-debug.js === + +-- Chunk 1 -- +// prod-debug.js:12-16 +async () => { + console.log("Watching for file changes..."); + // watch and trigger builds + await build(); +} + +-- Chunk 2 -- +// prod-debug.js:19-34 +async () => { + console.log("Building..."); + const child = await spawn("npm", ["run", "webpack:test"], { + stdio: "inherit", + }); + + await child.on("close", (code) => { + if (code !== 0) { + console.error("Build failed"); + } else { + console.log("Build successful"); + copyWebpackedFile(); + } + return; + }); +} + +-- Chunk 3 -- +// prod-debug.js:37-64 +async () => { + const debugConfig = await Debugger.getConfig(); + console.log("debugConfig", debugConfig); + const nodeDIR = debugConfig.nodeDir; + const sourcePath = __dirname + "/" + debugConfig.webpackedFilePath; + const desktopNodeExecutablePath = nodeDIR + "/" + debugConfig.destinationPath; + const desktopNodeLogPath = nodeDIR + "/" + debugConfig.logPath; + const keywords = debugConfig.keywords; + const taskID = debugConfig.taskID; + + if (!sourcePath || !desktopNodeExecutablePath) { + console.error("Source path or destination path not specified in .env"); + return; + } + + console.log( + `Copying webpacked file from ${sourcePath} to ${desktopNodeExecutablePath}...`, + ); + + fs.copyFile(sourcePath, desktopNodeExecutablePath, async (err) => { + if (err) { + console.error("Error copying file:", err); + } else { + console.log("File copied successfully"); + tailLogs(desktopNodeLogPath, keywords, taskID); + } + }); +} + +-- Chunk 4 -- +// prod-debug.js:67-108 +async (desktopNodeLogPath, keywords, taskID) => { + console.log("Watchings logs for messages containing ", keywords); + + // Extract the directory path from the full log file path + const dirPath = path.dirname(desktopNodeLogPath); + + // Check if the directory exists, create it if it doesn't + try { + await fs.promises.access(dirPath, fs.constants.F_OK); + } catch (dirErr) { + console.log( + "Unable to find task directory. Please make sure you have the correct task ID set in your .env file, and run the task on the Desktop Node before running prod-debug.", + ); + process.exit(1); + } + + // Ensure the log file exists, or create it if it doesn't + try { + await fs.promises.access(desktopNodeLogPath, fs.constants.F_OK); + } catch (err) { + console.log(`Log file not found, creating ${desktopNodeLogPath}`); + await fs.promises.writeFile(desktopNodeLogPath, "", { flag: "a" }); // 'a' flag ensures the file is created if it doesn't exist and not overwritten if it exists + } + + let tail = new Tail(desktopNodeLogPath, "\n", {}, true); + + console.log( + `Now watching logs for messages containing ${keywords.join(", ")}. Please start the task ${taskID} and keep it running on the Desktop Node.`, + ); + + tail.on("line", function (data) { + if (keywords.some((keyword) => keyword && data.includes(keyword))) { + console.log(chalk.magenta(data)); + } else { + console.log(data); + } + }); + + tail.on("error", function (error) { + console.log("ERROR: ", error); + }); +} + +=== File: tests/testAddVariables.js === + +-- Chunk 1 -- +// testAddVariables.js:3-21 +async function testCreateTaskVariable() { + try { + const response = await axios.post('http://localhost:30017/api/task-variables', { + label: "Test Variable", + value: "Test Value" + }); + + console.log('Success:', response.data); + } catch (error) { + if (error.response) { + // Server responded with error + console.error('Error:', error.response.data); + console.error('Status:', error.response.status); + } else { + // Network error or request failed + console.error('Request failed:', error.message); + } + } +} + +=== File: tests/test.webpack.config.js === + +-- Chunk 1 -- +// /app/repos/repo_3/repos/repo_0/tests/test.webpack.config.js:1-20 +import Dotenv from "dotenv-webpack"; + +export default { + entry: "./src/index.js", + target: "node", + // When uploading to arweave use the production mode + // mode:"production", + mode: "development", + devtool: "source-map", + optimization: { + usedExports: false, + }, + stats: { + moduleTrace: false, + }, + node: { + __dirname: true, + }, + plugins: [new Dotenv()], +}; + +=== File: tests/testTask.js === + +-- Chunk 1 -- +// testTask.js:6-10 +async function executeTasks() { + let round = 1; + await taskRunner.task(round); + // process.exit(0); +} + +=== File: tests/main.test.js === + +-- Chunk 1 -- +// main.test.js:13-23 +async () => { + await namespaceWrapper.defaultTaskSetup(); + initializeTaskManager({ + setup, + task, + submission, + audit, + distribution, + routes, + }); +} + +-- Chunk 2 -- +// main.test.js:25-174 +() => { + it("should performs the core logic task", async () => { + const round = 1; + await taskRunner.task(round); + const value = await namespaceWrapper.storeGet("value"); + expect(value).toBeDefined(); + expect(value).not.toBeNull(); + }); + + it("should make the submission to k2 for dummy round 1", async () => { + const round = 1; + await taskRunner.submitTask(round); + const taskState = await namespaceWrapper.getTaskState(); + const schema = Joi.object() + .pattern( + Joi.string(), + Joi.object().pattern( + Joi.string(), + Joi.object({ + submission_value: Joi.string().required(), + slot: Joi.number().integer().required(), + round: Joi.number().integer().required(), + }), + ), + ) + .required() + .min(1); + const validationResult = schema.validate(taskState.submissions); + try { + expect(validationResult.error).toBeUndefined(); + } catch (e) { + throw new Error("Submission doesn't exist or is incorrect"); + } + }); + + it("should make an audit on submission", async () => { + const round = 1; + await taskRunner.auditTask(round); + const taskState = await namespaceWrapper.getTaskState(); + console.log("TASK STATE", taskState); + console.log("audit task", taskState.submissions_audit_trigger); + const schema = Joi.object() + .pattern( + Joi.string(), + Joi.object().pattern( + Joi.string(), + Joi.object({ + trigger_by: Joi.string().required(), + slot: Joi.number().integer().required(), + votes: Joi.array().required(), + }), + ), + ) + .required(); + const validationResult = schema.validate( + taskState.submissions_audit_trigger, + ); + try { + expect(validationResult.error).toBeUndefined(); + } catch (e) { + throw new Error("Submission audit is incorrect"); + } + }); + it("should make the distribution submission to k2 for dummy round 1", async () => { + const round = 1; + await taskRunner.submitDistributionList(round); + + const taskState = await namespaceWrapper.getTaskState(); + const schema = Joi.object() + .pattern( + Joi.string(), + Joi.object().pattern( + Joi.string(), + Joi.object({ + submission_value: Joi.string().required(), + slot: Joi.number().integer().required(), + round: Joi.number().integer().required(), + }), + ), + ) + .required() + .min(1); + console.log( + "Distribution submission", + taskState.distribution_rewards_submission, + ); + const validationResult = schema.validate( + taskState.distribution_rewards_submission, + ); + try { + expect(validationResult.error).toBeUndefined(); + } catch (e) { + throw new Error("Distribution submission doesn't exist or is incorrect"); + } + }); + it("should make an audit on distribution submission", async () => { + const round = 1; + await taskRunner.auditDistribution(round); + const taskState = await namespaceWrapper.getTaskState(); + console.log("audit task", taskState.distributions_audit_trigger); + const schema = Joi.object() + .pattern( + Joi.string(), + Joi.object().pattern( + Joi.string(), + Joi.object({ + trigger_by: Joi.string().required(), + slot: Joi.number().integer().required(), + votes: Joi.array().required(), + }), + ), + ) + .required(); + const validationResult = schema.validate( + taskState.distributions_audit_trigger, + ); + try { + expect(validationResult.error).toBeUndefined(); + } catch (e) { + throw new Error("Distribution audit is incorrect"); + } + }); + + it("should make sure the submitted distribution list is valid", async () => { + const round = 1; + const distributionList = await namespaceWrapper.getDistributionList( + "", + round, + ); + console.log( + "Generated distribution List", + JSON.parse(distributionList.toString()), + ); + const schema = Joi.object() + .pattern(Joi.string().required(), Joi.number().integer().required()) + .required(); + const validationResult = schema.validate( + JSON.parse(distributionList.toString()), + ); + console.log(validationResult); + try { + expect(validationResult.error).toBeUndefined(); + } catch (e) { + throw new Error("Submitted distribution list is not valid"); + } + }); + + it("should test the endpoint", async () => { + const response = await axios.get("http://localhost:3000"); + expect(response.status).toBe(200); + +-- Chunk 3 -- +// main.test.js:175-206 + expect(response.data).toEqual({ message: "Running", status: 200 }); + }); + + it("should generate a empty distribution list when submission is 0", async () => { + const submitters = []; + const bounty = Math.floor(Math.random() * 1e15) + 1; + const roundNumber = Math.floor(Math.random() * 1e5) + 1; + const distributionList = await distribution(submitters, bounty, roundNumber); + expect(distributionList).toEqual({}); + }); + + it("should generate a distribution list contains all the submitters", async () => { + const simulatedSubmitters = 5; + const submitters = []; + // 10k is the rough maximum number of submitters + for (let i = 0; i < simulatedSubmitters; i++) { + const publicKey = `mockPublicKey${i}`; + submitters.push({ + publicKey, + votes: Math.floor(Math.random() * simulatedSubmitters) - 5000, + stake: Math.floor(Math.random() * 1e9) + 1, + }); + } + const bounty = Math.floor(Math.random() * 1e15) + 1; + const roundNumber = 1; + const distributionList = await distribution(submitters, bounty, roundNumber); + expect(Object.keys(distributionList).length).toBe(submitters.length); + expect(Object.keys(distributionList).sort()).toEqual( + submitters.map((submitter) => submitter.publicKey).sort(), + ); + }); +} + +-- Chunk 4 -- +// main.test.js:208-210 +async () => { + _server.close(); +} + +=== File: tests/simulateTask.js === + +-- Chunk 1 -- +// simulateTask.js:14-16 +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +-- Chunk 2 -- +// simulateTask.js:24-86 +async function executeTasks() { + for (let round = 0; round < numRounds; round++) { + const taskStartTime = Date.now(); + await taskRunner.task(round); + const taskEndTime = Date.now(); + TASK_TIMES.push(taskEndTime - taskStartTime); + await sleep(functionDelay); + + const taskSubmissionStartTime = Date.now(); + await taskRunner.submitTask(round); + const taskSubmissionEndTime = Date.now(); + SUBMISSION_TIMES.push(taskSubmissionEndTime - taskSubmissionStartTime); + await sleep(functionDelay); + + const auditStartTime = Date.now(); + await taskRunner.auditTask(round); + const auditEndTime = Date.now(); + AUDIT_TIMES.push(auditEndTime - auditStartTime); + await sleep(functionDelay); + + await taskRunner.selectAndGenerateDistributionList(round); + await sleep(functionDelay); + + await taskRunner.auditDistribution(round); + + if (round < numRounds - 1) { + await sleep(roundDelay); + } + } + console.log("TIME METRICS BELOW"); + function metrics(name, times) { + const average = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length; + const formatTime = (ms) => (ms / 1000).toFixed(4); + const formatSlot = (ms) => Math.ceil(ms / 408); + const min = Math.min(...times); + const max = Math.max(...times); + const avg = average(times); + const timeMin = formatTime(min); + const timeMax = formatTime(max); + const timeAvg = formatTime(avg); + const slotMin = formatSlot(min); + const slotMax = formatSlot(max); + const slotAvg = formatSlot(avg); + + return { + Metric: `SIMULATED ${name} WINDOW`, + "Avg Time (s)": timeAvg, + "Avg Slots": slotAvg, + "Min Time (s)": timeMin, + "Min Slots": slotMin, + "Max Time (s)": timeMax, + "Max Slots": slotMax, + }; + } + const timeMetrics = metrics("TASK", TASK_TIMES); + const submissionMetrics = metrics("SUBMISSION", SUBMISSION_TIMES); + const auditMetrics = metrics("AUDIT", AUDIT_TIMES); + + console.table([timeMetrics, submissionMetrics, auditMetrics]); + + console.log("All tasks executed. Test completed."); + process.exit(0); +} + +=== File: src/task/claude-flow.js === + +-- Chunk 1 -- +// claude-flow.js:4-153 +async function handleClaudeFlow(browser) { + let claudePage; + try { + // Create new page for Claude flow + claudePage = await browser.newPage(); + + // Add close listener to reset flag + claudePage.on("close", async () => { + try { + const pages = await browser.pages(); + const landingPage = pages[0]; + await landingPage.evaluate(() => { + window.flowInProgress = false; + }); + } catch (error) { + console.log("Could not reset flow flag on page close:", error); + } + }); + + // Set viewport size + await claudePage.setViewport({ + width: 1700, + height: 992, + }); + + // Add helper function to inject secure footer + const injectBranding = async (page) => { + await page.evaluate(() => { + // Only add if not already present + if (!document.querySelector(".koii-branding")) { + // Add logo + const logo = document.createElement("div"); + logo.className = "koii-logo"; + logo.innerHTML = ` + + `; + + // Add secure footer + const footer = document.createElement("div"); + footer.className = "secure-footer"; + footer.innerHTML = ` + + + + + + +
+ No Strings Attached + This page is secured by Koii, and running 100% on your computer +
+ `; + + // Add styles + const style = document.createElement("style"); + style.textContent = ` + .koii-logo { + position: fixed; + top: 20px; + left: 20px; + z-index: 9999; + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + border-radius: 8px; + backdrop-filter: blur(100px); + } + + .secure-footer { + position: fixed; + bottom: 160px; + left: 40px; + display: flex; + align-items: center; + border-radius: 10px; + background: rgba(137, 137, 199, 0.15); + padding: 12px; + gap: 10px; + border: none; + z-index: 9999; + backdrop-filter: blur(100px); + } + + .secure-footer::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 100%; + border-radius: 10px; + border-bottom: 1px solid rgba(229, 229, 229, 0.8); + border-left: 1px solid rgba(229, 229, 229, 0.8); + border-right: 1px solid rgba(229, 229, 229, 0.8); + mask-image: linear-gradient(to top, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%); + -webkit-mask-image: linear-gradient(to top, rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0) 100%); + pointer-events: none; + } + + .secure-footer .text { + color: rgba(255, 255, 255, 0.80); + font-family: 'Inter', sans-serif; + font-size: 15px; + font-style: normal; + font-weight: 500; + line-height: 150%; + letter-spacing: -0.165px; + } + + .secure-footer .text span { + display: block; + } + `; + + document.head.appendChild(style); + document.body.appendChild(logo); + document.body.appendChild(footer); + + // Add class to mark branding as added + document.body.classList.add("koii-branding"); + } + }); + }; + + // Add listener for navigation events to re-inject footer + claudePage.on("load", () => injectBranding(claudePage)); + + // Initial injection after page creation + await injectBranding(claudePage); + + // Navigate to Claude login + await claudePage.goto("https://console.anthropic.com/login", { + waitUntil: "networkidle0", + timeout: 600000, // 10 minutes + }); + + // Function to check if we're on the wrong page and need to restart + const checkAndRestartFlow = async () => { + const currentUrl = claudePage.url(); + const validUrls = [ + "https://console.anthropic.com/login", + "https://console.anthropic.com/dashboard", + "https://console.anthropic.com/settings/keys", + "https://console.anthropic.com/onboarding", + +-- Chunk 2 -- +// claude-flow.js:154-303 + "https://console.anthropic.com/create" + ]; + + if (!validUrls.some(url => currentUrl.startsWith(url))) { + await claudePage.evaluate(() => { + alert("⚠️ Oops! You've navigated to the wrong page. Redirecting back to login..."); + }); + + // Wait a moment for the alert to be seen + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Navigate back to login + await claudePage.goto("https://console.anthropic.com/login", { + waitUntil: "networkidle0", + timeout: 600000, + }); + return true; // Flow needs restart + } + return false; // Flow can continue + }; + + // Add navigation listener to check for wrong paths + claudePage.on('framenavigated', async frame => { + if (frame === claudePage.mainFrame()) { + await checkAndRestartFlow(); + } + }); + + // Remove Google login button and "Or" text immediately after page load + await claudePage.evaluate(() => { + const googleButton = document.querySelector( + '[data-testid="login-with-google"]', + ); + const orText = Array.from(document.getElementsByTagName("p")).find( + (p) => p.textContent.trim() === "Or", + ); + + // Hide elements instead of removing them + if (googleButton) googleButton.style.display = "none"; + if (orText) orText.style.display = "none"; + + // Add step indicator hint for email field + const emailField = document.querySelector('[data-testid="email"]'); + if (emailField) { + const hintElement = document.createElement("div"); + hintElement.innerHTML = ` +
+
+
Step 2
+
Log In or Sign Up to your Claude account
+
+ `; + emailField.parentElement.style.position = "relative"; + emailField.parentElement.insertBefore(hintElement, emailField); + } + }); + + // Add warning message and highlight login field + await claudePage.evaluate(() => { + const loginButton = document.querySelector('[data-testid="email"]'); + if (loginButton) { + // Add animation style + const style = document.createElement("style"); + style.textContent = ` + @keyframes warningPulse { + 0% { opacity: 0.8; transform: scale(1); } + 50% { opacity: 1; transform: scale(1.02); } + 100% { opacity: 0.8; transform: scale(1); } + } + `; + document.head.appendChild(style); + + // Highlight login button + loginButton.style.cssText = ` + border: 2px solid #e3b341 !important; + box-shadow: 0 0 5px rgba(227, 179, 65, 0.3); + animation: warningPulse 2s infinite; + `; + } + }); + + // Wait for the verification code input to appear + await claudePage.waitForSelector("#code", { + timeout: 600000, // 10 minutes + }); + + await claudePage.evaluate(() => { + // Add step indicator hint for verification code field + const codeField = document.querySelector("#code"); + if (codeField) { + const hintElement = document.createElement("div"); + hintElement.innerHTML = ` +
+
+
Step 3
+
1. Check your email
2. Click verification link
3. Copy code shown
4. Paste code here
+
+ `; + codeField.parentElement.style.position = "relative"; + codeField.parentElement.insertBefore(hintElement, codeField); + } + }); + + // Wait for second navigation (to verification code page) + await claudePage.waitForNavigation({ + waitUntil: "networkidle0", + timeout: 600000, // 10 minutes + }); + + // Modified navigation waiting logic for dashboard + let isDashboardReached = false; + for (let i = 0; i < 10; i++) { + try { + const currentUrl = claudePage.url(); + + // Check if we need to restart the flow + const needsRestart = await checkAndRestartFlow(); + if (needsRestart) { + continue; // Skip this iteration and try again + } + + if (currentUrl.includes("/dashboard")) { + isDashboardReached = true; + break; + } + + // If we're on onboarding or create pages, wait for next navigation + if (currentUrl.includes("/onboarding") || currentUrl.includes("/create")) { + try { + await claudePage.waitForNavigation({ + waitUntil: "networkidle0", + timeout: 60000, // 1 minute timeout + }).catch(() => {}); // Ignore timeout errors + } catch (error) { + console.log("Navigation wait error:", error); + } + } + + // Wait 3 seconds before checking again + await new Promise((resolve) => setTimeout(resolve, 3000)); + } catch (error) { + console.log("Loop iteration error:", error); + } + } + + if (isDashboardReached) { + await claudePage.evaluate(() => { + alert( + "You are now successfully logged in.\nRedirecting to the API key creation page.", + ); + }); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Navigate to tokens page + await claudePage.goto("https://console.anthropic.com/settings/keys", { + waitUntil: "networkidle0", + timeout: 600000, // 10 minutes + }); + + // Add step indicator for Create Key button + await claudePage.evaluate(() => { + const createKeyButton = Array.from( + document.querySelectorAll("button"), + ).find((button) => button.textContent.includes("Create Key")); + + if (createKeyButton) { + const hintElement = document.createElement("div"); + hintElement.innerHTML = ` +
+
+
Step 4
+
Click here to create a new API key
+
+ `; + createKeyButton.style.position = "relative"; + createKeyButton.appendChild(hintElement); + } + }); + + // Wait for the input field to appear after clicking Create Key + await claudePage.waitForSelector('input[placeholder="my-secret-key"]', { + timeout: 120000, // 2 minutes timeout + }); + + // Add hints and highlights for token creation + try { + // First find and interact with the input field + await claudePage.waitForSelector('input[placeholder="my-secret-key"]'); + await claudePage.click('input[placeholder="my-secret-key"]'); + await claudePage.type( + 'input[placeholder="my-secret-key"]', + "my-node-anthropic-key", + ); + + // Add the visual hints through evaluate + await claudePage.evaluate(() => { + const addButton = Array.from( + document.querySelectorAll("button"), + ).find((button) => button.textContent.includes("Add")); + + +-- Chunk 4 -- +// claude-flow.js:454-580 + if (addButton) { + // Add step indicator for Add button + const hintElement = document.createElement("div"); + hintElement.innerHTML = ` +
+
+
Step 5
+
Click Add to create your API key
+
+ `; + addButton.style.position = "relative"; + addButton.appendChild(hintElement); + } + }); + } catch (error) { + console.error("Error during key creation interaction:", error); + } + + // Wait for the key to be generated and displayed + await claudePage.waitForFunction( + () => { + const keyElement = document.querySelector( + ".bg-accent-secondary-900 p.text-text-000", + ); + return keyElement && keyElement.textContent.startsWith("sk-ant-"); + }, + { timeout: 600000 }, + ); // 10 minutes timeout + + // Get and store the API key + const apiKey = await claudePage.evaluate(() => { + const keyElement = document.querySelector( + ".bg-accent-secondary-900 p.text-text-000", + ); + return keyElement ? keyElement.textContent : null; + }); + + if (apiKey && apiKey.startsWith("sk-ant-")) { + console.log("Successfully retrieved API key"); + await namespaceWrapper.storeSet("claude_api_key", apiKey); + + let postSuccess = false; + let errorMessage = ''; + + // Post Claude API key to API + try { + const response = await axios.post( + "http://localhost:30017/api/task-variables", + { + label: "CLAUDE_API_KEY", + value: apiKey, + }, + ); + postSuccess = response.data.success; + if (!postSuccess) { + errorMessage = response.data.message || 'Failed to save API key'; + } + } catch (error) { + postSuccess = false; + errorMessage = error.response?.data?.message || error.message; + } + + // Show appropriate alert based on results + await claudePage.evaluate((success, error) => { + window.flowInProgress = false; // Reset the flag + if (success) { + alert("✅ Your Claude API key has been successfully saved!\nYou can now close this tab and return to the main page."); + } else { + if (error.includes("already exists")) { + alert("⚠️ Your Claude API key was saved locally but couldn't be updated in task variables because it already exists.\nYou can safely continue with the existing credentials."); + } else { + alert(`⚠️ There was an issue saving your Claude API key:\n\n${error}\n\nPlease try again.`); + } + } + }, postSuccess, errorMessage); + + // Only close the page if it's still open + if (!claudePage.isClosed()) { + await claudePage.close(); + } + return true; + } + } + return false; + } catch (error) { + console.error("Claude flow error:", error); + // Reset flow flag on error + await browser.evaluate(() => { + window.flowInProgress = false; + }); + return false; + } +} + +=== File: src/task/4-distribution.js === + +-- Chunk 1 -- +// 4-distribution.js:4-61 +async function distribution(submitters, bounty, roundNumber) { + /** + * Generate the reward list for a given round + * This function should return an object with the public keys of the submitters as keys + * and the reward amount as values + * + * IMPORTANT: If the slashedStake or reward is not an integer, the distribution list will be rejected + * Values are in ROE, or the KPL equivalent (1 Token = 10^9 ROE) + * + */ + console.log(`MAKE DISTRIBUTION LIST FOR ROUND ${roundNumber}`); + + // Initialize an empty object to store the final distribution list + const distributionList = {}; + + // Initialize an empty array to store the public keys of submitters with correct values + const approvedSubmitters = []; + + // Iterate through the list of submitters and handle each one + for (const submitter of submitters) { + // If the submitter's votes are 0, they do not get any reward + if (submitter.votes === 0) { + distributionList[submitter.publicKey] = 0; + + // If the submitter's votes are negative (submitted incorrect values), slash their stake + } else if (submitter.votes < 0) { + // Slash the submitter's stake by the defined percentage + const slashedStake = Math.floor(submitter.stake * SLASH_PERCENT); + // Add the slashed amount to the distribution list + // since the stake is positive, we use a negative value to indicate a slash + distributionList[submitter.publicKey] = -slashedStake; + + // Log that the submitter's stake has been slashed + console.log("CANDIDATE STAKE SLASHED", submitter.publicKey, slashedStake); + + // If the submitter's votes are positive, add their public key to the approved submitters list + } else { + approvedSubmitters.push(submitter.publicKey); + } + } + + // If no submitters submitted correct values, return the current distribution list + if (approvedSubmitters.length === 0) { + console.log("NO NODES TO REWARD"); + return distributionList; + } + + // Calculate the reward for each approved submitter by dividing the bounty per round equally among them + const reward = 0; + + // Assign the calculated reward to each approved submitter + approvedSubmitters.forEach((candidate) => { + distributionList[candidate] = reward; + }); + + // Return the final distribution list + return distributionList; +} + +=== File: src/task/2-submission.js === + +-- Chunk 1 -- +// 2-submission.js:3-39 +async function submission(roundNumber) { + /** + * Submit the task proofs for auditing + * Must return a string of max 512 bytes to be submitted on chain + */ + try { + let credentialCount = 0; + + // Check for github_username + const githubUsername = await namespaceWrapper.storeGet("github_username"); + if (githubUsername) credentialCount++; + + // Check for github_token + const githubToken = await namespaceWrapper.storeGet("github_token"); + if (githubToken) credentialCount++; + + // Check for claude_api_key + const claudeApiKey = await namespaceWrapper.storeGet("claude_api_key"); + if (claudeApiKey) credentialCount++; + + // If no credentials found, skip submission + if (credentialCount === 0) { + console.log('No credentials found, skipping submission'); + return; + } + + // Create submission string with format: MAIN_ACCOUNT_PUBKEY&credentialCount + const submissionString = `${MAIN_ACCOUNT_PUBKEY}&${credentialCount}`; + + console.log('Submitting credential count:', credentialCount); + return submissionString; + + } catch (error) { + console.error("MAKE SUBMISSION ERROR:", error); + return; // Return nothing in case of error + } +} + +=== File: src/task/github-flow.js === + +-- Chunk 1 -- +// github-flow.js:4-153 +async function handleGitHubFlow(browser) { + let githubPage; + try { + console.log('Creating new page for GitHub flow...'); + // Create new page for GitHub flow + githubPage = await browser.newPage(); + console.log('New page created successfully'); + + // Add close listener to reset flag + githubPage.on("close", async () => { + console.log('GitHub page closed, resetting flow flag...'); + try { + const pages = await browser.pages(); + const landingPage = pages[0]; + await landingPage.evaluate(() => { + window.flowInProgress = false; + }); + console.log('Flow flag reset successfully'); + } catch (error) { + console.log("Could not reset flow flag on page close:", error); + } + }); + + console.log('Setting viewport...'); + // Set viewport size + await githubPage.setViewport({ + width: 1700, + height: 992, + }); + console.log('Viewport set successfully'); + + console.log('Navigating to GitHub login...'); + // Navigate to GitHub login + await githubPage.goto("https://github.com/login", { + waitUntil: "networkidle0", + timeout: 600000, // 10 minutes + }); + console.log('Navigation to GitHub login successful'); + + // Function to check if we're on the wrong page and need to restart + const checkAndRestartFlow = async () => { + const currentUrl = githubPage.url(); + const validUrls = [ + "https://github.com/login", + "https://github.com/", + "https://github.com/dashboard", + "https://github.com/settings/tokens/new", + "https://github.com/settings/tokens" + ]; + + if (!validUrls.some(url => currentUrl.startsWith(url))) { + await githubPage.evaluate(() => { + alert("⚠️ Oops! You've navigated to the wrong page. Redirecting back to login..."); + }); + + // Wait a moment for the alert to be seen + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Navigate back to login + await githubPage.goto("https://github.com/login", { + waitUntil: "networkidle0", + timeout: 600000, + }); + return true; // Flow needs restart + } + return false; // Flow can continue + }; + + // Add navigation listener to check for wrong paths + githubPage.on('framenavigated', async frame => { + if (frame === githubPage.mainFrame()) { + await checkAndRestartFlow(); + } + }); + + // Add warning message and highlight login field + await githubPage.evaluate(() => { + const loginField = document.querySelector("#login_field"); + const createAccountLink = document.querySelector('a[href="/signup"]'); + + if (loginField) { + // Create warning message + const warningDiv = document.createElement("div"); + warningDiv.textContent = + "⚠️ Please use your spare GitHub account. If you don't have one, click the highlighted 'Create an account' button below."; + warningDiv.style.cssText = ` + color: #b59f00; + background: #fffbe6; + border: 1px solid #fff5c1; + border-radius: 6px; + font-size: 14px; + font-weight: 600; + margin-bottom: 15px; + padding: 8px 12px; + text-align: center; + animation: warningPulse 2s infinite; + `; + + // Add animation style + const style = document.createElement("style"); + style.textContent = ` + @keyframes warningPulse { + 0% { opacity: 0.8; transform: scale(1); } + 50% { opacity: 1; transform: scale(1.02); } + 100% { opacity: 0.8; transform: scale(1); } + } + `; + document.head.appendChild(style); + + // Insert warning before login field + loginField.parentNode.insertBefore(warningDiv, loginField); + + // Highlight login field + loginField.style.cssText = ` + border: 2px solid #e3b341 !important; + box-shadow: 0 0 5px rgba(227, 179, 65, 0.3); + `; + + // Highlight the Create Account link if it exists + if (createAccountLink) { + createAccountLink.style.cssText = ` + background: #2ea44f !important; + color: white !important; + border-radius: 6px; + padding: 8px 16px !important; + animation: pulseBorder 2s infinite; + font-weight: 600 !important; + box-shadow: 0 0 10px rgba(46, 164, 79, 0.4); + `; + + // Add target="_blank" to open in new tab + createAccountLink.setAttribute('target', '_blank'); + + // Add note about returning + const returnNote = document.createElement('div'); + returnNote.textContent = "After creating your account, please return to this tab to continue."; + returnNote.style.cssText = ` + color: #1a7f37; + font-size: 12px; + margin-top: 8px; + text-align: center; + font-style: italic; + `; + createAccountLink.parentNode.appendChild(returnNote); + + // Add pulse animation + const style = document.createElement('style'); + style.textContent = ` + @keyframes pulseBorder { + 0% { box-shadow: 0 0 5px rgba(46, 164, 79, 0.4); } + +-- Chunk 2 -- +// github-flow.js:154-303 + 50% { box-shadow: 0 0 15px rgba(46, 164, 79, 0.6); } + 100% { box-shadow: 0 0 5px rgba(46, 164, 79, 0.4); } + } + `; + document.head.appendChild(style); + } + } + }); + + // Show login alert with create account option + await githubPage.evaluate(() => { + alert("Please login to GitHub or create a new account (opens in new tab) to continue"); + }); + + // Modified navigation waiting logic + while (true) { + try { + await githubPage.waitForNavigation({ + waitUntil: "networkidle0", + timeout: 60000 // 1 minute + }).catch(() => {}); // Ignore timeout errors + + // Check if we need to restart the flow + const needsRestart = await checkAndRestartFlow(); + if (needsRestart) { + continue; // Restart the loop + } + + const currentUrl = githubPage.url(); + if (currentUrl === "https://github.com/" || currentUrl === "https://github.com/dashboard") { + break; // User is logged in, continue with the flow + } + } catch (error) { + console.log("Navigation error:", error); + } + + // Small delay between checks + await new Promise(resolve => setTimeout(resolve, 1000)); + } + + // Now check if login was successful + const currentUrl = githubPage.url(); + if (currentUrl === "https://github.com/" || currentUrl === "https://github.com/dashboard") { + // Get and store GitHub username + const username = await githubPage.evaluate(() => { + const metaElement = document.querySelector('meta[name="user-login"]'); + return metaElement ? metaElement.getAttribute("content") : null; + }); + + if (username) { + console.log("Successfully retrieved username:", username); + await namespaceWrapper.storeSet("github_username", username); + } + + await githubPage.evaluate(() => { + alert( + "You are now successfully logged in.\nRedirecting to token creation page in 3 seconds...", + ); + }); + + await new Promise((resolve) => setTimeout(resolve, 3000)); + + // Navigate to tokens page + await githubPage.goto("https://github.com/settings/tokens/new", { + waitUntil: "networkidle0", + timeout: 600000, // 10 minutes + }); + + // Add hints and highlights for token creation + await githubPage.evaluate(() => { + const inputElement = document.querySelector( + 'input[name="oauth_access[description]"]', + ); + if (inputElement) { + // Style the input element + inputElement.style.cssText = ` + border: 2px solid #2ea44f !important; + box-shadow: 0 0 5px rgba(46, 164, 79, 0.4); + animation: pulse 2s infinite; + `; + + // Create hint element + const hintElement = document.createElement("span"); + hintElement.textContent = + "Please enter a name for your token, for example: 247 builder"; + hintElement.style.cssText = ` + margin-left: 10px; + color: #2ea44f; + font-size: 12px; + font-style: italic; + display: inline-block; + vertical-align: middle; + animation: pulse 2s infinite; + `; + + // Insert hint after the input + inputElement.parentNode.insertBefore( + hintElement, + inputElement.nextSibling, + ); + } + + // Highlight the repo scope checkbox + const checkbox = document.querySelector('input[value="repo"]'); + if (checkbox) { + const checkboxContainer = + checkbox.closest("li") || checkbox.parentElement; + checkboxContainer.style.cssText = ` + background: rgba(46, 164, 79, 0.1); + border-radius: 6px; + padding: 8px; + border: 2px solid #2ea44f; + margin: 5px 0; + animation: pulse 2s infinite; + `; + + const checkboxHint = document.createElement("div"); + checkboxHint.textContent = + 'Please check this box and scroll down to the "Generate token" button'; + checkboxHint.style.cssText = ` + color: #2ea44f; + font-size: 12px; + font-style: italic; + margin-top: 5px; + animation: pulse 2s infinite; + `; + + checkboxContainer.appendChild(checkboxHint); + } + + // Add pulse animation + const styleSheet = document.createElement("style"); + styleSheet.textContent = ` + @keyframes pulse { + 0% { opacity: 0.6; } + 50% { opacity: 1; } + 100% { opacity: 0.6; } + } + `; + document.head.appendChild(styleSheet); + }); + + // Wait for navigation after clicking generate token + await githubPage.waitForNavigation({ + waitUntil: "networkidle0", + timeout: 600000, // 10 minutes + }); + + // Check if we're on the tokens page and save token + if (githubPage.url() === "https://github.com/settings/tokens") { + +-- Chunk 3 -- +// github-flow.js:304-413 + console.log("Successfully generated token"); + + const token = await githubPage.evaluate(() => { + const tokenElement = document.querySelector("#new-oauth-token"); + return tokenElement ? tokenElement.textContent : null; + }); + + if (token) { + console.log("Successfully retrieved token"); + await namespaceWrapper.storeSet("github_token", token); + + let postSuccess = true; + let errorMessages = []; + + // Post GitHub username to API + try { + const usernameResponse = await axios.post( + "http://localhost:30017/api/task-variables", + { + label: "GITHUB_USERNAME", + value: username, + }, + ); + if (!usernameResponse.data.success) { + postSuccess = false; + errorMessages.push(`GitHub Username Error: ${usernameResponse.data.message || 'Failed to save username'}`); + } + } catch (error) { + postSuccess = false; + const errorMsg = error.response?.data?.message || error.message; + errorMessages.push(`GitHub Username Error: ${errorMsg}`); + } + + // Post GitHub token to API + try { + const tokenResponse = await axios.post( + "http://localhost:30017/api/task-variables", + { + label: "GITHUB_TOKEN", + value: token, + }, + ); + if (!tokenResponse.data.success) { + postSuccess = false; + errorMessages.push(`GitHub Token Error: ${tokenResponse.data.message || 'Failed to save token'}`); + } + } catch (error) { + postSuccess = false; + const errorMsg = error.response?.data?.message || error.message; + errorMessages.push(`GitHub Token Error: ${errorMsg}`); + } + + // Show appropriate alert based on results + await githubPage.evaluate((success, errors) => { + window.flowInProgress = false; + if (success) { + alert("✅ Your GitHub information has been successfully saved!\nYou can now close this tab and return to the main page."); + } else { + if (errors.some(err => err.includes("already exists"))) { + alert("⚠️ Your GitHub information was saved locally but couldn't be updated in task variables because they already exist.\nYou can safely continue with the existing credentials."); + } else { + alert(`⚠️ There were some issues:\n\n${errors.join('\n\n')}\n\nPlease try again.`); + } + } + }, postSuccess, errorMessages); + + // Only close the GitHub page + if (!githubPage.isClosed()) { + await githubPage.close(); + } + return true; + } + } + } + return false; + } catch (error) { + console.error("GitHub flow error:", error); + console.error("Error stack:", error.stack); + // Reset flow flag on error + try { + await browser.evaluate(() => { + window.flowInProgress = false; + }); + console.log('Flow flag reset after error'); + } catch (evalError) { + console.error('Error resetting flow flag:', evalError); + } + return false; + } finally { + // Only close the GitHub page in finally if it exists and hasn't been closed + if (githubPage && !githubPage.isClosed()) { + try { + await githubPage.close(); + } catch (error) { + console.log("Page already closed"); + } + } + // Reset flow flag in case of manual close or any other scenario + try { + await browser.pages().then(async (pages) => { + const landingPage = pages[0]; + await landingPage.evaluate(() => { + window.flowInProgress = false; + }); + }); + } catch (error) { + console.log("Could not reset flow flag:", error); + } + } +} + +=== File: src/task/landing-page.js === + +-- Chunk 1 -- +// landing-page.js:1-150 +function getLandingPageContent() { + + return ` + + + + + + +
+ +
+ +

Get Set Up to Build with Prometheus

+
To activate Prometheus Builder, connect your tools and unlock full functionality.
Step 1 – Link your
GitHub account
Step 2 – Add your Anthropic Key (AI Agent)
💡 Link additional items to earn Super Contributor rewards and gain access to advanced features.
+ +
+

Free Accounts

+

Connect popular tools to complete more tasks and participate in governance.

+
+
+
+
+
+ + + +
+
+

Link Github

+

Create an account
to contribute to projects

+
+
+
+
Step 1
+
Log In or Sign Up to your Github account
+
+
+
*Mandatory for Prometheus Task
+
+
+
+ +
+

Paid Accounts

+

Some tasks require paid subscriptions, and often have greater rewards.

+
+
+
+
+
+ + + + + + + + + + +
+
+

Link Anthropic

+

Connect Anthropic's AI Agent to build apps.

+
+
You have to complete step 1 first
+
+
+
Step 2
+
Log In or Sign Up to your Anthropic account
+
+
+
*Mandatory for Prometheus Task
+
+ +
+
+ Gemini Logo +
+
+

Link Gemini

+

Coming Soon

+
+
+
+
+ + + + + + + `; +} + +=== File: src/task/5-routes.js === + +-- Chunk 1 -- +// 5-routes.js:5-73 +async function routes() { + /** + * + * Define all your custom routes here + * + */ + + // Landing page route + app.get("/landing-page", async (_req, res) => { + try { + // Get stored values from DB using correct keys + const githubToken = await namespaceWrapper.storeGet("github_token"); + const githubUsername = await namespaceWrapper.storeGet("github_username"); + const claudeApiKey = await namespaceWrapper.storeGet("claude_api_key"); + + // Get the landing page HTML with namespaceWrapper + const html = getLandingPageContent(namespaceWrapper); + + // Inject the DB values into the response + const htmlWithData = html.replace( + '