Skip to content
Draft
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
11 changes: 11 additions & 0 deletions typescript/agentkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,17 @@ const agent = createAgent({
</table>
</details>
<details>
<summary><strong>AZZLE</strong></summary>
<table width="100%">
<tr><td width="200"><code>post_azzle_task</code></td><td width="768">Posts an AZZLE V2 task on Base using an AZL-wei amount.</td></tr>
<tr><td width="200"><code>claim_azzle_task</code></td><td width="768">Claims a posted AZZLE V2 task.</td></tr>
<tr><td width="200"><code>fund_azzle_task</code></td><td width="768">Funds a claimed AZZLE task in AZL wei.</td></tr>
<tr><td width="200"><code>mark_azzle_task_delivered</code></td><td width="768">Marks an active AZZLE task delivered.</td></tr>
<tr><td width="200"><code>release_azzle_escrow</code></td><td width="768">Releases AZL escrow for a delivered task.</td></tr>
<tr><td width="200"><code>complete_azzle_task</code></td><td width="768">Completes a delivered AZZLE task after release.</td></tr>
</table>
</details>
<details>
<summary><strong>Base Account</strong></summary>
<table width="100%">
<tr>
Expand Down
27 changes: 27 additions & 0 deletions typescript/agentkit/src/action-providers/azzle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# AZZLE Action Provider

`AzzleActionProvider` exposes the supported AZZLE V2 task lifecycle on Base
mainnet:

- `post_azzle_task`
- `claim_azzle_task`
- `fund_azzle_task`
- `mark_azzle_task_delivered`
- `release_azzle_escrow`
- `complete_azzle_task`

All amounts are AZL wei. The caller supplies `taskRegistry` from its
runtime-loaded AZZLE V2 manifest; this provider intentionally does not embed
protocol addresses.

The provider supports only Base mainnet (`8453` / `base-mainnet`). Its
transactions use AgentKit's `EvmWalletProvider`, so AgentKit retains custody
and signing control.

The intentionally scoped lifecycle is:

`post -> claim -> fund -> markDelivered -> release / complete`

Cancellation, expiry, and dispute operations are not exposed by this initial
provider. Agents should validate task state and role permissions before calling
each write action.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { encodeFunctionData } from "viem";
import { EvmWalletProvider } from "../../wallet-providers";
import { AZZLE_TASK_REGISTRY_ABI } from "./constants";
import { azzleActionProvider } from "./azzleActionProvider";

const REGISTRY = "0x1111111111111111111111111111111111111111";
const HASH = "0x1234567890123456789012345678901234567890123456789012345678901234";

function wallet(chainId = "8453"): jest.Mocked<EvmWalletProvider> {
return {
getNetwork: jest.fn().mockReturnValue({ protocolFamily: "evm", chainId }),
sendTransaction: jest.fn().mockResolvedValue(HASH),
waitForTransactionReceipt: jest.fn().mockResolvedValue({ logs: [] }),
} as unknown as jest.Mocked<EvmWalletProvider>;
}

describe("AzzleActionProvider", () => {
const provider = azzleActionProvider();

it("only supports Base mainnet", () => {
expect(provider.supportsNetwork({ protocolFamily: "evm", chainId: "8453" })).toBe(true);
expect(provider.supportsNetwork({ protocolFamily: "evm", chainId: "1" })).toBe(false);
});

it("posts AZL-wei tasks through an AgentKit EVM wallet", async () => {
const mockWallet = wallet();
const deadline = Math.floor(Date.now() / 1000) + 3600;

const response = await provider.postAzzleTask(mockWallet, {
taskRegistry: REGISTRY,
totalAmountAzlWei: "1000000000000000000",
deadline,
});

expect(mockWallet.sendTransaction).toHaveBeenCalledWith({
to: REGISTRY,
data: encodeFunctionData({
abi: AZZLE_TASK_REGISTRY_ABI,
functionName: "post",
args: [1000000000000000000n, BigInt(deadline)],
}),
});
expect(mockWallet.waitForTransactionReceipt).toHaveBeenCalledWith(HASH);
expect(response).toContain("Posted AZZLE task");
});

it("refuses a non-Base wallet before sending", async () => {
const mockWallet = wallet("1");
const response = await provider.claimAzzleTask(mockWallet, {
taskRegistry: REGISTRY,
taskId: "1",
});

expect(mockWallet.sendTransaction).not.toHaveBeenCalled();
expect(response).toContain("require Base mainnet");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { z } from "zod";
import { encodeFunctionData, type Hex } from "viem";
import { CreateAction } from "../actionDecorator";
import { ActionProvider } from "../actionProvider";
import { Network } from "../../network";
import { EvmWalletProvider } from "../../wallet-providers";
import { AZZLE_TASK_REGISTRY_ABI } from "./constants";
import {
AzzleTaskAmountSchema,
AzzleTaskSchema,
PostAzzleTaskSchema,
} from "./schemas";

function isBaseMainnet(network: Network): boolean {
return network.protocolFamily === "evm" &&
(network.chainId === "8453" || network.networkId === "base-mainnet");
}

function futureDeadline(deadline: number): void {
if (deadline <= Math.floor(Date.now() / 1000)) {
throw new Error("deadline must be a Unix timestamp in the future.");
}
}

async function send(
walletProvider: EvmWalletProvider,
taskRegistry: string,
data: Hex,
): Promise<string> {
if (!isBaseMainnet(walletProvider.getNetwork())) {
throw new Error("AZZLE V2 actions require Base mainnet (chain ID 8453).");
}
const hash = await walletProvider.sendTransaction({
to: taskRegistry as Hex,
data,
});
await walletProvider.waitForTransactionReceipt(hash);
return hash;
}

/**
* AgentKit provider for AZZLE V2 task coordination on Base.
* Amounts are AZL wei. Registry addresses are supplied at runtime from the
* caller's current manifest rather than embedded in this provider.
*/
export class AzzleActionProvider extends ActionProvider<EvmWalletProvider> {
constructor() {
super("azzle", []);
}

supportsNetwork = isBaseMainnet;

@CreateAction({
name: "post_azzle_task",
description: "Post an AZZLE V2 task on Base. totalAmountAzlWei is AZL wei, not USDC.",
schema: PostAzzleTaskSchema,
})
async postAzzleTask(
walletProvider: EvmWalletProvider,
args: z.infer<typeof PostAzzleTaskSchema>,
): Promise<string> {
try {
futureDeadline(args.deadline);
const hash = await send(walletProvider, args.taskRegistry, encodeFunctionData({
abi: AZZLE_TASK_REGISTRY_ABI,
functionName: "post",
args: [BigInt(args.totalAmountAzlWei), BigInt(args.deadline)],
}));
return `Posted AZZLE task. Read TaskPosted from receipt ${hash} for its canonical v2:standard:N or v2:micro:N ID.`;
} catch (error) {
return `Error posting AZZLE task: ${error}`;
}
}

@CreateAction({ name: "claim_azzle_task", description: "Claim a posted AZZLE V2 task on Base.", schema: AzzleTaskSchema })
async claimAzzleTask(walletProvider: EvmWalletProvider, args: z.infer<typeof AzzleTaskSchema>): Promise<string> {
return this.call(walletProvider, args.taskRegistry, "claim", [BigInt(args.taskId)], "Claimed AZZLE task");
}

@CreateAction({ name: "fund_azzle_task", description: "Fund a claimed AZZLE task in AZL wei; full funding activates it.", schema: AzzleTaskAmountSchema })
async fundAzzleTask(walletProvider: EvmWalletProvider, args: z.infer<typeof AzzleTaskAmountSchema>): Promise<string> {
return this.call(walletProvider, args.taskRegistry, "fund", [BigInt(args.taskId), BigInt(args.amountAzlWei)], "Funded AZZLE task");
}

@CreateAction({ name: "mark_azzle_task_delivered", description: "Mark a fully funded active AZZLE task delivered.", schema: AzzleTaskSchema })
async markAzzleTaskDelivered(walletProvider: EvmWalletProvider, args: z.infer<typeof AzzleTaskSchema>): Promise<string> {
return this.call(walletProvider, args.taskRegistry, "markDelivered", [BigInt(args.taskId)], "Marked AZZLE task delivered");
}

@CreateAction({ name: "release_azzle_escrow", description: "Release AZL wei from a delivered AZZLE task.", schema: AzzleTaskAmountSchema })
async releaseAzzleEscrow(walletProvider: EvmWalletProvider, args: z.infer<typeof AzzleTaskAmountSchema>): Promise<string> {
return this.call(walletProvider, args.taskRegistry, "release", [BigInt(args.taskId), BigInt(args.amountAzlWei)], "Released AZZLE escrow");
}

@CreateAction({ name: "complete_azzle_task", description: "Complete a delivered AZZLE task after release.", schema: AzzleTaskSchema })
async completeAzzleTask(walletProvider: EvmWalletProvider, args: z.infer<typeof AzzleTaskSchema>): Promise<string> {
return this.call(walletProvider, args.taskRegistry, "complete", [BigInt(args.taskId)], "Completed AZZLE task");
}

private async call(
walletProvider: EvmWalletProvider,
taskRegistry: string,
functionName: "claim" | "fund" | "markDelivered" | "release" | "complete",
args: readonly bigint[],
success: string,
): Promise<string> {
try {
const data = functionName === "fund" || functionName === "release"
? encodeFunctionData({
abi: AZZLE_TASK_REGISTRY_ABI,
functionName,
args: [args[0], args[1]],
})
: encodeFunctionData({
abi: AZZLE_TASK_REGISTRY_ABI,
functionName,
args: [args[0]],
});
const hash = await send(walletProvider, taskRegistry, data);
return `${success}. Transaction hash: ${hash}`;
} catch (error) {
return `Error: ${error}`;
}
}
}

export const azzleActionProvider = () => new AzzleActionProvider();
53 changes: 53 additions & 0 deletions typescript/agentkit/src/action-providers/azzle/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
export const AZZLE_TASK_REGISTRY_ABI = [
{
type: "function",
name: "post",
stateMutability: "nonpayable",
inputs: [
{ name: "totalAmount", type: "uint256" },
{ name: "deadline", type: "uint64" },
],
outputs: [{ name: "", type: "uint256" }],
},
{
type: "function",
name: "claim",
stateMutability: "nonpayable",
inputs: [{ name: "taskId", type: "uint256" }],
outputs: [],
},
{
type: "function",
name: "fund",
stateMutability: "nonpayable",
inputs: [
{ name: "taskId", type: "uint256" },
{ name: "amount", type: "uint256" },
],
outputs: [],
},
{
type: "function",
name: "markDelivered",
stateMutability: "nonpayable",
inputs: [{ name: "taskId", type: "uint256" }],
outputs: [],
},
{
type: "function",
name: "release",
stateMutability: "nonpayable",
inputs: [
{ name: "taskId", type: "uint256" },
{ name: "amount", type: "uint256" },
],
outputs: [],
},
{
type: "function",
name: "complete",
stateMutability: "nonpayable",
inputs: [{ name: "taskId", type: "uint256" }],
outputs: [],
},
] as const;
2 changes: 2 additions & 0 deletions typescript/agentkit/src/action-providers/azzle/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./azzleActionProvider";
export * from "./schemas";
19 changes: 19 additions & 0 deletions typescript/agentkit/src/action-providers/azzle/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { z } from "zod";

const taskId = z.string().regex(/^[1-9]\d*$/, "Task ID must be a positive integer.");
const azlWei = z.string().regex(/^[1-9]\d*$/, "Amount must be a positive AZL-wei integer.");

export const PostAzzleTaskSchema = z.object({
taskRegistry: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Task registry must be an address."),
totalAmountAzlWei: azlWei,
deadline: z.number().int().positive().describe("Future Unix timestamp."),
});

export const AzzleTaskSchema = z.object({
taskRegistry: z.string().regex(/^0x[a-fA-F0-9]{40}$/, "Task registry must be an address."),
taskId,
});

export const AzzleTaskAmountSchema = AzzleTaskSchema.extend({
amountAzlWei: azlWei,
});
1 change: 1 addition & 0 deletions typescript/agentkit/src/action-providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export * from "./wallet";
export * from "./weth";
export * from "./wow";
export * from "./allora";
export * from "./azzle";
export * from "./flaunch";
export * from "./onramp";
export * from "./vaultsfyi";
Expand Down
Loading