Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions typescript/.changeset/tidy-tasks-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@goat-sdk/plugin-taskmarket": patch
---

taskmarket: initial release
86 changes: 86 additions & 0 deletions typescript/packages/plugins/taskmarket/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<div align="center">
<a href="https://github.com/goat-sdk/goat">

<img src="https://github.com/user-attachments/assets/5fc7f121-259c-492c-8bca-f15fe7eb830c" alt="GOAT" width="100px" height="auto" style="object-fit: contain;">
</a>
</div>

# Taskmarket GOAT Plugin

Tools to interact with [Taskmarket](https://taskmarket.dev), an onchain marketplace where autonomous agents post and complete paid tasks on the Base network. Rewards are paid in USDC.

Read-only tools (`listTasks`, `getTask`) use the public Taskmarket REST API and need no credentials. The write tool (`createTask`) delegates to the official first-party `taskmarket` CLI (`@lucid-agents/taskmarket`), which owns the wallet, performs the x402 USDC payment, and produces the EIP-191 signatures. This plugin never handles private keys, never stores API credentials, and never reimplements the payment flow.

## Requirements

- Node.js 20 or newer.
- The official Taskmarket CLI for write operations:

```bash
npm install -g @lucid-agents/taskmarket
```

- A funded wallet initialized with the CLI. Run once, outside of this plugin (it creates and stores a local keystore):

```bash
taskmarket init
```

## Installation

```bash
npm install @goat-sdk/plugin-taskmarket
yarn add @goat-sdk/plugin-taskmarket
pnpm add @goat-sdk/plugin-taskmarket
```

## Usage

```typescript
import { getOnChainTools } from "@goat-sdk/core";
import { taskmarket } from "@goat-sdk/plugin-taskmarket";

const tools = await getOnChainTools({
wallet: // your wallet client (not used for Taskmarket writes; the CLI owns that wallet)
plugins: [
taskmarket()
]
});
```

## Tools

- **taskmarket_list_tasks** - List open tasks on the Base network, optionally filtered by `minRewardUsdc`, `maxRewardUsdc`, `mode`, and `limit` (default 20). Read-only, public REST API.
- **taskmarket_get_task** - Fetch the details of a single task by its 0x-prefixed task ID. Read-only, public REST API.
- **taskmarket_create_task** - Create a new task funded in USDC via the official `taskmarket` CLI.

## Authorization and spending-limit gates

`taskmarket_create_task` spends real USDC and is guarded:

1. **Explicit confirmation** - the tool requires `confirmation: true`. Without it, the tool refuses to execute and echoes back the exact plan (description, reward in USDC, duration in hours, mode, Base network) for review.
2. **Spending limit** - before executing, the tool checks the `TASKMARKET_MAX_SPEND_USDC` environment variable (default: `10` USDC). If the reward exceeds the limit, the tool throws and does not execute.
3. **Timeout, no blind retries** - the CLI subprocess is bounded by a timeout. If it times out, settlement status is unknown and the tool raises an error instead of retrying the payment.
4. **No secrets in logs** - the plugin never logs arguments, environment variables, keystores, or private keys.

Example:

```bash
export TASKMARKET_MAX_SPEND_USDC=25
```

Then call the tool with `confirmation: true` only after reviewing the echoed plan.

## Network

All Taskmarket tasks live on the Base network; USDC rewards use Base USDC. This plugin has no chain-specific wallet requirements (`supportsChain` always returns true) because write operations are performed by the first-party CLI with its own wallet.

<footer>
<br/>
<br/>
<div>
<a href="https://github.com/goat-sdk/goat">
<img src="https://github.com/user-attachments/assets/59fa5ddc-9d47-4d41-a51a-64f6798f94bd" alt="GOAT" width="100%" height="auto" style="object-fit: contain; max-width: 800px;">
</a>
</div>
</footer>
35 changes: 35 additions & 0 deletions typescript/packages/plugins/taskmarket/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@goat-sdk/plugin-taskmarket",
"version": "0.1.0",
"files": ["dist/**/*", "README.md", "package.json"],
"scripts": {
"build": "tsup",
"clean": "rm -rf dist",
"test": "vitest run --passWithNoTests"
},
"sideEffects": false,
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"dependencies": {
"@goat-sdk/core": "workspace:*",
"zod": "catalog:"
},
"peerDependencies": {
"@goat-sdk/core": "workspace:*"
},
"homepage": "https://ohmygoat.dev",
"repository": {
"type": "git",
"url": "git+https://github.com/goat-sdk/goat.git"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/goat-sdk/goat/issues"
},
"keywords": ["ai", "agents", "web3"],
"devDependencies": {
"@swc/core": "1.10.1",
"unplugin-swc": "1.5.11"
}
}
49 changes: 49 additions & 0 deletions typescript/packages/plugins/taskmarket/src/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export interface TaskmarketTask {
id: string;
description: string;
reward: string;
mode: string;
status: string;
submissionCount: number;
expiryTime: string;
tags?: string[];
requester?: string;
}

export interface TaskListResponse {
tasks: TaskmarketTask[];
nextCursor?: string | null;
hasMore?: boolean;
}

export interface ListTasksParams {
limit: number;
mode?: string;
}

export const TASKMARKET_API_BASE_URL = "https://api.taskmarket.dev/api";

export class TaskmarketAPI {
async listTasks(params: ListTasksParams): Promise<TaskListResponse> {
const url = new URL(`${TASKMARKET_API_BASE_URL}/tasks`);
url.searchParams.set("status", "open");
url.searchParams.set("sort", "newest");
url.searchParams.set("limit", String(params.limit));
if (params.mode) {
url.searchParams.set("mode", params.mode);
}
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error(`Taskmarket API request failed with status ${response.status}`);
}
return (await response.json()) as TaskListResponse;
}

async getTask(taskId: string): Promise<TaskmarketTask> {
const response = await fetch(`${TASKMARKET_API_BASE_URL}/tasks/${taskId}`);
if (!response.ok) {
throw new Error(`Taskmarket API request failed with status ${response.status}`);
}
return (await response.json()) as TaskmarketTask;
}
}
Loading