diff --git a/.changeset/giant-lions-search.md b/.changeset/giant-lions-search.md new file mode 100644 index 000000000..358ffc9fb --- /dev/null +++ b/.changeset/giant-lions-search.md @@ -0,0 +1,6 @@ +--- +"@rabbitholegg/questdk-plugin-registry": minor +"@rabbitholegg/questdk-plugin-titles": minor +--- + +add titles create plugin to questdk diff --git a/packages/registry/package.json b/packages/registry/package.json index 7f01c5c32..8728bae2b 100644 --- a/packages/registry/package.json +++ b/packages/registry/package.json @@ -81,6 +81,7 @@ "@rabbitholegg/questdk-plugin-thruster": "workspace:*", "@rabbitholegg/questdk-plugin-orbit": "workspace:*", "@rabbitholegg/questdk-plugin-superbridge": "workspace:*", - "@rabbitholegg/questdk-plugin-neynar": "workspace:*" + "@rabbitholegg/questdk-plugin-neynar": "workspace:*", + "@rabbitholegg/questdk-plugin-titles": "workspace:*" } } diff --git a/packages/registry/src/index.ts b/packages/registry/src/index.ts index a824a16b3..0cbd29bc4 100644 --- a/packages/registry/src/index.ts +++ b/packages/registry/src/index.ts @@ -43,6 +43,7 @@ import { Base } from '@rabbitholegg/questdk-plugin-base' import { Orbit } from '@rabbitholegg/questdk-plugin-orbit' import { Superbridge } from '@rabbitholegg/questdk-plugin-superbridge' import { Neynar } from '@rabbitholegg/questdk-plugin-neynar' +import { Titles } from '@rabbitholegg/questdk-plugin-titles' // ^^^ New Imports Go Here ^^^ import { type IntentParams, @@ -111,6 +112,7 @@ export const plugins: Record = { [Orbit.pluginId]: Orbit, [Superbridge.pluginId]: Superbridge, [Neynar.pluginId]: Neynar, + [Titles.pluginId]: Titles, } export const getPlugin = (pluginId: string) => { diff --git a/packages/titles/README.md b/packages/titles/README.md new file mode 100644 index 000000000..841335092 --- /dev/null +++ b/packages/titles/README.md @@ -0,0 +1,18 @@ +# TITLES Plugin + +### About The Project + +TITLES builds creative tools powered by artist-trained AI models. Titles helps artists and IP holders maintain ownership of their AI likeness by making it easy to train, distribute, and monetize their own custom models. + +For the first time, artists can now take ownership and monetize their likeness through AI on TITLES. + +- Documentation: https://titlesxyz.notion.site/TITLES-341ca0582707492ea4b048291edd4698 + +### Implementation Details + +This is a simple `create` plugin which filters transaction based on the chain, contract and matching ABI function. The specific function we are watching for is `publishEdition` on the `TitlesPublisherV1` contract. + +- [**TitlesPublisherV1**](https://basescan.org/address/0x04e4d53374a5e6259ce06cfc6850a839bd960d01#code) + +#### Sample Transactions +- [**publishEdition**](https://basescan.org/tx/0x585edfb9b1899f37926c51274b8fbe0d62e1a00402a3cd084ae6a7af41226b9c) \ No newline at end of file diff --git a/packages/titles/babel.config.cjs b/packages/titles/babel.config.cjs new file mode 100644 index 000000000..7bf7a4762 --- /dev/null +++ b/packages/titles/babel.config.cjs @@ -0,0 +1,35 @@ +const sharedPresets = ['@babel/typescript'] +const shared = { + ignore: ['src/**/*.spec.ts'], + presets: sharedPresets, +} + +module.exports = { + env: { + esmUnbundled: shared, + esmBundled: { + ...shared, + presets: [ + [ + '@babel/env', + { + targets: '> 0.25%, not dead', + }, + ], + ...sharedPresets, + ], + }, + cjs: { + ...shared, + presets: [ + [ + '@babel/env', + { + modules: 'commonjs', + }, + ], + ...sharedPresets, + ], + }, + }, +} diff --git a/packages/titles/package.json b/packages/titles/package.json new file mode 100644 index 000000000..4b5857b6a --- /dev/null +++ b/packages/titles/package.json @@ -0,0 +1,43 @@ +{ + "name": "@rabbitholegg/questdk-plugin-titles", + "private": false, + "version": "1.0.0-alpha.0", + "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 Titles", + "scripts": { + "bench": "vitest bench", + "bench:ci": "CI=true vitest bench", + "build": "pnpm build:types && pnpm build:esm && pnpm postbuild:esm && pnpm build:cjs && pnpm postbuild:cjs && BABEL_ENV=esmBundled pnpm rollup -c", + "build:esm": "BABEL_ENV=esmUnbundled babel src --extensions '.ts' --out-dir 'dist/esm' --source-maps", + "build:cjs": "BABEL_ENV=cjs babel src --extensions '.ts' --out-dir 'dist/cjs' --source-maps", + "build:types": "tsc --project tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", + "clean": "rimraf dist", + "postbuild:cjs": "echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json", + "postbuild:esm": "echo '{\"type\":\"module\"}' > dist/esm/package.json", + "format": "rome format . --write", + "lint": "eslint .", + "lint:fix": "eslint . --fix", + "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/titles/rollup.config.mjs b/packages/titles/rollup.config.mjs new file mode 100644 index 000000000..cf5001391 --- /dev/null +++ b/packages/titles/rollup.config.mjs @@ -0,0 +1,52 @@ +import babel from '@rollup/plugin-babel' +import commonjs from '@rollup/plugin-commonjs' +import json from '@rollup/plugin-json' +import resolve from '@rollup/plugin-node-resolve' +import terser from '@rollup/plugin-terser' + +const extensions = ['.js', '.ts'] + +export default { + input: 'src/index.ts', + output: [ + { + inlineDynamicImports: true, + file: 'dist/bundles/bundle.esm.js', + format: 'esm', + sourcemap: true, + }, + { + inlineDynamicImports: true, + file: 'dist/bundles/bundle.esm.min.js', + format: 'esm', + plugins: [terser()], + sourcemap: true, + }, + { + inlineDynamicImports: true, + file: 'dist/bundles/bundle.umd.js', + format: 'umd', + name: 'myLibrary', + sourcemap: true, + }, + { + inlineDynamicImports: true, + file: 'dist/bundles/bundle.umd.min.js', + format: 'umd', + name: 'myLibrary', + plugins: [terser()], + sourcemap: true, + }, + ], + plugins: [ + json(), + resolve({ extensions, preferBuiltins: true }), + commonjs(), + babel({ + babelHelpers: 'bundled', + include: ['src/**/*.ts'], + extensions, + exclude: './node_modules/**', + }), + ], +} diff --git a/packages/titles/src/Titles.test.ts b/packages/titles/src/Titles.test.ts new file mode 100644 index 000000000..97b568253 --- /dev/null +++ b/packages/titles/src/Titles.test.ts @@ -0,0 +1,53 @@ +import { create } from './Titles' +import { failingTestCases, passingTestCases } from './test-transactions' +import { apply } from '@rabbitholegg/questdk' +import { describe, expect, test } from 'vitest' + +describe('Given the titles plugin', () => { + describe('When handling the create action', () => { + describe('should return a valid action filter', () => { + test('when making a valid create action', async () => { + const filter = await create({ + chainId: 8453, + }) + expect(filter).toBeTypeOf('object') + expect(Number(filter.chainId)).toBe(8453) + if (typeof filter.to === 'string') { + expect(filter.to).toMatch(/^0x[a-fA-F0-9]{40}$/) + } else { + // if to is an object, it should have a logical operator as the only key + expect(filter.to).toBeTypeOf('object') + expect(Object.keys(filter.to)).toHaveLength(1) + expect( + ['$or', '$and'].some((prop) => + Object.hasOwnProperty.call(filter.to, prop), + ), + ).to.be.true + expect(Object.values(filter.to)[0]).to.satisfy((arr: string[]) => + arr.every((val) => val.match(/^0x[a-fA-F0-9]{40}$/)), + ) + } + }) + }) + + describe('should pass filter with valid transactions', () => { + passingTestCases.forEach((testCase) => { + const { transaction, description, params } = testCase + test(description, async () => { + const filter = await create(params) + expect(apply(transaction, filter)).to.be.true + }) + }) + }) + + describe('should not pass filter with invalid transactions', () => { + failingTestCases.forEach((testCase) => { + const { transaction, description, params } = testCase + test(description, async () => { + const filter = await create(params) + expect(apply(transaction, filter)).to.be.false + }) + }) + }) + }) +}) diff --git a/packages/titles/src/Titles.ts b/packages/titles/src/Titles.ts new file mode 100644 index 000000000..0a6e9260f --- /dev/null +++ b/packages/titles/src/Titles.ts @@ -0,0 +1,32 @@ +import { TITLES_ABI_V1, TITLES_PUBLISHER_V1 } from './constants' +import { + type CreateActionParams, + type TransactionFilter, + compressJson, +} from '@rabbitholegg/questdk' +import { Chains } from '@rabbitholegg/questdk-plugin-utils' +import { type Address } from 'viem' + +export const create = async ( + create: CreateActionParams, +): Promise => { + const { chainId, contractAddress } = create + return compressJson({ + chainId, + to: contractAddress ?? TITLES_PUBLISHER_V1, + input: { + $abi: TITLES_ABI_V1, + }, + }) +} + +export const getSupportedTokenAddresses = async ( + _chainId: number, +): Promise => { + // Not used for create action + return [] +} + +export const getSupportedChainIds = async (): Promise => { + return [Chains.BASE] +} diff --git a/packages/titles/src/constants.ts b/packages/titles/src/constants.ts new file mode 100644 index 000000000..c4b33079d --- /dev/null +++ b/packages/titles/src/constants.ts @@ -0,0 +1,40 @@ +export const TITLES_PUBLISHER_V1 = '0x04e4d53374a5e6259ce06cfc6850a839bd960d01' + +export const TITLES_ABI_V1 = [ + { + inputs: [ + { internalType: 'address', name: '_creator', type: 'address' }, + { internalType: 'string', name: '_name', type: 'string' }, + { internalType: 'string', name: '_symbol', type: 'string' }, + { internalType: 'string', name: '_uri', type: 'string' }, + { + internalType: 'address[]', + name: 'creatorProceedAccounts', + type: 'address[]', + }, + { + internalType: 'uint32[]', + name: 'creatorProceedAllocations', + type: 'uint32[]', + }, + { + internalType: 'address[]', + name: 'derivativeFeeAccounts', + type: 'address[]', + }, + { + internalType: 'uint32[]', + name: 'derivativeFeeAllocations', + type: 'uint32[]', + }, + { internalType: 'uint256', name: '_price', type: 'uint256' }, + { internalType: 'uint256', name: '_maxSupply', type: 'uint256' }, + { internalType: 'uint256', name: '_mintLimitPerWallet', type: 'uint256' }, + { internalType: 'uint256', name: '_saleEndTime', type: 'uint256' }, + ], + name: 'publishEdition', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] diff --git a/packages/titles/src/index.ts b/packages/titles/src/index.ts new file mode 100644 index 000000000..3b4e1dccf --- /dev/null +++ b/packages/titles/src/index.ts @@ -0,0 +1,14 @@ +import { type IActionPlugin } from '@rabbitholegg/questdk' + +import { + create, + getSupportedChainIds, + getSupportedTokenAddresses, +} from './Titles' + +export const Titles: IActionPlugin = { + pluginId: 'titles', + getSupportedTokenAddresses, + getSupportedChainIds, + create, +} diff --git a/packages/titles/src/test-transactions.ts b/packages/titles/src/test-transactions.ts new file mode 100644 index 000000000..f74fc8c90 --- /dev/null +++ b/packages/titles/src/test-transactions.ts @@ -0,0 +1,34 @@ +import { type CreateActionParams } from '@rabbitholegg/questdk' +import { + Chains, + type TestParams, + createTestCase, +} from '@rabbitholegg/questdk-plugin-utils' + +export const PUBLISH_EDITION: TestParams = { + transaction: { + chainId: 8453, + from: '0x624310638b9901933fab5f5b0deb2b33e13c87cc', + hash: '0x585edfb9b1899f37926c51274b8fbe0d62e1a00402a3cd084ae6a7af41226b9c', + input: + '0x9cc866bd000000000000000000000000624310638b9901933fab5f5b0deb2b33e13c87cc000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000002c00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000038000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000666ba076000000000000000000000000000000000000000000000000000000000000000c67616c207665727365205345000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065449544c455300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035697066733a2f2f516d5848673953395862314e33466552356e394a587078657a32526d744d4248484e354b7744443676777667415100000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000624310638b9901933fab5f5b0deb2b33e13c87cc0000000000000000000000007f8b18bbbf77fafdee934eaa359da3193eada207000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000aae6000000000000000000000000000000000000000000000000000000000000493e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000005a6fd0c361a2ed387fe6c8346b1ced733ebd2c4e0000000000000000000000007f8b18bbbf77fafdee934eaa359da3193eada207000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000927c00000000000000000000000000000000000000000000000000000000000061a80', + to: '0x04e4d53374a5e6259ce06cfc6850a839bd960d01', + value: '0', + }, + params: { + chainId: Chains.BASE, + }, +} + +export const passingTestCases = [ + createTestCase(PUBLISH_EDITION, 'when creating a new collection'), +] + +export const failingTestCases = [ + createTestCase(PUBLISH_EDITION, 'when chainId is not correct', { + chainId: 1, + }), + createTestCase(PUBLISH_EDITION, 'when contractAddress is not correct', { + contractAddress: '0x777777C338d93e2C7adf08D102d45CA7CC4Ed021', + }), +] diff --git a/packages/titles/tsconfig.build.json b/packages/titles/tsconfig.build.json new file mode 100644 index 000000000..0f1d5bc4d --- /dev/null +++ b/packages/titles/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "extends": "tsconfig/base.json", + "include": ["src"], + "exclude": [ + "src/**/*.test.ts", + "src/**/*.test-d.ts", + "src/**/*.bench.ts", + "src/_test", + "scripts/**/*" + ], + "compilerOptions": { + "sourceMap": true, + "rootDir": "./src", + + } +} diff --git a/packages/titles/tsconfig.json b/packages/titles/tsconfig.json new file mode 100644 index 000000000..c76405177 --- /dev/null +++ b/packages/titles/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "tsconfig/base.json", + "include": ["src/**/*", "src/chain-data.ts"], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8733073d1..8ab15bcfd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -759,6 +759,9 @@ importers: '@rabbitholegg/questdk-plugin-thruster': specifier: workspace:* version: link:../thruster + '@rabbitholegg/questdk-plugin-titles': + specifier: workspace:* + version: link:../titles '@rabbitholegg/questdk-plugin-traderjoe': specifier: workspace:* version: link:../traderjoe @@ -892,6 +895,19 @@ importers: specifier: workspace:* version: link:../tsconfig + packages/titles: + 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/traderjoe: dependencies: '@rabbitholegg/questdk': @@ -3969,7 +3985,7 @@ packages: '@ledgerhq/hw-transport-node-hid-noevents': 5.51.1 '@ledgerhq/logs': 5.50.0 lodash: 4.17.21 - node-hid: 2.1.1 + node-hid: 1.3.0 usb: 1.9.2 dev: false optional: true @@ -11210,6 +11226,12 @@ packages: thenify-all: 1.6.0 dev: false + /nan@2.19.0: + resolution: {integrity: sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==} + requiresBuild: true + dev: false + optional: true + /nano-json-stream-parser@0.1.2: resolution: {integrity: sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew==} dev: false @@ -11342,6 +11364,19 @@ packages: hasBin: true dev: false + /node-hid@1.3.0: + resolution: {integrity: sha512-BA6G4V84kiNd1uAChub/Z/5s/xS3EHBCxotQ0nyYrUG65mXewUDHE1tWOSqA2dp3N+mV0Ffq9wo2AW9t4p/G7g==} + engines: {node: '>=6.0.0'} + hasBin: true + requiresBuild: true + dependencies: + bindings: 1.5.0 + nan: 2.19.0 + node-abi: 2.30.1 + prebuild-install: 5.3.6 + dev: false + optional: true + /node-hid@2.1.1: resolution: {integrity: sha512-Skzhqow7hyLZU93eIPthM9yjot9lszg9xrKxESleEs05V2NcbUptZc5HFqzjOkSmL0sFlZFr3kmvaYebx06wrw==} engines: {node: '>=10'} @@ -11375,6 +11410,12 @@ packages: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true + /noop-logger@0.1.1: + resolution: {integrity: sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==} + requiresBuild: true + dev: false + optional: true + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -11969,6 +12010,30 @@ packages: picocolors: 1.0.0 source-map-js: 1.2.0 + /prebuild-install@5.3.6: + resolution: {integrity: sha512-s8Aai8++QQGi4sSbs/M1Qku62PFK49Jm1CbgXklGz4nmHveDq0wzJkg7Na5QbnO1uNH8K7iqx2EQ/mV0MZEmOg==} + engines: {node: '>=6'} + hasBin: true + requiresBuild: true + dependencies: + detect-libc: 1.0.3 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 2.30.1 + noop-logger: 0.1.1 + npmlog: 4.1.2 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 3.1.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + which-pm-runs: 1.1.0 + dev: false + optional: true + /prebuild-install@6.1.4: resolution: {integrity: sha512-Z4vpywnK1lBg+zdPCVCsKq0xO66eEV9rWo2zrROGGiRS4JtueBOdlB1FnY8lcy7JsUud/Q3ijUxyWN26Ika0vQ==} engines: {node: '>=6'} @@ -15154,6 +15219,13 @@ packages: /which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + /which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + requiresBuild: true + dev: false + optional: true + /which-pm@2.0.0: resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} engines: {node: '>=8.15'}