diff --git a/.changeset/olive-cows-serve.md b/.changeset/olive-cows-serve.md new file mode 100644 index 000000000..548645ca4 --- /dev/null +++ b/.changeset/olive-cows-serve.md @@ -0,0 +1,5 @@ +--- +"@rabbitholegg/create-plugin": minor +--- + +create plugin details config file diff --git a/.github/scripts/registerNewPlugins.ts b/.github/scripts/registerNewPlugins.ts new file mode 100644 index 000000000..cd5187f51 --- /dev/null +++ b/.github/scripts/registerNewPlugins.ts @@ -0,0 +1,90 @@ +const _axios = require("axios"); +const _fs = require("fs/promises"); +const _yaml = require("js-yaml"); +const _utils = require("./utils"); + +async function sendPluginDetailsToAPI(detailsPath: string): Promise { + const fileContents = await _fs.readFile(detailsPath, "utf8"); + const details = _yaml.load(fileContents); + const { project, tasks } = details; + + // send project details to staging API + const { data: stagingData } = await _axios.post( + `${process.env.STAGING_API_URL}/plugins/add-project`, + { + ...project, + approvedForTerminal: true, + }, + { + headers: { + Authorization: `Bearer ${process.env.BOOST_API_TOKEN}`, + }, + }, + ); + + // send project details to production API + const { data } = await _axios.post( + `${process.env.PRODUCTION_API_URL}/plugins/add-project`, + project, + { + headers: { + Authorization: `Bearer ${process.env.BOOST_API_TOKEN}`, + }, + }, + ); + + for (const task of tasks) { + // send task details to staging API + await _axios.post( + `${process.env.STAGING_API_URL}/plugins/add-task`, + { + ...task, + projectId: stagingData.projectId, + approvedForTerminal: true, + }, + { + headers: { + Authorization: `Bearer ${process.env.BOOST_API_TOKEN}`, + }, + }, + ); + + // send task details to production API + await _axios.post( + `${process.env.PRODUCTION_API_URL}/plugins/add-task`, + { + ...task, + projectId: data.projectId, + }, + { + headers: { + Authorization: `Bearer ${process.env.BOOST_API_TOKEN}`, + }, + }, + ); + } + + console.log(`Successfully registered plugin details for ${project.name}`); +} + +async function _main() { + const newPackagesPaths = await _utils.getNewPackages(); + const updatedDetailsPaths = await _utils.getUpdatedPluginDetailsPaths(); + const uniqueDetailsPaths = Array.from( + new Set([...newPackagesPaths, ...updatedDetailsPaths]), + ); + if (uniqueDetailsPaths.length) { + const validDetailsPaths = await _utils.validatePluginDetailsPaths( + uniqueDetailsPaths, + ); + for (const detailsPath of validDetailsPaths) { + await sendPluginDetailsToAPI(detailsPath); + } + } else { + console.log("No new packages found."); + } +} + +_main().catch((error) => { + throw new Error(`Error registering plugin details: ${error}`); +}); diff --git a/.github/scripts/utils.ts b/.github/scripts/utils.ts new file mode 100644 index 000000000..01a822ae2 --- /dev/null +++ b/.github/scripts/utils.ts @@ -0,0 +1,74 @@ +const path = require("path"); +const { exec } = require("child_process"); +const file = require("fs/promises"); +const { promisify } = require("util"); + +const execAsync = promisify(exec); + +async function getNewPackages(): Promise { + // Get list of all directories in packages/ on main + const { stdout: mainDirs } = await execAsync( + "git ls-tree -d --name-only main:packages/", + ); + const mainPackagesSet = new Set( + mainDirs.split("\n").filter((name: string) => name.trim() !== ""), + ); + + // Get list of all directories in packages/ in the current HEAD + const { stdout: headDirs } = await execAsync( + "git ls-tree -d --name-only HEAD:packages/", + ); + const headPackages = headDirs + .split("\n") + .filter((name: string) => name.trim() !== ""); + + // Filter out directories that are also present on main + const newPackageDirs = headPackages + .filter((pkg: string) => !mainPackagesSet.has(pkg)) + .map((pkg: string) => path.join("packages", pkg)); + + return newPackageDirs; +} + +async function getUpdatedPluginDetailsPaths(): Promise { + // compares the current HEAD with the previous commit to get the updated plugin details + const { stdout, stderr } = await execAsync( + "git diff --name-only HEAD^ HEAD -- 'packages/*plugin-details.yml'", + ); + if (stderr) { + throw new Error(`Error getting updated plugin details: ${stderr}`); + } + const detailsPaths = stdout + .split("\n") + .filter( + (path: string) => + path.trim() !== "" && path.includes("plugin-details.yml"), + ) + .map((path: string) => path.replace("/plugin-details.yml", "").trim()); + + return detailsPaths; +} + +async function validatePluginDetailsPaths( + newPackagesPaths: string[], +): Promise { + const validDetailsPaths: string[] = []; + + for (const packageDir of newPackagesPaths) { + const detailsPath = path.join(packageDir, "plugin-details.yml"); + try { + await file.access(detailsPath); + console.log(`Valid: ${detailsPath} exists.`); + validDetailsPaths.push(detailsPath); + } catch (error) { + throw new Error(`Missing plugin-details.yml in package: ${packageDir}`); + } + } + return validDetailsPaths; +} + +module.exports = { + getNewPackages, + getUpdatedPluginDetailsPaths, + validatePluginDetailsPaths, +}; diff --git a/.github/scripts/verifyConfigFileFormat.ts b/.github/scripts/verifyConfigFileFormat.ts new file mode 100644 index 000000000..5315692a7 --- /dev/null +++ b/.github/scripts/verifyConfigFileFormat.ts @@ -0,0 +1,79 @@ +const axios = require("axios"); +const fs = require("fs/promises"); +const zod = require("zod"); +const yaml = require("js-yaml"); +const utils = require("./utils"); +const { z } = zod; + +const ProjectConfigSchema = z.object({ + name: z.string(), + iconOption: z.string().url().optional(), + appLink: z.string().url().optional(), +}); + +const TaskConfigSchema = z.object({ + name: z.string(), + link: z.string().url(), + iconOption: z.string().url(), + actionPluginId: z.string(), +}); + +const PluginConfigSchema = z.object({ + project: ProjectConfigSchema, + tasks: z.array(TaskConfigSchema).nonempty(), +}); + +async function validateConfigFile(filePath: string): Promise { + try { + const configFileContent = await fs.readFile(filePath, "utf8"); + const config = yaml.load(configFileContent); + PluginConfigSchema.parse(config); + const { project, tasks } = config; + + for (const task of tasks) { + // validate each unique icon option url + const uniqueIconOptions = new Set( + [project.iconOption, task.iconOption].filter(Boolean), + ); + for (const iconOption of uniqueIconOptions) { + await validateIcon(iconOption); + } + } + console.log(`Config in ${filePath} is valid.`); + } catch (error) { + console.error(`Error validating config in ${filePath}:`, error); + process.exit(1); + } +} + +async function validateIcon(iconUrl: string) { + const response = await axios.post( + `${process.env.APP_URL}/plugins/validate-icon`, + { + iconOption: iconUrl, + }, + ); + if (response.status === 200) { + console.log("Icon is valid:", iconUrl); + } else { + throw new Error(`Icon validation failed for ${iconUrl}`); + } +} + +async function main() { + const newPackagesPaths = await utils.getNewPackages(); + const updatedDetailsPaths = await utils.getUpdatedPluginDetailsPaths(); + const uniqueDetailsPaths = Array.from( + new Set([...newPackagesPaths, ...updatedDetailsPaths]), + ); + if (uniqueDetailsPaths.length) { + const paths = await utils.validatePluginDetailsPaths(uniqueDetailsPaths); + for (const path of paths) { + await validateConfigFile(path); + } + } else { + console.log("No new packages found."); + } +} + +main(); diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index b33bb984d..3fd274084 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -1,55 +1,37 @@ -name: Pull request -on: - pull_request: - types: [opened, reopened, synchronize, ready_for_review] - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - verify: - name: Verify - uses: ./.github/workflows/verify.yml - secrets: inherit - - # bench: - # if: false - # name: Benchmark - # runs-on: ubuntu-latest - # timeout-minutes: 5 - - # steps: - # - name: Clone repository - # uses: actions/checkout@v3 - - # - name: Install dependencies - # uses: ./.github/actions/install-dependencies - - # - name: Run benchmarks - # run: pnpm bench:ci - # env: - - # - name: Report benchmarks - # run: pnpm bun ./.github/scripts/bench.ts - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # size: - # name: Size - # runs-on: ubuntu-latest - # timeout-minutes: 5 - - # steps: - # - name: Clone repository - # uses: actions/checkout@v3 - - # - name: Install dependencies - # uses: ./.github/actions/install-dependencies - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # - name: Report build size - # uses: preactjs/compressed-size-action@v2 - # with: - # repo-token: ${{ secrets.GITHUB_TOKEN }} +name: Pull request +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + branches: + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + # verify: + # name: Verify + # uses: ./.github/workflows/verify.yml + # secrets: inherit + + verify-config-file-format: + name: Verify Config File Format + runs-on: ubuntu-latest + + steps: + - name: Clone repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch target branch + run: git fetch origin main:main + + - name: Install dependencies + uses: ./.github/actions/install-dependencies + + - name: Run config file format verification script + run: npx ts-node ./.github/scripts/verifyConfigFileFormat.ts + env: + APP_URL: https://5efc-70-67-36-90.ngrok-free.app diff --git a/.github/workflows/on-push-to-main.yml b/.github/workflows/on-push-to-main.yml index ea20e9dfa..78eb638b3 100644 --- a/.github/workflows/on-push-to-main.yml +++ b/.github/workflows/on-push-to-main.yml @@ -8,70 +8,103 @@ concurrency: cancel-in-progress: true jobs: - verify: - name: Verify - uses: ./.github/workflows/verify.yml - secrets: inherit + # verify: + # name: Verify + # uses: ./.github/workflows/verify.yml + # secrets: inherit - changesets: - name: Changesets + # changesets: + # name: Changesets + # runs-on: ubuntu-latest + # timeout-minutes: 5 + + # steps: + # - name: Clone repository + # uses: actions/checkout@v4 + # with: + # # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits + # fetch-depth: 0 + # - uses: actions/setup-node@v4 + # with: + # node-version: 18 + # registry-url: https://registry.npmjs.org/ + # scope: "@rabbitholegg" + + # - name: Install dependencies + # uses: ./.github/actions/install-dependencies + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # - name: Create Version Pull Request + # uses: changesets/action@v1 + # with: + # version: pnpm changeset:version + # commit: "chore: version package" + # title: "chore: version package" + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + register_new_plugins: + name: Register New Plugins runs-on: ubuntu-latest - timeout-minutes: 5 steps: - name: Clone repository uses: actions/checkout@v4 with: - # This makes Actions fetch all Git history so that Changesets can generate changelogs with the correct commits fetch-depth: 0 - - uses: actions/setup-node@v4 - with: - node-version: 18 - registry-url: https://registry.npmjs.org/ - scope: "@rabbitholegg" - name: Install dependencies uses: ./.github/actions/install-dependencies - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Create Version Pull Request - uses: changesets/action@v1 - with: - version: pnpm changeset:version - commit: "chore: version package" - title: "chore: version package" + - name: Fetch target branch + run: git fetch origin + + - name: Call API to register new plugin details without retry logic + run: | + for attempt in {1..3}; do + npx ts-node ./.github/scripts/registerNewPlugins.ts && break || { + echo "Attempt $attempt failed. Retrying in 5 seconds..." + sleep 5 + } + done + if [ $attempt -eq 3 ]; then + echo "All attempts failed. Exiting..." + exit 1 + fi env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + STAGING_API_URL: https://5efc-70-67-36-90.ngrok-free.app + PRODUCTION_API_URL: https://5efc-70-67-36-90.ngrok-free.app + BOOST_API_TOKEN: ${{ secrets.BOOST_API_TOKEN }} - release: - name: Release - needs: verify - runs-on: ubuntu-latest - timeout-minutes: 5 + # release: + # name: Release + # needs: verify + # runs-on: ubuntu-latest + # timeout-minutes: 5 - steps: - - name: Clone repository - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - registry-url: https://registry.npmjs.org/ - scope: "@rabbitholegg" + # steps: + # - name: Clone repository + # uses: actions/checkout@v4 + # - uses: actions/setup-node@v4 + # with: + # node-version: 18 + # registry-url: https://registry.npmjs.org/ + # scope: "@rabbitholegg" - - name: Install dependencies - uses: ./.github/actions/install-dependencies - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # - name: Install dependencies + # uses: ./.github/actions/install-dependencies + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Create .npmrc - run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc + # - name: Create .npmrc + # run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc - - name: Publish to NPM - uses: changesets/action@v1 - with: - publish: pnpm publish-packages - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN }} + # - name: Publish to NPM + # uses: changesets/action@v1 + # with: + # publish: pnpm publish-packages + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + # NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN }} diff --git a/apps/create-plugin/src/builder.ts b/apps/create-plugin/src/builder.ts index 282599f96..eed8f8866 100644 --- a/apps/create-plugin/src/builder.ts +++ b/apps/create-plugin/src/builder.ts @@ -175,6 +175,12 @@ async function replaceProjectName(params: BuilderParams) { const indexTemplate = Handlebars.compile(index) await fs.writeFile(indexPath, indexTemplate(params)) console.log(`\t ${arrow} Updated file ${cyan('index.ts')}!`) + + const configPath = path.join(dest, 'plugin-details.yml') + const config = await fs.readFile(configPath, 'utf8') + const configTemplate = Handlebars.compile(config) + await fs.writeFile(configPath, configTemplate(params)) + console.log(`\t ${arrow} Updated file ${cyan('plugin-details.yml')}!`) } /** diff --git a/apps/create-plugin/src/prompts.ts b/apps/create-plugin/src/prompts.ts index 6637d7ba2..d96f1441d 100644 --- a/apps/create-plugin/src/prompts.ts +++ b/apps/create-plugin/src/prompts.ts @@ -1,7 +1,12 @@ import { Answers, PromptObject } from 'prompts' import { green, red } from 'picocolors' import { Address, type Hash, parseUnits } from 'viem' -import { actionQuestions, mainQuestions, getTxHashQuestion } from './questions' +import { + actionQuestions, + mainQuestions, + getTxHashQuestion, + detailsQuestions +} from './questions' import { ActionParamKeys, Actions } from './types' import { getTokenInfo, getTransaction } from './viem' import type { @@ -78,12 +83,15 @@ export async function askQuestions() { addAnotherTransaction = addAnother } + const detailsResponse = await _prompts(detailsQuestions) + return { projectName: response.name, chains: response.chain, tx: transactions, actionType: response.action, publish: response.publish, + details: detailsResponse, } } diff --git a/apps/create-plugin/src/questions.ts b/apps/create-plugin/src/questions.ts index cf068eafd..785c21ee1 100644 --- a/apps/create-plugin/src/questions.ts +++ b/apps/create-plugin/src/questions.ts @@ -66,6 +66,43 @@ export const mainQuestions: PromptObject[] = [ }, ] +export const detailsQuestions: PromptObject[] = [ + { + type: 'text', + name: 'projectUrl', + message: 'What is the project url? (Optional)', + validate: (name: string) => { + if (name !== '' && !/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test(name)) { + return 'Please enter a valid URL'; + } + return true; + }, + }, + { + type: 'text', + name: 'iconUrl', + message: 'What is the project icon url? (Optional)', + initial: '', + validate: (name: string) => { + if (name !== '' && !/^(https?:\/\/.*\.(?:png|jpg|jpeg|svg)(\?.*)?)$/.test(name)) { + return 'Please enter a valid image URL'; + } + return true; + }, + }, + { + type: 'text', + name: 'taskUrl', + message: 'What is the action specfic url (ie: https://myProject/trade)? (Optional)', + validate: (name: string) => { + if (name !== '' && !/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/.test(name)) { + return 'Please enter a valid URL'; + } + return true; + }, + }, +] + const descriptionQuestion: PromptObject = { type: 'text', name: 'description', diff --git a/apps/create-plugin/template/plugin-details.yml b/apps/create-plugin/template/plugin-details.yml new file mode 100644 index 000000000..6cc8c1f5f --- /dev/null +++ b/apps/create-plugin/template/plugin-details.yml @@ -0,0 +1,12 @@ +project: + name: {{capitalize projectName}} + iconOption: {{{details.iconUrl}}} + appLink: {{{details.projectUrl}}} + +tasks: + # action specific name, this is what will show as the default title for your boosts + - name: {{capitalize actionType}} on {{capitalize projectName}} + # action specific link to your project. (ie: myapp.com/{{lowercase actionType}}) + link: {{#if details.taskUrl}}{{{details.taskUrl}}}{{else}}{{{details.projectUrl}}}{{/if}} + iconOption: {{{details.iconUrl}}} + actionPluginId: {{lowercase actionType}} diff --git a/apps/create-plugin/template/src/test-transactions.ts.t b/apps/create-plugin/template/src/test-transactions.ts.t index 15f626875..946652968 100644 --- a/apps/create-plugin/template/src/test-transactions.ts.t +++ b/apps/create-plugin/template/src/test-transactions.ts.t @@ -8,8 +8,8 @@ import { type {{capitalize actionType}}ActionParams } from '@rabbitholegg/questd {{/hasAmountKey}} import { createTestCase, - type TestParams,{{#eq actionType 'bridge'}} - Chains{{/eq}} + type TestParams,{{#eq actionType 'bridge'}}{{#if tx.length}} + Chains{{/if}}{{/eq}} } from '@rabbitholegg/questdk-plugin-utils' {{#unless tx.length}} @@ -29,12 +29,12 @@ export const {{uppercase actionType}}_TEST: TestParams<{{capitalize actionType}} destinationChainId: 0, {{else}} {{#eq actionType 'mint'}} - tokenId: '0', - amount: '0', + chainId: 0, + contractAddress: '0x0', {{else}} {{#eq actionType 'burn'}} - tokenId: '0', - amount: '0', + chainId: 0, + contractAddress: '0x0', {{else}} {{#eq actionType 'delegate'}} chainId: 0, diff --git a/packages/across/plugin-details.yml b/packages/across/plugin-details.yml new file mode 100644 index 000000000..9f08316d9 --- /dev/null +++ b/packages/across/plugin-details.yml @@ -0,0 +1,12 @@ +project: + name: AcrossTest + iconOption: https://assets.coingecko.com/coins/images/13397/standard/Graph_Token.png?1696513159 + appLink: https://acrosstest.org + +task: + # action specific name, this is what will show as the default title for your boosts + name: Mint on acrosstest + # action specific link to your project. (ie: myapp.com/mint) + link: https://acrosstest.org/mint + iconOption: https://assets.coingecko.com/coins/images/13397/standard/Graph_Token.png?1696513159 + actionPluginId: mint diff --git a/packages/firsttest/README.md b/packages/firsttest/README.md new file mode 100644 index 000000000..f928de72c --- /dev/null +++ b/packages/firsttest/README.md @@ -0,0 +1,15 @@ +## FirstTest Plugin for Boost + +### New Plugin TODO list + +1. Find the ABI of the function your transaction is calling, and add export it as a const in the abi.ts file + 1. this can be multiple ABIs, if the transaction is calling multiple functions +2. in FirstTest.ts, fill out each Action function by mapping the ActionParams to the ABI of the function + + + +### Actions and Descriptions + + + +### Example Transactions \ No newline at end of file diff --git a/packages/firsttest/package.json b/packages/firsttest/package.json new file mode 100644 index 000000000..70d7669f3 --- /dev/null +++ b/packages/firsttest/package.json @@ -0,0 +1,42 @@ +{ + "name": "@rabbitholegg/questdk-plugin-firsttest", + "private": true, + "version": "1.0.0-alpha.0", + "type": "module", + "exports": { + "require": "./dist/cjs/index.js", + "import": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts" + }, + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "description": "Plugin for firstTest", + "scripts": { + "bench": "vitest bench", + "bench:ci": "CI=true vitest bench", + "build": "pnpm run clean && pnpm run build:cjs && pnpm run build:esm && pnpm run build:types", + "build:cjs": "tsc --project tsconfig.build.json --module commonjs --outDir ./dist/cjs --removeComments --verbatimModuleSyntax false && echo > ./dist/cjs/package.json '{\"type\":\"commonjs\"}'", + "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir ./dist/esm && echo > ./dist/esm/package.json '{\"type\":\"module\",\"sideEffects\":false}'", + "build:types": "tsc --project tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", + "clean": "rimraf dist", + "format": "rome format . --write", + "lint": "rome check .", + "lint:fix": "pnpm lint --apply", + "test": "vitest dev", + "test:cov": "vitest dev --coverage", + "test:ci": "CI=true vitest --coverage", + "test:ui": "vitest dev --ui" + }, + "keywords": [], + "author": "", + "license": "ISC", + "types": "./dist/types/index.d.ts", + "typings": "./dist/types/index.d.ts", + "devDependencies": { + "tsconfig": "workspace:*" + }, + "dependencies": { + "@rabbitholegg/questdk-plugin-utils": "workspace:*", + "@rabbitholegg/questdk": "workspace:*" + } +} diff --git a/packages/firsttest/plugin-details.yml b/packages/firsttest/plugin-details.yml new file mode 100644 index 000000000..d39cb4e10 --- /dev/null +++ b/packages/firsttest/plugin-details.yml @@ -0,0 +1,12 @@ +project: + name: FirstTest + iconOption: https://assets.coingecko.com/coins/images/325/standard/Tether.png?1696501661 + appLink: https://firsttest.org + +task: + # action specific name, this is what will show as the default title for your boosts + name: Mint on FirstTest + # action specific link to your project. (ie: myapp.com/mint) + link: https://firsttest.org/mint + iconOption: https://assets.coingecko.com/coins/images/325/standard/Tether.png?1696501661 + actionPluginId: mint diff --git a/packages/firsttest/src/FirstTest.test.ts b/packages/firsttest/src/FirstTest.test.ts new file mode 100644 index 000000000..91dd3397a --- /dev/null +++ b/packages/firsttest/src/FirstTest.test.ts @@ -0,0 +1,31 @@ +import { apply } from '@rabbitholegg/questdk/filter' +import { describe, expect, test } from 'vitest' +import { passingTestCases, failingTestCases } from './test-transactions' +import { mint } from './FirstTest' + +describe('Given the firsttest plugin', () => { + describe('When handling the mint action', () => { + + describe('should pass filter with valid transactions', () => { + passingTestCases.forEach((testCase) => { + const { transaction, description, params } = testCase + test(description, async () => { + const filter = await mint(params) + expect(apply(transaction, filter)).to.be.false + }) + }) + }) + + describe('should not pass filter with invalid transactions', () => { + failingTestCases.forEach((testCase) => { + const { transaction, description, params } = testCase + test(description, async () => { + const filter = await mint(params) + expect(apply(transaction, filter)).to.be.false + }) + }) + }) + }) +}) + +// changes diff --git a/packages/firsttest/src/FirstTest.ts b/packages/firsttest/src/FirstTest.ts new file mode 100644 index 000000000..7e2d2cadc --- /dev/null +++ b/packages/firsttest/src/FirstTest.ts @@ -0,0 +1,27 @@ +import { + type TransactionFilter, + type MintActionParams, + compressJson, +} from '@rabbitholegg/questdk' +import { type Address } from 'viem' + + export const mint = async(_params: MintActionParams): Promise => { + return compressJson({ + chainId: '0x0', + to: '0x0', // The to field is the address of the contract we're interacting with + input: {}, // The input object is where we'll put the ABI and the parameters + }) + + } + +export const getSupportedTokenAddresses = async ( + _chainId: number, +): Promise => { + // Given a specific chain we would expect this function to return a list of supported token addresses + return [] +} + +export const getSupportedChainIds = async (): Promise => { + // This should return all of the ChainIds that are supported by the Project we're integrating + return [] +} diff --git a/packages/firsttest/src/index.ts b/packages/firsttest/src/index.ts new file mode 100644 index 000000000..821f8d1bb --- /dev/null +++ b/packages/firsttest/src/index.ts @@ -0,0 +1,16 @@ +import { + type IActionPlugin, +} from '@rabbitholegg/questdk' + +import { + mint, + getSupportedChainIds, + getSupportedTokenAddresses, +} from './FirstTest.js' + +export const FirstTest: IActionPlugin = { + pluginId: "firsttest", + getSupportedTokenAddresses, + getSupportedChainIds, + mint, +} diff --git a/packages/firsttest/src/test-transactions.ts b/packages/firsttest/src/test-transactions.ts new file mode 100644 index 000000000..c9791aacf --- /dev/null +++ b/packages/firsttest/src/test-transactions.ts @@ -0,0 +1,29 @@ +import { type MintActionParams } from '@rabbitholegg/questdk' +import { + createTestCase, + type TestParams, +} from '@rabbitholegg/questdk-plugin-utils' + +// values are placeholders, replace with actual values from your test transaction +export const MINT_TEST: TestParams = { + transaction: { + chainId: 1, + from: '0x0', + hash: '0x0', + input: '0x0', + to: '0x0', + value: '0', + }, + params: { + chainId: 0, + contractAddress: '0x0', + }, +} + +export const passingTestCases = [ + createTestCase(MINT_TEST, 'this is a demo test'), +] + +export const failingTestCases = [ + createTestCase(MINT_TEST, 'when chainId is not correct', { chainId: 99 }), +] diff --git a/packages/firsttest/tsconfig.build.json b/packages/firsttest/tsconfig.build.json new file mode 100644 index 000000000..9f29e5ec9 --- /dev/null +++ b/packages/firsttest/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "tsconfig/base.json", + "include": ["src"], + "exclude": [ + "src/**/*.test.ts", + "src/**/*.test-d.ts", + "src/**/*.bench.ts", + "src/_test", + "scripts/**/*" + ], + "compilerOptions": { + "declaration": true, + "declarationDir": "./dist/types", + "resolveJsonModule": true, + "sourceMap": true, + "rootDir": "./src" + } +} diff --git a/packages/firsttest/tsconfig.json b/packages/firsttest/tsconfig.json new file mode 100644 index 000000000..c76405177 --- /dev/null +++ b/packages/firsttest/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "tsconfig/base.json", + "include": ["src/**/*", "src/chain-data.ts"], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/noconfig/README.md b/packages/noconfig/README.md new file mode 100644 index 000000000..36767867f --- /dev/null +++ b/packages/noconfig/README.md @@ -0,0 +1,15 @@ +## Noconfig Plugin for Boost + +### New Plugin TODO list + +1. Find the ABI of the function your transaction is calling, and add export it as a const in the abi.ts file + 1. this can be multiple ABIs, if the transaction is calling multiple functions +2. in Noconfig.ts, fill out each Action function by mapping the ActionParams to the ABI of the function + + + +### Actions and Descriptions + + + +### Example Transactions \ No newline at end of file diff --git a/packages/noconfig/package.json b/packages/noconfig/package.json new file mode 100644 index 000000000..49109293f --- /dev/null +++ b/packages/noconfig/package.json @@ -0,0 +1,42 @@ +{ + "name": "@rabbitholegg/questdk-plugin-noconfig", + "private": true, + "version": "1.0.0-alpha.0", + "type": "module", + "exports": { + "require": "./dist/cjs/index.js", + "import": "./dist/esm/index.js", + "types": "./dist/types/index.d.ts" + }, + "main": "./dist/cjs/index.js", + "module": "./dist/esm/index.js", + "description": "Plugin for noconfig", + "scripts": { + "bench": "vitest bench", + "bench:ci": "CI=true vitest bench", + "build": "pnpm run clean && pnpm run build:cjs && pnpm run build:esm && pnpm run build:types", + "build:cjs": "tsc --project tsconfig.build.json --module commonjs --outDir ./dist/cjs --removeComments --verbatimModuleSyntax false && echo > ./dist/cjs/package.json '{\"type\":\"commonjs\"}'", + "build:esm": "tsc --project tsconfig.build.json --module es2015 --outDir ./dist/esm && echo > ./dist/esm/package.json '{\"type\":\"module\",\"sideEffects\":false}'", + "build:types": "tsc --project tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", + "clean": "rimraf dist", + "format": "rome format . --write", + "lint": "rome check .", + "lint:fix": "pnpm lint --apply", + "test": "vitest dev", + "test:cov": "vitest dev --coverage", + "test:ci": "CI=true vitest --coverage", + "test:ui": "vitest dev --ui" + }, + "keywords": [], + "author": "", + "license": "ISC", + "types": "./dist/types/index.d.ts", + "typings": "./dist/types/index.d.ts", + "devDependencies": { + "tsconfig": "workspace:*" + }, + "dependencies": { + "@rabbitholegg/questdk-plugin-utils": "workspace:*", + "@rabbitholegg/questdk": "workspace:*" + } +} diff --git a/packages/noconfig/plugin-details.yml b/packages/noconfig/plugin-details.yml new file mode 100644 index 000000000..c5a78c8f0 --- /dev/null +++ b/packages/noconfig/plugin-details.yml @@ -0,0 +1,12 @@ +project: + name: testtwo + iconOption: https://assets.coingecko.com/coins/images/11939/standard/shiba.png?1696511800 + appLink: https://testtwo.org + +task: + # action specific name, this is what will show as the default title for your boosts + name: Mint on testtwo + # action specific link to your project. (ie: myapp.com/mint) + link: https://testtwo.org/mint + iconOption: https://assets.coingecko.com/coins/images/11939/standard/shiba.png?1696511800 + actionPluginId: mint diff --git a/packages/noconfig/src/Noconfig.test.ts b/packages/noconfig/src/Noconfig.test.ts new file mode 100644 index 000000000..fc4449e31 --- /dev/null +++ b/packages/noconfig/src/Noconfig.test.ts @@ -0,0 +1,30 @@ +import { apply } from '@rabbitholegg/questdk/filter' +import { describe, expect, test } from 'vitest' +import { passingTestCases, failingTestCases } from './test-transactions' +import { bridge } from './Noconfig' + +describe('Given the noconfig plugin', () => { + describe('When handling the bridge action', () => { + + + describe('should pass filter with valid transactions', () => { + passingTestCases.forEach((testCase) => { + const { transaction, description, params } = testCase + test(description, async () => { + const filter = await bridge(params) + expect(apply(transaction, filter)).to.be.false + }) + }) + }) + + describe('should not pass filter with invalid transactions', () => { + failingTestCases.forEach((testCase) => { + const { transaction, description, params } = testCase + test(description, async () => { + const filter = await bridge(params) + expect(apply(transaction, filter)).to.be.false + }) + }) + }) + }) +}) diff --git a/packages/noconfig/src/Noconfig.ts b/packages/noconfig/src/Noconfig.ts new file mode 100644 index 000000000..8f7ad8676 --- /dev/null +++ b/packages/noconfig/src/Noconfig.ts @@ -0,0 +1,47 @@ +import { + type TransactionFilter, + type BridgeActionParams, + compressJson, +} from '@rabbitholegg/questdk' +import { type Address } from 'viem' +import { Chains } from '@rabbitholegg/questdk-plugin-utils' + +/* + * Function templates for handling various blockchain action types. + * It's adaptable for actions defined in ActionParams: Bridge, Swap, Stake, Mint, Delegate, Quest, Etc. + * Duplicate and customize for each specific action type. + * If you wish to use a different action other than swap, import one of the ActionParams types + * from @rabbitholegg/questdk (ie: SwapActionParams) and change the function below to use + * the action params you wish to use. + */ + + export const bridge = async(_params: BridgeActionParams): Promise => { + + // the ActionParams for this function are populated in the Boost Manager when the actual Boost is launched. + + // In this function you should load the ABI, and translate any ActionParams into the input object defined below + // which should match the parameter names in the transaction + + // You can also use the boostdk filter system to support operators on parameters, for example, greater than + + + // We always want to return a compressed JSON object which we'll transform into a TransactionFilter + return compressJson({ + chainId: '0x0', + to: '0x0', // The to field is the address of the contract we're interacting with + input: {}, // The input object is where we'll put the ABI and the parameters + }) + + } + +export const getSupportedTokenAddresses = async ( + _chainId: number, +): Promise => { + // Given a specific chain we would expect this function to return a list of supported token addresses + return [] +} + +export const getSupportedChainIds = async (): Promise => { + // This should return all of the ChainIds that are supported by the Project we're integrating + return [Chains.ETHEREUM, ] +} diff --git a/packages/noconfig/src/index.ts b/packages/noconfig/src/index.ts new file mode 100644 index 000000000..3b77310cf --- /dev/null +++ b/packages/noconfig/src/index.ts @@ -0,0 +1,16 @@ +import { + type IActionPlugin, +} from '@rabbitholegg/questdk' + +import { + bridge, + getSupportedChainIds, + getSupportedTokenAddresses, +} from './Noconfig.js' + +export const Noconfig: IActionPlugin = { + pluginId: "noconfig", + getSupportedTokenAddresses, + getSupportedChainIds, + bridge, +} diff --git a/packages/noconfig/src/test-transactions.ts b/packages/noconfig/src/test-transactions.ts new file mode 100644 index 000000000..28a1fdac3 --- /dev/null +++ b/packages/noconfig/src/test-transactions.ts @@ -0,0 +1,29 @@ +import { type BridgeActionParams } from '@rabbitholegg/questdk' +import { + createTestCase, + type TestParams, +} from '@rabbitholegg/questdk-plugin-utils' + +// values are placeholders, replace with actual values from your test transaction +export const BRIDGE_TEST: TestParams = { + transaction: { + chainId: 1, + from: '0x0', + hash: '0x0', + input: '0x0', + to: '0x0', + value: '0', + }, + params: { + sourceChainId: 0, + destinationChainId: 0, + }, +} + +export const passingTestCases = [ + createTestCase(BRIDGE_TEST, 'this is a demo test'), +] + +export const failingTestCases = [ + createTestCase(BRIDGE_TEST, 'when sourceChainId is not correct', { sourceChainId: 99 }), +] diff --git a/packages/noconfig/tsconfig.build.json b/packages/noconfig/tsconfig.build.json new file mode 100644 index 000000000..9f29e5ec9 --- /dev/null +++ b/packages/noconfig/tsconfig.build.json @@ -0,0 +1,18 @@ +{ + "extends": "tsconfig/base.json", + "include": ["src"], + "exclude": [ + "src/**/*.test.ts", + "src/**/*.test-d.ts", + "src/**/*.bench.ts", + "src/_test", + "scripts/**/*" + ], + "compilerOptions": { + "declaration": true, + "declarationDir": "./dist/types", + "resolveJsonModule": true, + "sourceMap": true, + "rootDir": "./src" + } +} diff --git a/packages/noconfig/tsconfig.json b/packages/noconfig/tsconfig.json new file mode 100644 index 000000000..c76405177 --- /dev/null +++ b/packages/noconfig/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "tsconfig/base.json", + "include": ["src/**/*", "src/chain-data.ts"], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/registry/package.json b/packages/registry/package.json index e78759a42..03073f222 100644 --- a/packages/registry/package.json +++ b/packages/registry/package.json @@ -68,6 +68,8 @@ "@rabbitholegg/questdk-plugin-llama": "workspace:*", "@rabbitholegg/questdk-plugin-boost": "workspace:*", "@rabbitholegg/questdk-plugin-kote": "workspace:*", - "@rabbitholegg/questdk-plugin-jojo": "workspace:*" + "@rabbitholegg/questdk-plugin-jojo": "workspace:*", + "@rabbitholegg/questdk-plugin-firsttest": "workspace:*", + "@rabbitholegg/questdk-plugin-noconfig": "workspace:*" } } diff --git a/packages/registry/src/index.ts b/packages/registry/src/index.ts index 38eaee780..55935885c 100644 --- a/packages/registry/src/index.ts +++ b/packages/registry/src/index.ts @@ -31,6 +31,8 @@ import { Vela } from '@rabbitholegg/questdk-plugin-vela' import { WooFi } from '@rabbitholegg/questdk-plugin-woofi' import { Zora } from '@rabbitholegg/questdk-plugin-zora' import { JOJO } from '@rabbitholegg/questdk-plugin-jojo' +import { FirstTest } from '@rabbitholegg/questdk-plugin-firsttest' +import { Noconfig } from '@rabbitholegg/questdk-plugin-noconfig' import { ENTRYPOINT } from './contract-addresses' import { type IntentParams, @@ -85,6 +87,8 @@ export const plugins: Record = { [Llama.pluginId]: Llama, [Kote.pluginId]: Kote, [JOJO.pluginId]: JOJO, + [FirstTest.pluginId]: FirstTest, + [Noconfig.pluginId]: Noconfig, } export const getPlugin = (pluginId: string) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5313bad21..021131231 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -219,6 +219,19 @@ importers: specifier: workspace:* version: link:../tsconfig + packages/firsttest: + dependencies: + '@rabbitholegg/questdk': + specifier: workspace:* + version: link:../../apps/questdk + '@rabbitholegg/questdk-plugin-utils': + specifier: workspace:* + version: link:../utils + devDependencies: + tsconfig: + specifier: workspace:* + version: link:../tsconfig + packages/gmx: dependencies: '@rabbitholegg/questdk': @@ -348,6 +361,19 @@ importers: specifier: workspace:* version: link:../tsconfig + packages/noconfig: + dependencies: + '@rabbitholegg/questdk': + specifier: workspace:* + version: link:../../apps/questdk + '@rabbitholegg/questdk-plugin-utils': + specifier: workspace:* + version: link:../utils + devDependencies: + tsconfig: + specifier: workspace:* + version: link:../tsconfig + packages/okutrade: dependencies: '@rabbitholegg/questdk': @@ -458,6 +484,9 @@ importers: '@rabbitholegg/questdk-plugin-connext': specifier: workspace:* version: link:../connext + '@rabbitholegg/questdk-plugin-firsttest': + specifier: workspace:* + version: link:../firsttest '@rabbitholegg/questdk-plugin-gmx': specifier: workspace:* version: link:../gmx @@ -485,6 +514,9 @@ importers: '@rabbitholegg/questdk-plugin-mux': specifier: workspace:* version: link:../mux + '@rabbitholegg/questdk-plugin-noconfig': + specifier: workspace:* + version: link:../noconfig '@rabbitholegg/questdk-plugin-okutrade': specifier: workspace:* version: link:../okutrade