diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..059019c4 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +PERMISSION=active +NETWORK_NAME=mainnet +ACTOR=eosaccountname +PRIVATE_KEY=private_key_here +PUBLIC_KEY=public_key_here diff --git a/.gitignore b/.gitignore index f6e9bfd2..f31a036f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,10 @@ lerna-debug.log* .env.development.local .env.test.local .env.production.local +.env.testnet +.env.mainnet .envrc # ignore lock files other then bun. package-lock.json -yarn.lock \ No newline at end of file +yarn.lock diff --git a/README.md b/README.md index c193b397..09e8f8f5 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,16 @@ If you want to add contributions to this repository, please follow the instructi ## 🏠 Local Development Follow the docs to Set Up Your Local Development Environment to contribute to the framework and documentation. +### Testing +#### `.env` + +To run the tests locally, you will need to provide an EOS account that can be used to make transactions on your behalf. +Copy the `.env.example` file to `.env.testnet` and fill in the required parameters. + +#### PowerUp + +You might need to power up your account, either by adding some Native Token to your account or EFX tokens. +You can powerup your account at the following link: https://monitor4.jungletestnet.io/ diff --git a/bun.lockb b/bun.lockb index dc964540..8eb3799c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docs/pages/docs/compatibility.mdx b/docs/pages/docs/compatibility.mdx index 2f3dc4f9..941d98c5 100644 --- a/docs/pages/docs/compatibility.mdx +++ b/docs/pages/docs/compatibility.mdx @@ -1,3 +1,5 @@ # Compatibility -The Effect AI SDK is compatible with all Node.js environments, including browsers and React Native. \ No newline at end of file +The Effect AI SDK is compatible with all Node.js environments, including browsers and React Native. +It might be necessary to use a particular `fetch` polyfill in some environments. +You can read more about it [here](./glossary/client_options.mdx) diff --git a/docs/pages/docs/getting-started.mdx b/docs/pages/docs/getting-started.mdx deleted file mode 100644 index 9a109e60..00000000 --- a/docs/pages/docs/getting-started.mdx +++ /dev/null @@ -1,14 +0,0 @@ -# Getting Started [Get started with the SDK in just a few lines of code.] - -## Overview - -## Installation - -## Quick Start - -### 1. - -## Live example - -TODO:: insert example - diff --git a/docs/pages/docs/gettingStarted.mdx b/docs/pages/docs/gettingStarted.mdx new file mode 100644 index 00000000..6cc33084 --- /dev/null +++ b/docs/pages/docs/gettingStarted.mdx @@ -0,0 +1,157 @@ +# Getting Started [Get started with the SDK in just a few lines of code.] + +## Overview + +## Installation + +Use your favorite package manager to install the SDK. + + :::code-group + + ```bash [npm] + + npm i @effectai/effect-js + ``` + + ```bash [bun] + bun i @effectai/effect-js + ``` + + ```bash [pnpm] + pnpm i @effectai/effect-js + ``` + ::: +## Quick Start + +### 1. Import the SDK + +```ts +import { createClient } from '@effectai/effect-js' +``` + +### 2. Instantiate the EffectAI Client + +Here the handy dandy `createClient` function is used to create a client instance. +It takes two optional arguments, the first one is the network configuration, the second one is the options object. +You can read more about the options object in the [ClientOptions](#client-options) section. + +```ts +// This would be ideal, the big question is if using mainnet should be explicitly passed or not. +const client = createClient() + +// But currently it is like this: +import { jungle4 } from '@effectai/effect-js' +// Atleast the opts ({}) object is now optional +const client = createClient(jungle4) +``` + +### 3. Interact with the Client + +The sdk is built using a stateless model, meaning that all the functions are pure and do not mutate the client object. +This means, that you need to creat one client, and you can pass that client as an argument to functions to make transactions. + +Here is an example of how to get the balance of an account, assuming that the client is already set up as described above. + +```ts +import { getVAccounts, getAccountById } from "@effectai/effect-js"; +import { Name } from "@wharfkit/antelope"; + +const actor = Name.from("forcedev1234"); +// Notice we are passing the clinet as an argument to the function. +const [vacc] = await getVAccounts({ client, actor }); + +console.log(vacc) +/** +{ + id: 24, + nonce: 0, + address: [ "name", "forcedev1234" ], + balance: { + quantity: "0.0000 EFX", + contract: "efxtoken1112", + }, +} +*/ +``` + +### 4. Set up wallet and session + +If you want to make transactions, such as creating a new account, creating a new tasks, or transfering tokens, you need to set up a wallet and a session. +The wallet plguin is an interface that allows you to sign transactions and interact with the blockchain. +The SDK comes with a few wallet plugins out of the box, but you can also create your own. + +In this example we will use the `WalletPluginPrivateKey` plugin, which requres a private key to be passed in. + +Afterwards we can set up the Session object, which allows us to specify other parameters like the actor, permission, and the blockchain netork. + +Last but not least we connect the session to the client, and the client will be ready to use. + +```ts +import { createBatch, createVAccount } from "@effectai/effect-js"; +import { Session } from "@wharfkit/session"; +import { WalletPluginPrivateKey } from "@wharfkit/wallet-plugin-privatekey" + +const walletPlugin = new WalletPluginPrivateKey("your_private_key_here") + +const session = new Session({ + actor: "forcedev1234", + permission: "active", + walletPlugin, + chain: { + id: jungle4.eosChainId, + url: jungle4.eosRpcUrl, + }, +}) + +// Connect the session to the client. +await session.setSession(session) + +// Now you can use the client to make authenticated transactions. +const account = Name.from("efxforce1112"); +await createVAccount({ client, account }); +``` + +## Full example + +```ts +import { Session, Name } from "@wharfkit/session"; +import { WalletPluginPrivateKey } from "@wharfkit/wallet-plugin-privatekey"; +import { + createClient, + jungle4, + eos, // Use `eos` to use mainnet + getVAccounts, + createVAccount, +} from "@effectai/effect-js"; + +const client = createClient({ network: jungle4 }); + +// Make unauthenticated requests +const actor = Name.from("forcedev1234"); +const [vacc] = await getVAccounts({ client, actor }); + +// Set up wallet with privatekey +const walletPlugin = new WalletPluginPrivateKey("your_private_key_here"); + +// Set up session with wallet and chain +const session = new Session({ + actor: "forcedev1234", + permission: "active", + walletPlugin, + chain: { + id: jungle4.eosChainId, + url: jungle4.eosRpcUrl, + }, +}); + +// Connect session to client +await client.setSession(session); + +// Now you can use the client to make authenticated transactions. +const account = Name.from("efxforce1112"); +await createVAccount({ client, account }); +``` + +Now that we have a basic understaning of how to set up the client, we can move on to more advanced topics. +Read more on the following pages. + diff --git a/docs/pages/docs/glossary/clientOptions.mdx b/docs/pages/docs/glossary/clientOptions.mdx new file mode 100644 index 00000000..d42ce0e8 --- /dev/null +++ b/docs/pages/docs/glossary/clientOptions.mdx @@ -0,0 +1,50 @@ +# Client Options + +The ClientOpts interface is used to define the options that can be passed to the EffectAI Client constructor. + +```typescript +interface ClientOpts { + ipfsCacheDurationInMs?: number | null; + fetchProvider?: FetchProviderOptions; + cacheImplementation?: Cache; +} +``` + +As we can see, the ClientOpts interface has three optional properties: + +## `ipfsCacheDurationIMs` + +This property is used to set the cache duration for the IPFS data. + The default value is 600_000 milliseconds; 10 minutes. + +## `fetchProfider` + +This property is used to set the fetch provider. +This is needed because of the different runtimes availalbe to Java Script. +For example, in older versions of Node.js, the fetch API is not available. +For older versions of Node.js, you can use the [`node-fetch` ](https://github.com/node-fetch/node-fetch) package. + +Since Node.js v18.0.0, the fetch API is available by default. + +In the browser fetch is generally available, and is available on the `window.fetch` object. +You can read more about it here: [MDN Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) + +Other serverside Java Script runtimes such as [Bun](https://bun.sh/) and (Deno)[https://deno.com/] already have fetch available on the global `fetch` object. + +## `cacheImplementation` + +This property is used to set the cache implementation. +There are three different cache mechanigms abailable in the EffectAI SDK. +First of all, "IDBCache", "LocalStorageCache", "MemoryCache". +Any of these can be passed to the `cacheImplementation` property while instanlizing the EffectAI Client. + +```typescript +import { createClient, IDBCache, LocalStorageCache, MemoryCache } from '@effectai/sdk' +const client = createClient({ + cacheImplementation: new IDBCache() // or new LocalStorageCache() or new MemoryCache() +}) +``` + +# TODO: This needs to be changed when the `dev` branch is merged into main. +[See Type](https://github.com/effectai/effect-js/blob/main/src/client.ts) + diff --git a/docs/pages/docs/guides/create-a-campaign.mdx b/docs/pages/docs/guides/create-a-campaign.mdx deleted file mode 100644 index 8e1bb31f..00000000 --- a/docs/pages/docs/guides/create-a-campaign.mdx +++ /dev/null @@ -1,3 +0,0 @@ -## Creating Your First Campaign - -To create a campaign, you need to have a few things in place: diff --git a/docs/pages/docs/guides/createACampaign.mdx b/docs/pages/docs/guides/createACampaign.mdx new file mode 100644 index 00000000..e27149ea --- /dev/null +++ b/docs/pages/docs/guides/createACampaign.mdx @@ -0,0 +1,90 @@ +## Creating Your First Campaign + +To create a campaign, you need to have a few things in place: + + +# Create and upload campaign + +## Usage + +```ts [example.ts] +import { createClient, createCampaign } from '@effectai/effect-js' + +const campaign: Mkcampaign = { + owner: ["name", "efxefxefxefx"], + content: { + field_0: 0, + field_1: "QmVKwq3bYM6cPW6kstpiq4WYckWRtdfJnzAmms2iMyGqQg", + }, + max_task_time: 100, + reward: { quantity: "42.1234, EFX", contract: "efxtoken1112" }, + qualis: [], + payer: "efxefxefxefx", +}; + +const data = { + version: 1.1, + title: "Labelstudio OCR (LAION)", + description: + "You are contributing to a dataset for conversational style chatbots.", + instructions: "Some instructions here", + template: "

Template here

", + input_schema: null, + output_schema: null, + image: "", + category: "", + example_task: "", + estimated_time: 10, +}; + +const client = await testClientSession({ testEnvNetwork: jungle4 }); +const result = await createCampaign({ client, campaign, data }); +``` + +## Returns + +Result is a transaction response object. + +```json +response: { + transaction_id: "9d321af28b7354c5cbee6ee956ea3e6590228b48539a9f0cafc6a8ca5ffe0ca2", + processed: { + id: "9d321af28b7354c5cbee6ee956ea3e6590228b48539a9f0cafc6a8ca5ffe0ca2", + block_num: 137520447, + block_time: "2024-05-01T03:55:31.500", + producer_block_id: null, + receipt: [Object ...], + elapsed: 4854, + net_usage: 176, + scheduled: false, + action_traces: [ + [Object ...] + ], + account_ram_delta: null, + except: null, + error_code: null, + }, +} +``` + + +- **Type:** [`Promise>`](/docs/glossary/types#campaign) +- **Description:** A list of campaigns. +- **Properties:** + - **rows:** An array of campaigns. + - **more:** A boolean indicating if there are more campaigns to fetch. + - **next_key:** A string that can be used to fetch the next page of campaigns. + +## Parameters + +### client +- **Type:** `Client` + +### limit (optional) +- **Type:** `number` +- **Default:** `10` + +### page (optional) +- **Type:** `number` +- **Default:** `1` + diff --git a/docs/pages/docs/introduction.mdx b/docs/pages/docs/introduction.mdx index 410c3c0a..6728b05b 100644 --- a/docs/pages/docs/introduction.mdx +++ b/docs/pages/docs/introduction.mdx @@ -1,6 +1,57 @@ -# Why Effect AI [A brief reasoning on why we built the Effect AI SDK] +# Why Effect AI + +## Introduction + +Effect AI was built to solve the problem of AI development. +It's a platform that connects AI developers with AI trainers to create data to train AI models. ## Problems -TODO::write problem statement +The problem that Effect AI is trying to solve is the lack of training data for AI models. +AI models are only as good as the data they are trained on. +The more data you have, the better the model will be. + +There is an issue though that now we are facing, AI models are being trained with data that ws created by other AI models. +This is a problem because the data is not always accurate, the model will learn and exacerbate mistakes from the training data. +Leading to unreliable AI models. + +## Solution + +The soluiton is to create high quality training data. +This is where Effect AI comes in. + +Effect AI is a platform that connects AI developers with AI trainers. + +AI developers can post tasks that they need training data for. +Ai trainers can then complete these tasks and get paid for their work. + +--- + + +In the rapidly evolving landscape of artificial intelligence (AI), the quest for optimal development and performance remains paramount. +Effect AI emerges as a beacon in this realm, offering a solution to a pressing dilemma: the scarcity of high-quality training data for AI models. + +At its core, Effect AI addresses the fundamental challenge of AI development—the insufficiency of robust datasets. +As the adage goes, "garbage in, garbage out"; AI models are only as proficient as the data they are trained upon. +Consequently, the quest for superior data becomes imperative for fostering the accuracy and reliability of AI technologies. + +However, a disconcerting trend has emerged in the AI domain—models being trained with data generated by other AI models. +This recursive process, while ostensibly efficient, precipitates a cascade of issues. +Foremost among these concerns is the potential for error amplification: inaccuracies within the training data can be perpetuated and magnified within the AI model, resulting in flawed outcomes and unreliable performance. + +Enter Effect AI, a platform meticulously crafted to address these challenges. +Serving as a nexus between AI developers and AI trainers, Effect AI facilitates the creation of high-quality training data, thereby fortifying the foundations of AI development. + +The modus operandi of Effect AI is elegantly simple yet profoundly impactful. +AI developers leverage the platform to articulate their data requirements through the formulation of tasks. +These tasks, ranging from image classification to natural language processing, encapsulate the diverse array of challenges encountered in AI development. + +In response, AI trainers—individuals proficient in data annotation and curation—step forward to fulfill these tasks. +Through their collective efforts, they meticulously craft datasets that adhere to the highest standards of quality and accuracy. +In doing so, they contribute not merely to the advancement of AI technologies but to the cultivation of a virtuous cycle wherein superior data begets superior models. + +Crucially, Effect AI's platform incentivizes participation and excellence. +AI trainers are duly compensated for their contributions, thereby fostering a symbiotic relationship wherein both developers and trainers are mutually benefited. +In conclusion, Effect AI stands as a testament to the power of innovation in addressing complex challenges. +By championing the creation of high-quality training data, it not only mitigates the pitfalls of current AI development practices but also charts a course towards a future where AI technologies are synonymous with reliability, accuracy, and societal benefit. diff --git a/docs/pages/docs/local-development.mdx b/docs/pages/docs/localDevelopment.mdx similarity index 100% rename from docs/pages/docs/local-development.mdx rename to docs/pages/docs/localDevelopment.mdx diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx index 4638f512..27cc5b06 100644 --- a/docs/pages/index.mdx +++ b/docs/pages/index.mdx @@ -19,7 +19,7 @@ import { HomePage } from 'vocs/components' Effect logo
Effect AI is the data network for training next-gen transparent AI models. Integrate the SDK into your app and tap into a global, decentralized workforce.
- Get started + Get started GitHub
diff --git a/docs/sidebar.ts b/docs/sidebar.ts index 639f9b98..29bfeadf 100644 --- a/docs/sidebar.ts +++ b/docs/sidebar.ts @@ -1,31 +1,48 @@ import type { Sidebar } from "vocs"; export const sidebar = { - "/docs/": [ - { - text: "Introduction", - items: [ - { text: "Why Effect AI", link: "/docs/introduction" }, - { text: "Getting Started", link: "/docs/getting-started" }, - { text: "Local Development", link: "/docs/local-development" }, - ], - }, - { - text: "Guides", - items: [ - { - text: "Create Your First Campaign", - link: "/docs/guides/create-a-campaign", - }, - ], - }, - { - text: "Token", - items: [{ text: "getPrice", link: "/docs/token/getPrice" }], - }, - { - text: "Tasks", - items: [{ text: "getCampaigns", link: "/docs/tasks/getCampaigns" }], - }, - ], + "/docs/": [ + { + text: "Introduction", + items: [ + { text: "Why Effect AI", link: "/docs/introduction" }, + { text: "Getting Started", link: "/docs/gettingStarted" }, + { text: "Local Development", link: "/docs/localDevelopment" }, + ], + }, + { + text: "Guides", + items: [ + { + text: "Create Your First Campaign", + link: "/docs/guides/createACampaign", + }, + ], + }, + { + text: "Token", + items: [{ text: "getPrice", link: "/docs/token/getPrice" }], + }, + { + text: "Tasks", + items: [{ text: "getCampaigns", link: "/docs/tasks/campaigns/getCampaigns" }], + }, + { + text: "Local Development", + items: [{ text: "localDevelopment", link: "/docs/localDevelopment" }], + }, + { + text: "Glossary", + items: [ + { text: "Client Options", link: "/docs/glossary/clientOptions" } + ] + }, + { + text: "FAQ", + items: [ + { text: "FAQ", link: "/docs/faq" } + ] + } + + ], } as const satisfies Sidebar; diff --git a/package.json b/package.json index 97c8eee5..aa337f10 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,10 @@ "types": "./dist/exports/index.d.ts", "files": ["dist"], "scripts": { - "test": "vitest run", + "test": "bun --env-file=.env test", + "test:watch": "bun --env-file=.env test --watch", + "test:coverage": "bun --env-file=.env test --coverage", + "test:mainnet": "bun --env-file=.env.mainnet test", "dev": "tsc -w", "build": "tsc --module es2020", "lint": "bun biome lint --apply .", @@ -43,12 +46,9 @@ "@biomejs/biome": "1.7.0", "@changesets/changelog-github": "^0.4.5", "@changesets/cli": "^2.23.2", - "@greymass/abi2core": "^2.0.1", "@size-limit/preset-big-lib": "^11.1.2", - "@types/dotenv": "^8.2.0", - "dotenv": "^16.3.1", - "typescript": "^5.4.3", - "vitest": "^1.4.0" + "@greymass/abi2core": "^2.0.1", + "typescript": "^5.4.3" }, "dependencies": { "@wharfkit/antelope": "^1.0.7", diff --git a/src/actions/atomic/getAccountAssets.test.ts b/src/actions/atomic/getAccountAssets.test.ts new file mode 100644 index 00000000..5de25b0e --- /dev/null +++ b/src/actions/atomic/getAccountAssets.test.ts @@ -0,0 +1,48 @@ +import { test, expect, describe } from "bun:test"; +import { getAccountAssets, type IAssetRow } from "./getAccountAssets"; +import { destructureEnv, testClientSession } from "../../testHelper"; +import { Name } from "@wharfkit/antelope"; +import { createClient, eos, jungle4 } from "../../exports"; + +describe("getAccountAssets", async () => { + const accountAssetExample: IAssetRow = { + asset_id: "2199024546793", + collection_name: "pomelo", + schema_name: "astronauts", + template_id: 7022, + ram_payer: "pomelo", + backed_tokens: [], + immutable_serialized_data: [ + 5, 46, 81, 109, 90, 57, 102, 105, 106, 75, 54, 100, 109, 113, 66, 121, 66, + 81, 117, 105, 57, 66, 71, 74, 111, 68, 77, 83, 122, 84, 76, 90, 104, 70, + 55, 69, 70, 53, 90, 88, 78, 112, 57, 67, 111, 78, 102, 71, 9, 6, 67, 121, + 98, 111, 114, 103, 10, 8, 65, 113, 117, 97, 114, 105, 117, 109, 11, 5, 67, + 108, 111, 119, 110, 12, 6, 77, 111, 100, 101, 114, 110, 13, 4, 78, 111, + 110, 101, 14, 4, 66, 97, 108, 100, 15, 4, 78, 111, 110, 101, 16, 16, 68, + 97, 114, 107, 32, 66, 114, 97, 115, 115, 32, 83, 116, 117, 100, 115, 17, + 11, 70, 108, 111, 119, 101, 114, 32, 69, 121, 101, 115, 18, 6, 78, 111, + 114, 109, 97, 108, 19, 4, 78, 111, 110, 101, 20, 4, 78, 111, 110, 101, + ], + mutable_serialized_data: [], + }; + + test("getAccountAssets() returns IAssetRow", async () => { + const account = Name.from("cryptonode42"); + const client = await createClient({ network: eos }); + const assets = await getAccountAssets({ client, account }); + + expect(assets).toBeDefined(); + expect(assets).toBeArray(); + expect(assets[0]).toMatchObject(accountAssetExample); + }); + + test("getAccountAssets() should throw return empty array when no assets are found", async () => { + const account = Name.from("cryptonode99"); + const client = await createClient({ network: eos }); + const assets = await getAccountAssets({ client, account }); + + expect(assets).toBeDefined(); + expect(assets).toBeArray(); + expect(assets).toBeEmpty(); + }); +}); diff --git a/src/actions/atomic/getAccountAssets.ts b/src/actions/atomic/getAccountAssets.ts index 5e2c9e4b..6f0fab18 100644 --- a/src/actions/atomic/getAccountAssets.ts +++ b/src/actions/atomic/getAccountAssets.ts @@ -7,7 +7,8 @@ export type getAccountAssetsArgs = { account: Name; }; -//Override this interface, atomicassets exports a broken type. +// TODO: What does this comment mean? +// Override this interface, atomicassets exports a broken type. export interface IAssetRow { asset_id: string; collection_name: string; @@ -15,11 +16,11 @@ export interface IAssetRow { template_id: number; ram_payer: string; backed_tokens: string[]; - immutable_serialized_data: Uint8Array; - mutable_serialized_data: Uint8Array; + immutable_serialized_data: number[]; + mutable_serialized_data: number[]; } -//TODO:: Implement Pagination +// TODO:: Implement Pagination export const getAccountAssets = async ({ client, account, diff --git a/src/actions/session/createSession.ts b/src/actions/session/createSession.ts index 167a3aa8..aa3cc88c 100644 --- a/src/actions/session/createSession.ts +++ b/src/actions/session/createSession.ts @@ -17,17 +17,12 @@ export const createSession = async ({ privateKey, }: CreateSessionArgs) => { const walletPlugin = new WalletPluginPrivateKey(privateKey); - - const { eosRpcUrl, eosChainId } = client.network; - + const { eosRpcUrl: url, eosChainId: id } = client.network; const session = new Session({ actor, permission, - chain: { - id: eosChainId, - url: eosRpcUrl, - }, walletPlugin, + chain: { id, url }, }); await setSession({ client, session }); diff --git a/src/actions/tasks/campaigns/campaign.test.ts b/src/actions/tasks/campaigns/campaign.test.ts new file mode 100644 index 00000000..f42256b5 --- /dev/null +++ b/src/actions/tasks/campaigns/campaign.test.ts @@ -0,0 +1,146 @@ +import { describe, test, expect } from "bun:test"; +import { getCampaignById } from "./getCampaignById"; +import { getAllCampaigns } from "./getAllCampaigns"; +import { createCampaign } from "./createCampaign"; +import { getCampaigns, type CampaignWithInfo } from "./getCampaigns"; +import { createClient } from "../../../client"; +import { testClientSession } from "../../../testHelper"; +import { jungle4 } from "../../../exports"; +import type { Mkcampaign } from "../../../@generated/types/effecttasks2"; + +describe("getCamapignById", async () => { + test("getCampaignById() should throw an error when accessing unretrievable id", async () => { + const client = createClient({ network: jungle4 }); + const id = 11111; + expect(async () => { + await getCampaignById({ client, id }); + }).toThrowError(); + }); + + test("getCampaignById() should retrieve campaign on testnet", async () => { + const client = createClient({ network: jungle4 }); + const id = 1; + const campaign = await getCampaignById({ client, id }); + expect(campaign).toBeDefined(); + expect(campaign).toBeObject(); + expect(campaign).toContainKeys(Object.keys(campaignExample)); + }); +}); + +describe("getAllCampaigns", async () => { + test("getAllCampaigns() should retrieve all campaign", async () => { + const client = createClient({ network: jungle4 }); + const campaigns = await getAllCampaigns({ client }); + expect(campaigns).toBeDefined(); + expect(campaigns).toBeArray(); + expect(campaigns[0]).toContainKeys(Object.keys(campaignExample)); + }); +}); + +describe("getCampaigns", async () => { + test("getCampaigns() should return 3 campaigns", async () => { + const client = await testClientSession({ testEnvNetwork: jungle4 }); + const campaigns = await getCampaigns({ client, limit: 3 }); + expect(campaigns).toBeDefined(); + expect(campaigns.rows).toBeArray(); + expect(campaigns.rows.length).toBe(3); + }); +}); + +describe("createCampaign", async () => { + const campaign: Mkcampaign = { + owner: ["name", "efxefxefxefx"], + content: { + field_0: 0, + field_1: "QmVKwq3bYM6cPW6kstpiq4WYckWRtdfJnzAmms2iMyGqQg", + }, + max_task_time: 100, + reward: { quantity: "42.1234, EFX", contract: "efxtoken1112" }, + qualis: [], + payer: "efxefxefxefx", + }; + + const data = { + version: 1.1, + title: "Labelstudio OCR (LAION)", + description: + "You are contributing to a dataset for conversational style chatbots.", + instructions: "Some instructions here", + template: "

Template here

", + input_schema: null, + output_schema: null, + image: "", + category: "", + example_task: "", + estimated_time: 10, + }; + + test("createCampaign() should throw an error", async () => { + const client = createClient({ network: jungle4 }); + expect(async () => { + await createCampaign({ client, campaign, data }); + }).toThrowError(); + }); + + test.skip("createCampaign() should create a new campaign", async () => { + const client = await testClientSession({ testEnvNetwork: jungle4 }); + const result = await createCampaign({ client, campaign, data }); + expect(result).toBeDefined(); + /** + * response: { + * transaction_id: "9d321af28b7354c5cbee6ee956ea3e6590228b48539a9f0cafc6a8ca5ffe0ca2", + * processed: { + * id: "9d321af28b7354c5cbee6ee956ea3e6590228b48539a9f0cafc6a8ca5ffe0ca2", + * block_num: 137520447, + * block_time: "2024-05-01T03:55:31.500", + * producer_block_id: null, + * receipt: [Object ...], + * elapsed: 4854, + * net_usage: 176, + * scheduled: false, + * action_traces: [ + * [Object ...] + * ], + * account_ram_delta: null, + * except: null, + * error_code: null, + * }, + * } + */ + }); +}); + +const campaignExample: CampaignWithInfo = { + id: 1, + reservations_done: 1, + total_submissions: 2, + total_tasks: 1, + active_batch: 1, + num_batches: 1, + owner: ["name", "efxefxefxefx"], + paused: false, + content: { + field_0: 0, + field_1: "QmVKwq3bYM6cPW6kstpiq4WYckWRtdfJnzAmms2iMyGqQg", + }, + max_task_time: 3600, + reward: { + quantity: "0.0100 EFX", + contract: "efxtoken1112", + }, + qualis: [], + info: { + version: 1.1, + title: "Labelstudio OCR (LAION)", + description: + "You are contributing to a dataset for conversational style chatbots.", + instructions: "Some instructions here", + template: "Template here", + input_schema: null, + output_schema: null, + image: "", + category: "", + example_task: "", + estimated_time: 10, + }, +}; diff --git a/src/actions/tasks/campaigns/getCampaignById.ts b/src/actions/tasks/campaigns/getCampaignById.ts index 173249b7..3c384709 100644 --- a/src/actions/tasks/campaigns/getCampaignById.ts +++ b/src/actions/tasks/campaigns/getCampaignById.ts @@ -1,4 +1,3 @@ -import type { Campaign } from "../../../@generated/types/effecttasks2"; import { type CampaignWithInfo, getIpfsResource, @@ -27,11 +26,8 @@ export const getCampaignById = async ({ }); const [campaign] = response.rows; - - campaign.info = await getIpfsResource({ - client, - hash: campaign.content.field_1, - }); + const { field_1: hash } = campaign.content; + campaign.info = await getIpfsResource({ client, hash }); return campaign; }; diff --git a/src/actions/token/getBalance.ts b/src/actions/token/getBalance.ts index 17766213..38dafafe 100644 --- a/src/actions/token/getBalance.ts +++ b/src/actions/token/getBalance.ts @@ -1,4 +1,4 @@ -import type { Name } from "@wharfkit/session"; +import type { Asset, Name } from "@wharfkit/session"; import type { Client } from "../../exports"; export type GetBalanceArgs = { @@ -6,7 +6,23 @@ export type GetBalanceArgs = { actor: Name; }; -export const getBalance = async ({ client, actor }: GetBalanceArgs) => { +/** + * Get the balance of a user + * @param {{ client: Client, actor: Name}} Effect SDK client and the actor to get the balance for. + * @returns {Promise} The balance of the user + * @throws {Error} if no balance is found + * + * @example + * const client = createClient({ network: eos }); + * const actor = Name.from("cryptonode42"); + * const balance = await getBalance({ client, actor }); + * console.log(balance.toString()); + * // => "100.0000 EFX" + */ +export const getBalance = async ({ + client, + actor, +}: GetBalanceArgs): Promise => { const { network, provider } = client; const { contracts } = network.config.efx; diff --git a/src/actions/token/getDefiBoxPair.ts b/src/actions/token/getDefiBoxPair.ts index af0b5c8d..55a1b0c6 100644 --- a/src/actions/token/getDefiBoxPair.ts +++ b/src/actions/token/getDefiBoxPair.ts @@ -5,7 +5,9 @@ export enum DefiBoxPairEnum { export const getDefiBoxPair = async (pairEnum: DefiBoxPairEnum) => { try { - const result = await window.fetch( + // TODO: Check how resilient this is, otherwise figure out how to use FetchProvider from the SDK Client. + const useFetch = fetch ?? window.fetch; + const result = await useFetch( "https://eos.greymass.com/v1/chain/get_table_rows", { method: "POST", diff --git a/src/actions/token/getPrice.ts b/src/actions/token/getPrice.ts index e990aa78..634189f2 100644 --- a/src/actions/token/getPrice.ts +++ b/src/actions/token/getPrice.ts @@ -1,5 +1,9 @@ import { DefiBoxPairEnum, getDefiBoxPair } from "./getDefiBoxPair"; +/** + * Get the current price of EFX in USDT + * @returns {Promis} The current price of EFX in USDT + */ export const getPrice = async (): Promise => { try { const eosEfxPair = await getDefiBoxPair(DefiBoxPairEnum.EosEfx); @@ -8,7 +12,6 @@ export const getPrice = async (): Promise => { Number(eosEfxPair.price1_last) * Number(eosUsdtPair.price0_last); return efxUsdt; } catch (error) { - console.error(error); throw new Error("Error retrieving EFX Ticker Price from DefiBox"); } }; diff --git a/src/actions/token/swap.ts b/src/actions/token/swap.ts index a83e4398..c45ecc53 100644 --- a/src/actions/token/swap.ts +++ b/src/actions/token/swap.ts @@ -1,4 +1,9 @@ -import { type AnyAction, Asset, type Name } from "@wharfkit/antelope"; +import { + type AnyAction, + Asset, + type Name, + type PermissionLevelType, +} from "@wharfkit/antelope"; import type { Client } from "../../client"; import { DefiBoxPairEnum } from "./getDefiBoxPair"; import { getPrice } from "./getPrice"; @@ -12,7 +17,7 @@ export const swapDirection = { export const buildSwapAction = ( direction: string, actor: Name, - authorization: { permission: Name; actor: Name }[], + authorization: PermissionLevelType[], amount: number, tokenContract: string, efxPrice: number, @@ -57,18 +62,20 @@ export const buildSwapAction = ( return swapAction[direction]; }; -export const swap = async ( - client: Client, - amount: number, - direction: string, -) => { +export type SwapArgs = { + client: Client; + amount: number; + direction: "EfxToUsdt" | "UsdtToEfx"; +}; + +export const swap = async ({ client, amount, direction }: SwapArgs) => { try { if (!client.session) { throw new Error("Session is required for this method."); } const { transact, actor, authorization } = client.session; - const { token } = useEFXContracts(client); + const { token: tokenContract } = useEFXContracts(client); const efxPrice = await getPrice(); const action = buildSwapAction( @@ -76,7 +83,7 @@ export const swap = async ( actor, authorization, amount, - token, + tokenContract, efxPrice, ); @@ -86,7 +93,6 @@ export const swap = async ( return await transact({ action }); } catch (e) { - console.error(e); - throw new Error("Error swapping tokens"); + throw new Error(`Error swapping: ${e}`); } }; diff --git a/src/actions/token/token.test.ts b/src/actions/token/token.test.ts new file mode 100644 index 00000000..c700d104 --- /dev/null +++ b/src/actions/token/token.test.ts @@ -0,0 +1,64 @@ +import { describe, test, expect } from "bun:test"; +import { getPrice } from "./getPrice"; +import { getBalance } from "./getBalance"; +import { swap, type SwapArgs } from "./swap"; +import { createClient } from "../../client"; +import { testClientSession } from "../../testHelper"; +import { eos, jungle4 } from "../../exports"; +import { Name } from "@wharfkit/antelope"; + +describe("getPrice", async () => { + test("getPrice() should retrieve price on mainnet", async () => { + const client = createClient({ network: eos }); + const price = await getPrice(); + expect(price).toBeDefined(); + expect(price).toBeNumber(); + }); +}); + +describe("getBalance", async () => { + test("getBalance() should retrieve balance from user on mainnet", async () => { + const client = createClient({ network: jungle4 }); + const actor = Name.from("forcedev1234"); + const balance = await getBalance({ client, actor }); + expect(balance).toBeDefined(); + expect(balance.toString()).toBeDefined(); + expect(balance.toString()).toContain("EFX"); + }); + + test("getBalance() should throw Error retrieving balance from unknown user.", async () => { + const client = createClient({ network: eos }); + const actor = Name.from("cryptonode99"); + expect(async () => await getBalance({ client, actor })).toThrowError(); + }); +}); + +describe("buildSwapAction", async () => { + test.todo("buildSwapAction() should return a swap action object."); +}); + +describe("Swap", async () => { + // Use Mainnet + + test("swap() should throw an error when Session is not set on Client.", async () => { + const swapArgs: SwapArgs = { + client: createClient({ network: jungle4 }), + amount: 1, + direction: "UsdtToEfx", + }; + + expect(async () => await swap(swapArgs)).toThrow( + new Error("Error swapping: Error: Session is required for this method."), + ); + }); + + test("swap() should fail when amount is 0", async () => { + const swapArgs: SwapArgs = { + client: await testClientSession({ testEnvNetwork: jungle4 }), + amount: 0, + direction: "UsdtToEfx", + }; + + expect(async () => await swap(swapArgs)).toThrow(); + }); +}); diff --git a/src/actions/vaccount/createAccount.test.ts b/src/actions/vaccount/createAccount.test.ts new file mode 100644 index 00000000..ab956b37 --- /dev/null +++ b/src/actions/vaccount/createAccount.test.ts @@ -0,0 +1,25 @@ +import { expect, test, describe, mock } from "bun:test"; +import { destructureEnv, testClientSession } from "../../testHelper"; +import { createClient } from "../../client"; +import { createVAccount } from "./createAccount"; +import { Name } from "@wharfkit/antelope"; +import { jungle4 } from "../../exports"; + +describe("Create Virtual account", () => { + const testEnvNetwork = jungle4; + + test.skip("createVAccount() should return a TransactResult", async () => { + const client = await testClientSession({ testEnvNetwork }); + const account = Name.from("efxforce1112"); + const result = await createVAccount({ client, account }); + expect(result).toBeDefined(); + }); + + test("createVAccount() should throw Error when no Session is found", async () => { + expect(async () => { + const client = createClient({ network: testEnvNetwork }); + const account = Name.from("efxforce1112"); + await createVAccount({ client, account }); + }).toThrowError(); + }); +}); diff --git a/src/actions/vaccount/createAccount.ts b/src/actions/vaccount/createAccount.ts index a53eff86..d9a172d2 100644 --- a/src/actions/vaccount/createAccount.ts +++ b/src/actions/vaccount/createAccount.ts @@ -1,4 +1,4 @@ -import { Name, type Session } from "@wharfkit/session"; +import type { Name, TransactResult, Session } from "@wharfkit/session"; import type { Client } from "../../client"; import { ExtendedSymbol } from "../../utils/structs"; import { VAddress } from "../../utils/variants"; @@ -9,11 +9,35 @@ export type CreateVAccountArgs = { account?: Name; }; +/** + * Creates a virtual Effect account + * Client must be initialized + * Session is optional if the client has a session already + * Account is optional, can be initialized with: + * + * ```ts + * import { Name } from "@wharfkit/session"; + * const account: Name = Name.from("accountname"); + * ``` + * The account name must be a valid EOS account name. + * If no account is provided, the current session actor will be used. + * + * @param {CreateVAccountArgs} { client, session, account } - Provide the client, session, and account name. + * @returns {TransactResult = The result of the the transaction.} + * + * @example + * ```ts + * import { createVAccount } from "@effectai/effect-js"; + * import { Name } from "@wharfkit/session"; + * const account: Name = Name.from("accountname"); + * const result = await createVAccount({ client, account }); + * ``` + */ export const createVAccount = async ({ client, session, account, -}: CreateVAccountArgs) => { +}: CreateVAccountArgs): Promise => { const sessionToUse = session ?? client.session; if (!sessionToUse) { @@ -21,10 +45,10 @@ export const createVAccount = async ({ } // If no account is provided, use the current session actor - const acc = account ?? sessionToUse.actor; + const acc: Name = account ?? sessionToUse.actor; const { actor } = sessionToUse; - const { contracts } = client.network.config.efx; + const { contracts, token } = client.network.config.efx; const authorization = [ { @@ -38,8 +62,11 @@ export const createVAccount = async ({ name: "open", authorization, data: { - acc: VAddress.from(Name.from(acc.toString())), - symbol: new ExtendedSymbol("4,EFX", contracts.token), + acc: VAddress.from(acc), + symbol: new ExtendedSymbol( + `${token.precision},${token.symbol}`, + contracts.token, + ), payer: actor, }, }; diff --git a/src/actions/vaccount/deposit.test.ts b/src/actions/vaccount/deposit.test.ts new file mode 100644 index 00000000..42c380de --- /dev/null +++ b/src/actions/vaccount/deposit.test.ts @@ -0,0 +1,23 @@ +import { describe, test, expect } from "bun:test"; +import { deposit } from "./deposit"; +import { destructureEnv, testClientSession } from "../../testHelper"; +import { eos, jungle4 } from "../../exports"; +import { getOrCreateVAccount } from "./getOrCreate"; +import { Name } from "@wharfkit/antelope"; + +describe("deposit", async () => { + test.todo("Should throw an error when Session is not set on Client.", () => { + // TODO: implement test + }); + + test.skip("Check that deposit is functioning correctly", async () => { + const { network, actor } = destructureEnv(jungle4); + const client = await testClientSession({ testEnvNetwork: network }); + console.debug(client.network); + const acc = Name.from(actor); + const vAccount = await getOrCreateVAccount({ client, actor: acc }); + const vAccId = Number(vAccount.id); + const result = await deposit({ client, vAccountId: vAccId, amount: 0.1 }); + console.debug(result); + }); +}); diff --git a/src/actions/vaccount/getAccounts.test.ts b/src/actions/vaccount/getAccounts.test.ts new file mode 100644 index 00000000..7c3505ec --- /dev/null +++ b/src/actions/vaccount/getAccounts.test.ts @@ -0,0 +1,45 @@ +import { expect, test, describe, beforeAll } from "bun:test"; +import type { VAccount } from "../../types/user"; +import { testClientSession } from "../../testHelper"; +import type { Client } from "../../client"; +import { getVAccounts, getAccountById } from "./getAccounts"; +import { Name } from "@wharfkit/antelope"; +import { jungle4 } from "../../exports"; + +describe("Get Virtual Accounts", () => { + const vaccExample: VAccount = { + id: 0, + nonce: 53, + address: ["name", "efxforceacc1"], + balance: { + quantity: "3525.0000 EFX", + contract: "effecttokens", + }, + }; + + let client: Client; + beforeAll(async () => { + client = await testClientSession({ testEnvNetwork: jungle4 }); + }); + + test("getVAccounts() on testnet", async () => { + const actor = Name.from("efxforce1112"); + const vaccs = await getVAccounts({ client, actor }); + expect(vaccs).toBeDefined(); + expect(vaccs).toBeArray(); + expect(vaccs[0]).toContainKeys(Object.keys(vaccExample)); + }); + + test("getAccountById()", async () => { + const vacc = await getAccountById({ client, accountId: 0 }); + expect(vacc).toBeDefined(); + expect(vacc).toContainKeys(Object.keys(vaccExample)); + }); + + test("getAccountByID() should throw Error", async () => { + const accountId = 9999999; // Should be imposible to find + expect(async () => { + await getAccountById({ client, accountId }); + }).toThrowError(); + }); +}); diff --git a/src/actions/vaccount/getAccounts.ts b/src/actions/vaccount/getAccounts.ts index eb87ebea..381ed7c1 100644 --- a/src/actions/vaccount/getAccounts.ts +++ b/src/actions/vaccount/getAccounts.ts @@ -9,6 +9,11 @@ export type GetVAccountsArgs = { actor: Name; }; +/** + * Get all virtual accounts for a given account + * @param {GetVAccountsArgs} getVAccountargs - Object with sdk client and account name + * @returns {Promise} VAccount[] - Array of Effect Virtual Accounts + */ export const getVAccounts = async ({ client, actor, diff --git a/src/actions/vaccount/getAvatar.ts b/src/actions/vaccount/getAvatar.ts index b1e49ebf..c7cda088 100644 --- a/src/actions/vaccount/getAvatar.ts +++ b/src/actions/vaccount/getAvatar.ts @@ -11,7 +11,6 @@ export const getAvatar = async (client: Client, account: string) => { account, assetId: daoAvatar.asset_id, }); - console.debug(asset); return { ...asset, img: asset.immutable_deserialized_data?.img ?? defaultImg, diff --git a/src/client.test.ts b/src/client.test.ts new file mode 100644 index 00000000..5c6ff416 --- /dev/null +++ b/src/client.test.ts @@ -0,0 +1,111 @@ +import { expect, test, describe, beforeAll } from "bun:test"; +import { jungle4, eos } from "../src/exports"; +import { testClientSession, destructureEnv } from "./testHelper"; +import { createClient, Client as ClientConstructor } from "./client"; +import { Name } from "@wharfkit/antelope"; +import { WalletPluginPrivateKey } from "@wharfkit/wallet-plugin-privatekey"; +import { Session } from "@wharfkit/session"; +import { EffectSession } from "./session"; + +describe("Client", async () => { + test("createClient TestNet", () => { + const client = createClient({ network: jungle4 }); + expect(client).toBeDefined(); + expect(client).toBeInstanceOf(ClientConstructor); + }); + + test("createClient Mainnet", () => { + const client = createClient({ network: eos }); + expect(client).toBeDefined(); + expect(client).toBeInstanceOf(ClientConstructor); + }); + + test("Connect Client to Session Mainnet", async () => { + const { network, permission, actor, privateKey } = destructureEnv(eos); + const client = createClient({ network }); + + expect(client.session).toBeNull(); + + // Create wallet with privatekey + const walletPlugin = new WalletPluginPrivateKey(privateKey); + + // Set up session with wallet + const session = new Session({ + actor, + permission, + walletPlugin, + chain: { + id: network.eosChainId, + url: network.eosRpcUrl, + }, + }); + + // connect session to client + await client.setSession(session); + + expect(client.session).toBeDefined(); + expect(client.session).toBeInstanceOf(EffectSession); + expect(client.session?.vAccount?.address[1]).toEqual(actor); + }); + + test("Connect Client to Session Testnet", async () => { + const { network, permission, actor, privateKey } = destructureEnv(jungle4); + const client = createClient({ network }); + + expect(client.session).toBeNull(); + + // Create wallet with privatekey + const walletPlugin = new WalletPluginPrivateKey(privateKey); + + // Set up session with wallet + const session = new Session({ + actor, + permission, + walletPlugin, + chain: { + id: network.eosChainId, + url: network.eosRpcUrl, + }, + }); + + // connect session to client + await client.setSession(session); + + expect(client.session).toBeDefined(); + expect(client.session).toBeInstanceOf(EffectSession); + expect(client.session?.vAccount?.address[1]).toEqual(actor); + }); +}); + +describe("Client testHelper Mainnet", async () => { + const testEnvNetwork = eos; + + test("testClient defined Mainnet", async () => { + const client = await testClientSession({ testEnvNetwork }); + expect(client).toBeDefined(); + expect(client).toBeInstanceOf(ClientConstructor); + }); + + test("testClient session connected Mainnet", async () => { + const client = await testClientSession({ testEnvNetwork }); + expect(client.session).toBeDefined(); + expect(client.session?.vAccount).toBeDefined(); + expect(client.session?.actor).toBeInstanceOf(Name); + }); +}); + +describe("Client testHelper Testnet", async () => { + const testEnvNetwork = jungle4; + test("testClient defined testnet", async () => { + const client = await testClientSession({ testEnvNetwork }); + expect(client).toBeDefined(); + expect(client).toBeInstanceOf(ClientConstructor); + }); + + test("testClient session connected Testnet", async () => { + const client = await testClientSession({ testEnvNetwork }); + expect(client.session).toBeDefined(); + expect(client.session?.vAccount).toBeDefined(); + expect(client.session?.actor).toBeInstanceOf(Name); + }); +}); diff --git a/src/client.ts b/src/client.ts index 4635bf57..a66acbe2 100644 --- a/src/client.ts +++ b/src/client.ts @@ -4,14 +4,11 @@ import { type FetchProviderOptions, } from "@wharfkit/antelope"; import type { Session } from "@wharfkit/session"; - +import type { Network } from "./types/network"; import { type Cache, MemoryCache } from "./cache"; - +import { type CacheManager, IDBCache, createCacheManager } from "./cache"; import { getOrCreateVAccount } from "./actions/vaccount/getOrCreate"; import { EffectSession } from "./session"; -import type { Network } from "./types/network"; -import { type CacheManager, IDBCache, createCacheManager } from "./cache"; -import { chains } from "./constants/network"; export interface ClientOpts { ipfsCacheDurationInMs?: number | null; @@ -23,6 +20,14 @@ const defaultClientOpts: ClientOpts = { ipfsCacheDurationInMs: 600_000, // 10 minutes }; +/** + * Represents a client for the Effect SDK, used for interacting with the network. + * @class Client + * @property {FetchProvider} fetchProvider - the fetch provider for the client, allows for custtom fetch implementations. + * @property {Network} network - the network configuration for the client. + * @property {ClientOpts} options = additonal options for the client. + * @property {APIClient} provider - the API client for the client. + */ export class Client { public readonly fetchProvider: FetchProvider; public readonly network: Network; @@ -37,6 +42,11 @@ export class Client { return this._session; } + /** + * Constructs a new instance of the Client class. + * @param {Network} network The network configuration for the client. + * @param {ClientOpts} options Additional options for the client. + */ constructor(network: Network, options: ClientOpts) { this.options = { ...defaultClientOpts, ...options }; @@ -57,7 +67,42 @@ export class Client { } } - // Set the session for the client + /** + * Sets the session for the client, using user credentials. + * @param {Session|null} session The session to set for the client. + * @returns {Promise | null} The updated session for the client. + * @throws {Error} If failed to set session. + * + * ```typescript + * // Import the Effect Client, network configuration, and sdk function + * import { createClient, eos, createBatch } from "@effectai/effect-js" + * import { Session } from "@wharfkit/session"; + * import { WalletPluginPrivateKey } from "@wharfkit/wallet-plugin-privatekey"; + * + * // Create a new client + * const client = createClient({ network: eos }); + * + * // Set up wallet with privatekey + * const walletPlugin = new WalletPluginPrivateKey(privateKey); + * + * // Set up session with wallet and chain + * const session = new Session({ + * actor, + * permission, + * walletPlugin, + * chain: { + * id: network.eosChainId, + * url: network.eosRpcUrl, + * }, + * }); + * + * // Connect session to client + * await client.setSession(session); + * + * // Use the client to create a batch with the session + * await createBatch({ client, batch, data }) + * ``` + */ public setSession = async (session: Session | null) => { try { if (!session) { @@ -83,13 +128,35 @@ export class Client { }; } +/** + * Create a new client for the Effect SDK. + * Pass in the network configuration and any additonal options. + * Client must be used as an argument to SDK functions to interact with the network. + * + * @param {Object} createClientParam - The network and options for the Effect SDK client. + * @param {Network} createClientParam.network - The network configuration for the client. + * @param {ClientOpts} [createClientParam.options] - Additional options for the client. + * @returns {Client} A new instance of the Client class. + * + * ```typescript + * // Import the Effect Client, network configuration, and sdk function + * import { createClient, eos, getAccountById } from "@effectai/effect-js" + * + * // Create a new client + * const client = createClient({ network: eos }); + * + * // Use the client to get an account by id + * const accountId = 42; + * const vAccount = await getAccountById({ client, accountId }); + * ``` + */ export const createClient = ({ network, session, options = {}, }: { network: Network; - session: Session; + session?: Session; options?: ClientOpts; }) => { return new Client(network, options); diff --git a/src/constants/config.ts b/src/constants/config.ts index 885e9f52..e310ac90 100644 --- a/src/constants/config.ts +++ b/src/constants/config.ts @@ -5,7 +5,7 @@ import type { RelayerConfig, } from "../types/network"; -export const efxConfig: EfxConfig = { +export const efxTestnetConfig: EfxConfig = { token: { symbol: "EFX", precision: 4, @@ -21,6 +21,22 @@ export const efxConfig: EfxConfig = { }, }; +export const efxMainnetConfig: EfxConfig = { + token: { + symbol: "EFX", + precision: 4, + }, + contracts: { + tasks: "force.efx", + token: "effecttokens", + stake: "efxstakepool", + feepool: "feepool.efx", + proposals: "daoproposals", + vaccount: "vaccount.efx", + dao: "theeffectdao", + }, +}; + export const ipfsConfig: IpfsConfig = { ipfsEndpoint: "https://ipfs.effect.ai", }; @@ -38,9 +54,9 @@ export const relayerConfig: RelayerConfig = { "https://vaccount-relayer-service-jungle-96xyn.ondigitalocean.app", }; -export const defaultNetworkConfig = { - efx: efxConfig, +export const defaultNetworkConfig = (network: string) => ({ + efx: network === "eos" ? efxMainnetConfig : efxTestnetConfig, ipfs: ipfsConfig, atomic: atomicConfig, relayer: relayerConfig, -}; +}); diff --git a/src/constants/network.ts b/src/constants/network.ts index 30a27607..e4d4da30 100644 --- a/src/constants/network.ts +++ b/src/constants/network.ts @@ -8,7 +8,7 @@ export const jungle4: Network = { eosChainId: "73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d", - config: defaultNetworkConfig, + config: defaultNetworkConfig("jungle4"), }; export const eos: Network = { @@ -17,8 +17,7 @@ export const eos: Network = { eosRpcUrl: "https://eos.greymass.com/", eosChainId: "73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d", - - config: defaultNetworkConfig, + config: defaultNetworkConfig("eos"), }; export const chains = [eos, jungle4]; diff --git a/src/testHelper.ts b/src/testHelper.ts new file mode 100644 index 00000000..6ae93aff --- /dev/null +++ b/src/testHelper.ts @@ -0,0 +1,87 @@ +import { + PrivateKey, + type PrivateKeyType, + Session, + PermissionLevel, +} from "@wharfkit/session"; +import { WalletPluginPrivateKey } from "@wharfkit/wallet-plugin-privatekey"; +import { jungle4, eos } from "../src/exports"; +import { createClient } from "./../src/client"; +import type { Client } from "./../src/client"; +import type { Network } from "./types/network"; + +declare module "bun" { + interface Env { + TESTNET_PERMISSION: string; + TESTNET_NETWORK_NAME: string; + TESTNET_ACTOR: string; + TESTNET_PRIVATE_KEY: string; + MAINNET_PERMISSION: string; + MAINNET_NETWORK_NAME: string; + MAINNET_ACTOR: string; + MAINNET_PRIVATE_KEY: string; + } +} + +export interface testEnv { + network: Network; + networkName: string; + permission: string; + actor: string; + privateKey: PrivateKeyType; +} + +export const destructureEnv = (networkEnv: Network) => { + // Mainnet Config + if (networkEnv === eos) { + return { + network: eos, + networkName: process.env.MAINNET_NETWORK_NAME, + permission: process.env.MAINNET_PERMISSION, + actor: process.env.MAINNET_ACTOR, + privateKey: PrivateKey.from(process.env.MAINNET_PRIVATE_KEY), + }; + } + + // Testnet Config + return { + network: jungle4, + networkName: process.env.TESTNET_NETWORK_NAME, + permission: process.env.TESTNET_PERMISSION, + actor: process.env.TESTNET_ACTOR, + privateKey: PrivateKey.from(process.env.TESTNET_PRIVATE_KEY), + }; +}; + +export const testClientSession = async ({ + testEnvNetwork, +}: { testEnvNetwork: Network }): Promise => { + // Retrieve parameters for session. + const { network, permission, actor, privateKey } = + destructureEnv(testEnvNetwork); + const { eosRpcUrl: url, eosChainId: id } = network; + + // Create client + const client = createClient({ network }); + + // Set up wallet with privatekey + const pk: PrivateKeyType = PrivateKey.fromString( + privateKey.toString(), + false, + ); + const walletPlugin = new WalletPluginPrivateKey(pk); + + // Set up session with wallet and chain + const session = new Session({ + actor, + permission, + walletPlugin, + chain: { id, url }, + permissionLevel: PermissionLevel.from(`${actor}@${permission}`), + }); + + // Connect session to client + await client.setSession(session); + + return client; +}; diff --git a/test/helpers.ts b/test/helpers.ts deleted file mode 100644 index 9238fa2c..00000000 --- a/test/helpers.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Session } from "@wharfkit/session"; -import { WalletPluginPrivateKey } from "@wharfkit/wallet-plugin-privatekey"; -import { jungle4 } from "../src/constants/network"; -import { createClient } from "./../src/client"; - -const client = createClient(jungle4, {}); - -//jungle 4 test key -const walletPlugin = new WalletPluginPrivateKey( - "5KSG1pLHubiQ2JD4G4Pr32zxz7oQvpagBofYPrdS1FALKVjxdPM", -); - -//set a test session to jungle -client.state.setState({ - session: new Session({ - actor: "forcedev1234", - permission: "active", - chain: { - id: "73e4385a2708e6d7048834fbc1079f2fabb17b3c125b146af438971e90716c4d", - url: "https://jungle4.greymass.com", - }, - walletPlugin, - }), -}); - -export { client }; diff --git a/tsconfig.json b/tsconfig.json index 0f9c3aeb..250d1a38 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,6 @@ "outDir": "dist", "skipLibCheck": true }, - "exclude": ["node_modules/**/*"], + "exclude": ["node_modules/**/*", "**/*.test.ts"], "include": ["src/**/*", "abis/**/*"] }