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/beaver-knight-action-provider.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added Beaver Knight action provider: a read-only counterparty check (rate_wallet), ranked trading vaults with significance-tested figures (get_vault_rankings), and full Integrity Reports with on-chain EAS attestations (get_integrity_report) from the Beaver Knight trust bureau.
93 changes: 93 additions & 0 deletions typescript/agentkit/src/action-providers/beaverknight/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
# Beaver Knight Action Provider

This directory contains the **Beaver Knight** action provider. [Beaver Knight](https://www.beaverknight.com) is a trust bureau for autonomous trading agents: it rates agents and vaults on what they actually did with real money on chain (realised P&L read from the venue itself, with a statistical-significance gate), publishes the rating whether or not the subject asked, and attests ratings on Base via [EAS](https://attest.org) from a canister-controlled address that has no private key.

The actions are public, unauthenticated, read-only, and network-agnostic. They give an agent a counterparty check before it pays, delegates to, or copies another agent or vault.

## Directory Structure

```
beaverknight/
├── beaverknightActionProvider.ts # Main provider
├── beaverknightActionProvider.test.ts # Tests
├── constants.ts # Base URL, sort keys
├── index.ts # Main exports
├── README.md # Documentation
└── schemas.ts # Action schemas
```

## Actions

- `rate_wallet`: Check an address (execution wallet, owner wallet or token; EVM or Solana) against the bureau.

- Returns score (0-99), level (`strong | solid | fair | unproven | flag`), verdict, and two **separate** lists: `findings` (about the subject) and `limits` (about the bureau's own reach).
- An unrated address returns `found: false`. That is an absence of evidence, **not** a clean bill of health.

- `get_vault_rankings`: The vaults on the board (Hyperliquid trading vaults, ERC-4626 yield vaults), ranked.

- Sort by `score` (default), `return`, `sharpe`, `sortino`, `calmar`, `drawdown`, `tvl` or `decisions`; filter by level, venue, minimum TVL.
- Each vault carries the figures an allocator compares on, including the t-statistic of the edge and whether it clears the significance gate. A `null` figure means unmeasured, never zero.

- `get_integrity_report`: The full Integrity Report for one record (board id, Virtuals ACP id, or address).
- Every metric, the factor breakdown behind the score, findings and limits, disclosures, recent windows, a "basis" line describing how to re-derive every number from the venue's public API, and provenance, including the on-chain EAS attestation (UID, tx, keyless attester) when one exists.

## Adding to an agent

```typescript
import { AgentKit, beaverknightActionProvider } from "@coinbase/agentkit";

const agentKit = await AgentKit.from({
walletProvider,
actionProviders: [beaverknightActionProvider()],
});
```

An optional base URL can be passed (`beaverknightActionProvider("https://...")`) for a self-hosted or staging bureau.

## Examples

### Checking a counterparty

```bash
Prompt: before I pay this agent, is 0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04 rated?

-------------------
{
"version": 1,
"query": "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
"found": true,
"rating": {
"id": "hlv-pf1-a1b6d8",
"name": "PF1",
"score": 99,
"status": "Strong",
"level": "strong",
"verdict": "verified edge",
"venue": "Hyperliquid",
...
},
"meaning": "Verified track record, and the edge is statistically distinguishable from luck. This is the strongest verdict we issue.",
"findings": [],
"limits": [{ "label": "below size floor", "detail": null }],
...
}
```

### A miss

```bash
Prompt: is 0x0000000000000000000000000000000000000001 rated?

-------------------
{
"found": false,
"rating": null,
"meaning": "No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH. ... Do not treat a miss as a pass."
}
```

## Notes

- A tool error ("could not check", e.g. HTTP 503 from the bureau) is **not** the same as `found: false`; the provider keeps the two distinguishable.
- Ratings are a third-party census; no subject pays to be rated, and none can opt out. The bureau publishes what it could not establish alongside what it found.
- API documentation for machines: https://www.beaverknight.com/llms.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
import { beaverknightActionProvider } from "./beaverknightActionProvider";

describe("BeaverKnightActionProvider", () => {
const fetchMock = jest.fn();
global.fetch = fetchMock;

const provider = beaverknightActionProvider();

beforeEach(() => {
jest.resetAllMocks();
});

const okResponse = (body: unknown) => ({
ok: true,
status: 200,
text: jest.fn().mockResolvedValue(JSON.stringify(body)),
});

describe("rateWallet", () => {
it("returns the rating payload and hits /api/rate with the wallet", async () => {
const payload = {
version: 1,
found: true,
rating: { id: "hlv-pf1-a1b6d8", score: 99, level: "strong" },
findings: [],
limits: [{ label: "below size floor", detail: null }],
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
});

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/rate?wallet=0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
);
});

it("passes a found:false miss through unchanged (a miss is not a pass)", async () => {
const payload = {
version: 1,
found: false,
rating: null,
meaning:
"No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH.",
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0x0000000000000000000000000000000000000001",
});

expect(JSON.parse(result).found).toBe(false);
expect(result).toContain("NOT A CLEAN BILL OF HEALTH");
});

it("reports an upstream failure as 'could not check', never as unrated", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue('{"error":"upstream_unavailable"}'),
});

const result = await provider.rateWallet({ wallet: "0xabc" });

expect(result).toContain("Error checking wallet");
expect(result).toContain("503");
expect(result).toContain('NOT "unrated"');
});

it("handles network errors", async () => {
fetchMock.mockRejectedValue(new Error("Network error"));
const result = await provider.rateWallet({ wallet: "0xabc" });
expect(result).toContain("Error checking wallet");
expect(result).toContain("Network error");
});
});

describe("getVaultRankings", () => {
it("builds the query from the provided filters and defaults the limit", async () => {
const payload = { version: 1, vaults: [{ rank: 1, id: "hlv-pf1-a1b6d8" }], withdrawn: [] };
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getVaultRankings({
sort: "calmar",
level: "strong,solid",
minTvl: 250000,
venue: "Hyperliquid",
limit: null,
});

expect(JSON.parse(result)).toEqual(payload);
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl.startsWith("https://www.beaverknight.com/api/vaults?")).toBe(true);
const params = new URL(calledUrl).searchParams;
expect(params.get("sort")).toBe("calmar");
expect(params.get("level")).toBe("strong,solid");
expect(params.get("min_tvl")).toBe("250000");
expect(params.get("venue")).toBe("Hyperliquid");
expect(params.get("limit")).toBe("25");
});

it("omits null filters", async () => {
fetchMock.mockResolvedValue(okResponse({ vaults: [] }));
await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: 10,
});
const params = new URL(fetchMock.mock.calls[0][0] as string).searchParams;
expect(params.has("sort")).toBe(false);
expect(params.has("level")).toBe(false);
expect(params.has("min_tvl")).toBe(false);
expect(params.has("venue")).toBe(false);
expect(params.get("limit")).toBe("10");
});

it("handles API errors gracefully", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 500,
text: jest.fn().mockResolvedValue("boom"),
});
const result = await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: null,
});
expect(result).toContain("Error fetching vault rankings");
expect(result).toContain("500");
});
});

describe("getIntegrityReport", () => {
it("fetches the report by id and returns it unchanged", async () => {
const payload = {
found: true,
subject: { id: "hlv-pf1-a1b6d8", name: "PF1" },
provenance: { attestation: { uid: "0xec0e" } },
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getIntegrityReport({ id: "hlv-pf1-a1b6d8" });

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/hlv-pf1-a1b6d8",
);
});

it("URL-encodes the id", async () => {
fetchMock.mockResolvedValue(okResponse({ found: false }));
await provider.getIntegrityReport({ id: "weird id/with slash" });
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/weird%20id%2Fwith%20slash",
);
});

it("reports an upstream failure as 'could not check'", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue("{}"),
});
const result = await provider.getIntegrityReport({ id: "x" });
expect(result).toContain("Error fetching integrity report");
expect(result).toContain('NOT "unrated"');
});
});

describe("supportsNetwork", () => {
it("is network-agnostic", () => {
expect(provider.supportsNetwork()).toBe(true);
});
});

describe("custom base URL", () => {
it("uses the override and strips a trailing slash", async () => {
const custom = beaverknightActionProvider("https://example.test/");
fetchMock.mockResolvedValue(okResponse({ found: false }));
await custom.rateWallet({ wallet: "0xabc" });
expect(fetchMock).toHaveBeenCalledWith("https://example.test/api/rate?wallet=0xabc");
});
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat: add Beaver Knight action provider by liander-ai · Pull Request #1455 · coinbase/agentkit · GitHub
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/beaver-knight-action-provider.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added Beaver Knight action provider: a read-only counterparty check (rate_wallet), ranked trading vaults with significance-tested figures (get_vault_rankings), and full Integrity Reports with on-chain EAS attestations (get_integrity_report) from the Beaver Knight trust bureau.
93 changes: 93 additions & 0 deletions typescript/agentkit/src/action-providers/beaverknight/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
# Beaver Knight Action Provider

This directory contains the **Beaver Knight** action provider. [Beaver Knight](https://www.beaverknight.com) is a trust bureau for autonomous trading agents: it rates agents and vaults on what they actually did with real money on chain (realised P&L read from the venue itself, with a statistical-significance gate), publishes the rating whether or not the subject asked, and attests ratings on Base via [EAS](https://attest.org) from a canister-controlled address that has no private key.

The actions are public, unauthenticated, read-only, and network-agnostic. They give an agent a counterparty check before it pays, delegates to, or copies another agent or vault.

## Directory Structure

```
beaverknight/
├── beaverknightActionProvider.ts # Main provider
├── beaverknightActionProvider.test.ts # Tests
├── constants.ts # Base URL, sort keys
├── index.ts # Main exports
├── README.md # Documentation
└── schemas.ts # Action schemas
```

## Actions

- `rate_wallet`: Check an address (execution wallet, owner wallet or token; EVM or Solana) against the bureau.

- Returns score (0-99), level (`strong | solid | fair | unproven | flag`), verdict, and two **separate** lists: `findings` (about the subject) and `limits` (about the bureau's own reach).
- An unrated address returns `found: false`. That is an absence of evidence, **not** a clean bill of health.

- `get_vault_rankings`: The vaults on the board (Hyperliquid trading vaults, ERC-4626 yield vaults), ranked.

- Sort by `score` (default), `return`, `sharpe`, `sortino`, `calmar`, `drawdown`, `tvl` or `decisions`; filter by level, venue, minimum TVL.
- Each vault carries the figures an allocator compares on, including the t-statistic of the edge and whether it clears the significance gate. A `null` figure means unmeasured, never zero.

- `get_integrity_report`: The full Integrity Report for one record (board id, Virtuals ACP id, or address).
- Every metric, the factor breakdown behind the score, findings and limits, disclosures, recent windows, a "basis" line describing how to re-derive every number from the venue's public API, and provenance, including the on-chain EAS attestation (UID, tx, keyless attester) when one exists.

## Adding to an agent

```typescript
import { AgentKit, beaverknightActionProvider } from "@coinbase/agentkit";

const agentKit = await AgentKit.from({
walletProvider,
actionProviders: [beaverknightActionProvider()],
});
```

An optional base URL can be passed (`beaverknightActionProvider("https://...")`) for a self-hosted or staging bureau.

## Examples

### Checking a counterparty

```bash
Prompt: before I pay this agent, is 0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04 rated?

-------------------
{
"version": 1,
"query": "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
"found": true,
"rating": {
"id": "hlv-pf1-a1b6d8",
"name": "PF1",
"score": 99,
"status": "Strong",
"level": "strong",
"verdict": "verified edge",
"venue": "Hyperliquid",
...
},
"meaning": "Verified track record, and the edge is statistically distinguishable from luck. This is the strongest verdict we issue.",
"findings": [],
"limits": [{ "label": "below size floor", "detail": null }],
...
}
```

### A miss

```bash
Prompt: is 0x0000000000000000000000000000000000000001 rated?

-------------------
{
"found": false,
"rating": null,
"meaning": "No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH. ... Do not treat a miss as a pass."
}
```

## Notes

- A tool error ("could not check", e.g. HTTP 503 from the bureau) is **not** the same as `found: false`; the provider keeps the two distinguishable.
- Ratings are a third-party census; no subject pays to be rated, and none can opt out. The bureau publishes what it could not establish alongside what it found.
- API documentation for machines: https://www.beaverknight.com/llms.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
import { beaverknightActionProvider } from "./beaverknightActionProvider";

describe("BeaverKnightActionProvider", () => {
const fetchMock = jest.fn();
global.fetch = fetchMock;

const provider = beaverknightActionProvider();

beforeEach(() => {
jest.resetAllMocks();
});

const okResponse = (body: unknown) => ({
ok: true,
status: 200,
text: jest.fn().mockResolvedValue(JSON.stringify(body)),
});

describe("rateWallet", () => {
it("returns the rating payload and hits /api/rate with the wallet", async () => {
const payload = {
version: 1,
found: true,
rating: { id: "hlv-pf1-a1b6d8", score: 99, level: "strong" },
findings: [],
limits: [{ label: "below size floor", detail: null }],
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
});

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/rate?wallet=0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
);
});

it("passes a found:false miss through unchanged (a miss is not a pass)", async () => {
const payload = {
version: 1,
found: false,
rating: null,
meaning:
"No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH.",
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0x0000000000000000000000000000000000000001",
});

expect(JSON.parse(result).found).toBe(false);
expect(result).toContain("NOT A CLEAN BILL OF HEALTH");
});

it("reports an upstream failure as 'could not check', never as unrated", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue('{"error":"upstream_unavailable"}'),
});

const result = await provider.rateWallet({ wallet: "0xabc" });

expect(result).toContain("Error checking wallet");
expect(result).toContain("503");
expect(result).toContain('NOT "unrated"');
});

it("handles network errors", async () => {
fetchMock.mockRejectedValue(new Error("Network error"));
const result = await provider.rateWallet({ wallet: "0xabc" });
expect(result).toContain("Error checking wallet");
expect(result).toContain("Network error");
});
});

describe("getVaultRankings", () => {
it("builds the query from the provided filters and defaults the limit", async () => {
const payload = { version: 1, vaults: [{ rank: 1, id: "hlv-pf1-a1b6d8" }], withdrawn: [] };
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getVaultRankings({
sort: "calmar",
level: "strong,solid",
minTvl: 250000,
venue: "Hyperliquid",
limit: null,
});

expect(JSON.parse(result)).toEqual(payload);
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl.startsWith("https://www.beaverknight.com/api/vaults?")).toBe(true);
const params = new URL(calledUrl).searchParams;
expect(params.get("sort")).toBe("calmar");
expect(params.get("level")).toBe("strong,solid");
expect(params.get("min_tvl")).toBe("250000");
expect(params.get("venue")).toBe("Hyperliquid");
expect(params.get("limit")).toBe("25");
});

it("omits null filters", async () => {
fetchMock.mockResolvedValue(okResponse({ vaults: [] }));
await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: 10,
});
const params = new URL(fetchMock.mock.calls[0][0] as string).searchParams;
expect(params.has("sort")).toBe(false);
expect(params.has("level")).toBe(false);
expect(params.has("min_tvl")).toBe(false);
expect(params.has("venue")).toBe(false);
expect(params.get("limit")).toBe("10");
});

it("handles API errors gracefully", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 500,
text: jest.fn().mockResolvedValue("boom"),
});
const result = await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: null,
});
expect(result).toContain("Error fetching vault rankings");
expect(result).toContain("500");
});
});

describe("getIntegrityReport", () => {
it("fetches the report by id and returns it unchanged", async () => {
const payload = {
found: true,
subject: { id: "hlv-pf1-a1b6d8", name: "PF1" },
provenance: { attestation: { uid: "0xec0e" } },
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getIntegrityReport({ id: "hlv-pf1-a1b6d8" });

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/hlv-pf1-a1b6d8",
);
});

it("URL-encodes the id", async () => {
fetchMock.mockResolvedValue(okResponse({ found: false }));
await provider.getIntegrityReport({ id: "weird id/with slash" });
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/weird%20id%2Fwith%20slash",
);
});

it("reports an upstream failure as 'could not check'", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue("{}"),
});
const result = await provider.getIntegrityReport({ id: "x" });
expect(result).toContain("Error fetching integrity report");
expect(result).toContain('NOT "unrated"');
});
});

describe("supportsNetwork", () => {
it("is network-agnostic", () => {
expect(provider.supportsNetwork()).toBe(true);
});
});

describe("custom base URL", () => {
it("uses the override and strips a trailing slash", async () => {
const custom = beaverknightActionProvider("https://example.test/");
fetchMock.mockResolvedValue(okResponse({ found: false }));
await custom.rateWallet({ wallet: "0xabc" });
expect(fetchMock).toHaveBeenCalledWith("https://example.test/api/rate?wallet=0xabc");
});
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: add Beaver Knight action provider by liander-ai · Pull Request #1455 · coinbase/agentkit · GitHub
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/beaver-knight-action-provider.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added Beaver Knight action provider: a read-only counterparty check (rate_wallet), ranked trading vaults with significance-tested figures (get_vault_rankings), and full Integrity Reports with on-chain EAS attestations (get_integrity_report) from the Beaver Knight trust bureau.
93 changes: 93 additions & 0 deletions typescript/agentkit/src/action-providers/beaverknight/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
# Beaver Knight Action Provider

This directory contains the **Beaver Knight** action provider. [Beaver Knight](https://www.beaverknight.com) is a trust bureau for autonomous trading agents: it rates agents and vaults on what they actually did with real money on chain (realised P&L read from the venue itself, with a statistical-significance gate), publishes the rating whether or not the subject asked, and attests ratings on Base via [EAS](https://attest.org) from a canister-controlled address that has no private key.

The actions are public, unauthenticated, read-only, and network-agnostic. They give an agent a counterparty check before it pays, delegates to, or copies another agent or vault.

## Directory Structure

```
beaverknight/
├── beaverknightActionProvider.ts # Main provider
├── beaverknightActionProvider.test.ts # Tests
├── constants.ts # Base URL, sort keys
├── index.ts # Main exports
├── README.md # Documentation
└── schemas.ts # Action schemas
```

## Actions

- `rate_wallet`: Check an address (execution wallet, owner wallet or token; EVM or Solana) against the bureau.

- Returns score (0-99), level (`strong | solid | fair | unproven | flag`), verdict, and two **separate** lists: `findings` (about the subject) and `limits` (about the bureau's own reach).
- An unrated address returns `found: false`. That is an absence of evidence, **not** a clean bill of health.

- `get_vault_rankings`: The vaults on the board (Hyperliquid trading vaults, ERC-4626 yield vaults), ranked.

- Sort by `score` (default), `return`, `sharpe`, `sortino`, `calmar`, `drawdown`, `tvl` or `decisions`; filter by level, venue, minimum TVL.
- Each vault carries the figures an allocator compares on, including the t-statistic of the edge and whether it clears the significance gate. A `null` figure means unmeasured, never zero.

- `get_integrity_report`: The full Integrity Report for one record (board id, Virtuals ACP id, or address).
- Every metric, the factor breakdown behind the score, findings and limits, disclosures, recent windows, a "basis" line describing how to re-derive every number from the venue's public API, and provenance, including the on-chain EAS attestation (UID, tx, keyless attester) when one exists.

## Adding to an agent

```typescript
import { AgentKit, beaverknightActionProvider } from "@coinbase/agentkit";

const agentKit = await AgentKit.from({
walletProvider,
actionProviders: [beaverknightActionProvider()],
});
```

An optional base URL can be passed (`beaverknightActionProvider("https://...")`) for a self-hosted or staging bureau.

## Examples

### Checking a counterparty

```bash
Prompt: before I pay this agent, is 0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04 rated?

-------------------
{
"version": 1,
"query": "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
"found": true,
"rating": {
"id": "hlv-pf1-a1b6d8",
"name": "PF1",
"score": 99,
"status": "Strong",
"level": "strong",
"verdict": "verified edge",
"venue": "Hyperliquid",
...
},
"meaning": "Verified track record, and the edge is statistically distinguishable from luck. This is the strongest verdict we issue.",
"findings": [],
"limits": [{ "label": "below size floor", "detail": null }],
...
}
```

### A miss

```bash
Prompt: is 0x0000000000000000000000000000000000000001 rated?

-------------------
{
"found": false,
"rating": null,
"meaning": "No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH. ... Do not treat a miss as a pass."
}
```

## Notes

- A tool error ("could not check", e.g. HTTP 503 from the bureau) is **not** the same as `found: false`; the provider keeps the two distinguishable.
- Ratings are a third-party census; no subject pays to be rated, and none can opt out. The bureau publishes what it could not establish alongside what it found.
- API documentation for machines: https://www.beaverknight.com/llms.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
import { beaverknightActionProvider } from "./beaverknightActionProvider";

describe("BeaverKnightActionProvider", () => {
const fetchMock = jest.fn();
global.fetch = fetchMock;

const provider = beaverknightActionProvider();

beforeEach(() => {
jest.resetAllMocks();
});

const okResponse = (body: unknown) => ({
ok: true,
status: 200,
text: jest.fn().mockResolvedValue(JSON.stringify(body)),
});

describe("rateWallet", () => {
it("returns the rating payload and hits /api/rate with the wallet", async () => {
const payload = {
version: 1,
found: true,
rating: { id: "hlv-pf1-a1b6d8", score: 99, level: "strong" },
findings: [],
limits: [{ label: "below size floor", detail: null }],
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
});

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/rate?wallet=0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
);
});

it("passes a found:false miss through unchanged (a miss is not a pass)", async () => {
const payload = {
version: 1,
found: false,
rating: null,
meaning:
"No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH.",
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0x0000000000000000000000000000000000000001",
});

expect(JSON.parse(result).found).toBe(false);
expect(result).toContain("NOT A CLEAN BILL OF HEALTH");
});

it("reports an upstream failure as 'could not check', never as unrated", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue('{"error":"upstream_unavailable"}'),
});

const result = await provider.rateWallet({ wallet: "0xabc" });

expect(result).toContain("Error checking wallet");
expect(result).toContain("503");
expect(result).toContain('NOT "unrated"');
});

it("handles network errors", async () => {
fetchMock.mockRejectedValue(new Error("Network error"));
const result = await provider.rateWallet({ wallet: "0xabc" });
expect(result).toContain("Error checking wallet");
expect(result).toContain("Network error");
});
});

describe("getVaultRankings", () => {
it("builds the query from the provided filters and defaults the limit", async () => {
const payload = { version: 1, vaults: [{ rank: 1, id: "hlv-pf1-a1b6d8" }], withdrawn: [] };
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getVaultRankings({
sort: "calmar",
level: "strong,solid",
minTvl: 250000,
venue: "Hyperliquid",
limit: null,
});

expect(JSON.parse(result)).toEqual(payload);
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl.startsWith("https://www.beaverknight.com/api/vaults?")).toBe(true);
const params = new URL(calledUrl).searchParams;
expect(params.get("sort")).toBe("calmar");
expect(params.get("level")).toBe("strong,solid");
expect(params.get("min_tvl")).toBe("250000");
expect(params.get("venue")).toBe("Hyperliquid");
expect(params.get("limit")).toBe("25");
});

it("omits null filters", async () => {
fetchMock.mockResolvedValue(okResponse({ vaults: [] }));
await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: 10,
});
const params = new URL(fetchMock.mock.calls[0][0] as string).searchParams;
expect(params.has("sort")).toBe(false);
expect(params.has("level")).toBe(false);
expect(params.has("min_tvl")).toBe(false);
expect(params.has("venue")).toBe(false);
expect(params.get("limit")).toBe("10");
});

it("handles API errors gracefully", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 500,
text: jest.fn().mockResolvedValue("boom"),
});
const result = await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: null,
});
expect(result).toContain("Error fetching vault rankings");
expect(result).toContain("500");
});
});

describe("getIntegrityReport", () => {
it("fetches the report by id and returns it unchanged", async () => {
const payload = {
found: true,
subject: { id: "hlv-pf1-a1b6d8", name: "PF1" },
provenance: { attestation: { uid: "0xec0e" } },
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getIntegrityReport({ id: "hlv-pf1-a1b6d8" });

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/hlv-pf1-a1b6d8",
);
});

it("URL-encodes the id", async () => {
fetchMock.mockResolvedValue(okResponse({ found: false }));
await provider.getIntegrityReport({ id: "weird id/with slash" });
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/weird%20id%2Fwith%20slash",
);
});

it("reports an upstream failure as 'could not check'", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue("{}"),
});
const result = await provider.getIntegrityReport({ id: "x" });
expect(result).toContain("Error fetching integrity report");
expect(result).toContain('NOT "unrated"');
});
});

describe("supportsNetwork", () => {
it("is network-agnostic", () => {
expect(provider.supportsNetwork()).toBe(true);
});
});

describe("custom base URL", () => {
it("uses the override and strips a trailing slash", async () => {
const custom = beaverknightActionProvider("https://example.test/");
fetchMock.mockResolvedValue(okResponse({ found: false }));
await custom.rateWallet({ wallet: "0xabc" });
expect(fetchMock).toHaveBeenCalledWith("https://example.test/api/rate?wallet=0xabc");
});
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: add Beaver Knight action provider by liander-ai · Pull Request #1455 · coinbase/agentkit · GitHub
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/beaver-knight-action-provider.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added Beaver Knight action provider: a read-only counterparty check (rate_wallet), ranked trading vaults with significance-tested figures (get_vault_rankings), and full Integrity Reports with on-chain EAS attestations (get_integrity_report) from the Beaver Knight trust bureau.
93 changes: 93 additions & 0 deletions typescript/agentkit/src/action-providers/beaverknight/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
# Beaver Knight Action Provider

This directory contains the **Beaver Knight** action provider. [Beaver Knight](https://www.beaverknight.com) is a trust bureau for autonomous trading agents: it rates agents and vaults on what they actually did with real money on chain (realised P&L read from the venue itself, with a statistical-significance gate), publishes the rating whether or not the subject asked, and attests ratings on Base via [EAS](https://attest.org) from a canister-controlled address that has no private key.

The actions are public, unauthenticated, read-only, and network-agnostic. They give an agent a counterparty check before it pays, delegates to, or copies another agent or vault.

## Directory Structure

```
beaverknight/
├── beaverknightActionProvider.ts # Main provider
├── beaverknightActionProvider.test.ts # Tests
├── constants.ts # Base URL, sort keys
├── index.ts # Main exports
├── README.md # Documentation
└── schemas.ts # Action schemas
```

## Actions

- `rate_wallet`: Check an address (execution wallet, owner wallet or token; EVM or Solana) against the bureau.

- Returns score (0-99), level (`strong | solid | fair | unproven | flag`), verdict, and two **separate** lists: `findings` (about the subject) and `limits` (about the bureau's own reach).
- An unrated address returns `found: false`. That is an absence of evidence, **not** a clean bill of health.

- `get_vault_rankings`: The vaults on the board (Hyperliquid trading vaults, ERC-4626 yield vaults), ranked.

- Sort by `score` (default), `return`, `sharpe`, `sortino`, `calmar`, `drawdown`, `tvl` or `decisions`; filter by level, venue, minimum TVL.
- Each vault carries the figures an allocator compares on, including the t-statistic of the edge and whether it clears the significance gate. A `null` figure means unmeasured, never zero.

- `get_integrity_report`: The full Integrity Report for one record (board id, Virtuals ACP id, or address).
- Every metric, the factor breakdown behind the score, findings and limits, disclosures, recent windows, a "basis" line describing how to re-derive every number from the venue's public API, and provenance, including the on-chain EAS attestation (UID, tx, keyless attester) when one exists.

## Adding to an agent

```typescript
import { AgentKit, beaverknightActionProvider } from "@coinbase/agentkit";

const agentKit = await AgentKit.from({
walletProvider,
actionProviders: [beaverknightActionProvider()],
});
```

An optional base URL can be passed (`beaverknightActionProvider("https://...")`) for a self-hosted or staging bureau.

## Examples

### Checking a counterparty

```bash
Prompt: before I pay this agent, is 0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04 rated?

-------------------
{
"version": 1,
"query": "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
"found": true,
"rating": {
"id": "hlv-pf1-a1b6d8",
"name": "PF1",
"score": 99,
"status": "Strong",
"level": "strong",
"verdict": "verified edge",
"venue": "Hyperliquid",
...
},
"meaning": "Verified track record, and the edge is statistically distinguishable from luck. This is the strongest verdict we issue.",
"findings": [],
"limits": [{ "label": "below size floor", "detail": null }],
...
}
```

### A miss

```bash
Prompt: is 0x0000000000000000000000000000000000000001 rated?

-------------------
{
"found": false,
"rating": null,
"meaning": "No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH. ... Do not treat a miss as a pass."
}
```

## Notes

- A tool error ("could not check", e.g. HTTP 503 from the bureau) is **not** the same as `found: false`; the provider keeps the two distinguishable.
- Ratings are a third-party census; no subject pays to be rated, and none can opt out. The bureau publishes what it could not establish alongside what it found.
- API documentation for machines: https://www.beaverknight.com/llms.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
import { beaverknightActionProvider } from "./beaverknightActionProvider";

describe("BeaverKnightActionProvider", () => {
const fetchMock = jest.fn();
global.fetch = fetchMock;

const provider = beaverknightActionProvider();

beforeEach(() => {
jest.resetAllMocks();
});

const okResponse = (body: unknown) => ({
ok: true,
status: 200,
text: jest.fn().mockResolvedValue(JSON.stringify(body)),
});

describe("rateWallet", () => {
it("returns the rating payload and hits /api/rate with the wallet", async () => {
const payload = {
version: 1,
found: true,
rating: { id: "hlv-pf1-a1b6d8", score: 99, level: "strong" },
findings: [],
limits: [{ label: "below size floor", detail: null }],
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
});

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/rate?wallet=0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
);
});

it("passes a found:false miss through unchanged (a miss is not a pass)", async () => {
const payload = {
version: 1,
found: false,
rating: null,
meaning:
"No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH.",
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0x0000000000000000000000000000000000000001",
});

expect(JSON.parse(result).found).toBe(false);
expect(result).toContain("NOT A CLEAN BILL OF HEALTH");
});

it("reports an upstream failure as 'could not check', never as unrated", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue('{"error":"upstream_unavailable"}'),
});

const result = await provider.rateWallet({ wallet: "0xabc" });

expect(result).toContain("Error checking wallet");
expect(result).toContain("503");
expect(result).toContain('NOT "unrated"');
});

it("handles network errors", async () => {
fetchMock.mockRejectedValue(new Error("Network error"));
const result = await provider.rateWallet({ wallet: "0xabc" });
expect(result).toContain("Error checking wallet");
expect(result).toContain("Network error");
});
});

describe("getVaultRankings", () => {
it("builds the query from the provided filters and defaults the limit", async () => {
const payload = { version: 1, vaults: [{ rank: 1, id: "hlv-pf1-a1b6d8" }], withdrawn: [] };
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getVaultRankings({
sort: "calmar",
level: "strong,solid",
minTvl: 250000,
venue: "Hyperliquid",
limit: null,
});

expect(JSON.parse(result)).toEqual(payload);
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl.startsWith("https://www.beaverknight.com/api/vaults?")).toBe(true);
const params = new URL(calledUrl).searchParams;
expect(params.get("sort")).toBe("calmar");
expect(params.get("level")).toBe("strong,solid");
expect(params.get("min_tvl")).toBe("250000");
expect(params.get("venue")).toBe("Hyperliquid");
expect(params.get("limit")).toBe("25");
});

it("omits null filters", async () => {
fetchMock.mockResolvedValue(okResponse({ vaults: [] }));
await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: 10,
});
const params = new URL(fetchMock.mock.calls[0][0] as string).searchParams;
expect(params.has("sort")).toBe(false);
expect(params.has("level")).toBe(false);
expect(params.has("min_tvl")).toBe(false);
expect(params.has("venue")).toBe(false);
expect(params.get("limit")).toBe("10");
});

it("handles API errors gracefully", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 500,
text: jest.fn().mockResolvedValue("boom"),
});
const result = await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: null,
});
expect(result).toContain("Error fetching vault rankings");
expect(result).toContain("500");
});
});

describe("getIntegrityReport", () => {
it("fetches the report by id and returns it unchanged", async () => {
const payload = {
found: true,
subject: { id: "hlv-pf1-a1b6d8", name: "PF1" },
provenance: { attestation: { uid: "0xec0e" } },
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getIntegrityReport({ id: "hlv-pf1-a1b6d8" });

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/hlv-pf1-a1b6d8",
);
});

it("URL-encodes the id", async () => {
fetchMock.mockResolvedValue(okResponse({ found: false }));
await provider.getIntegrityReport({ id: "weird id/with slash" });
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/weird%20id%2Fwith%20slash",
);
});

it("reports an upstream failure as 'could not check'", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue("{}"),
});
const result = await provider.getIntegrityReport({ id: "x" });
expect(result).toContain("Error fetching integrity report");
expect(result).toContain('NOT "unrated"');
});
});

describe("supportsNetwork", () => {
it("is network-agnostic", () => {
expect(provider.supportsNetwork()).toBe(true);
});
});

describe("custom base URL", () => {
it("uses the override and strips a trailing slash", async () => {
const custom = beaverknightActionProvider("https://example.test/");
fetchMock.mockResolvedValue(okResponse({ found: false }));
await custom.rateWallet({ wallet: "0xabc" });
expect(fetchMock).toHaveBeenCalledWith("https://example.test/api/rate?wallet=0xabc");
});
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat: add Beaver Knight action provider by liander-ai · Pull Request #1455 · coinbase/agentkit · GitHub
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/beaver-knight-action-provider.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added Beaver Knight action provider: a read-only counterparty check (rate_wallet), ranked trading vaults with significance-tested figures (get_vault_rankings), and full Integrity Reports with on-chain EAS attestations (get_integrity_report) from the Beaver Knight trust bureau.
93 changes: 93 additions & 0 deletions typescript/agentkit/src/action-providers/beaverknight/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
# Beaver Knight Action Provider

This directory contains the **Beaver Knight** action provider. [Beaver Knight](https://www.beaverknight.com) is a trust bureau for autonomous trading agents: it rates agents and vaults on what they actually did with real money on chain (realised P&L read from the venue itself, with a statistical-significance gate), publishes the rating whether or not the subject asked, and attests ratings on Base via [EAS](https://attest.org) from a canister-controlled address that has no private key.

The actions are public, unauthenticated, read-only, and network-agnostic. They give an agent a counterparty check before it pays, delegates to, or copies another agent or vault.

## Directory Structure

```
beaverknight/
├── beaverknightActionProvider.ts # Main provider
├── beaverknightActionProvider.test.ts # Tests
├── constants.ts # Base URL, sort keys
├── index.ts # Main exports
├── README.md # Documentation
└── schemas.ts # Action schemas
```

## Actions

- `rate_wallet`: Check an address (execution wallet, owner wallet or token; EVM or Solana) against the bureau.

- Returns score (0-99), level (`strong | solid | fair | unproven | flag`), verdict, and two **separate** lists: `findings` (about the subject) and `limits` (about the bureau's own reach).
- An unrated address returns `found: false`. That is an absence of evidence, **not** a clean bill of health.

- `get_vault_rankings`: The vaults on the board (Hyperliquid trading vaults, ERC-4626 yield vaults), ranked.

- Sort by `score` (default), `return`, `sharpe`, `sortino`, `calmar`, `drawdown`, `tvl` or `decisions`; filter by level, venue, minimum TVL.
- Each vault carries the figures an allocator compares on, including the t-statistic of the edge and whether it clears the significance gate. A `null` figure means unmeasured, never zero.

- `get_integrity_report`: The full Integrity Report for one record (board id, Virtuals ACP id, or address).
- Every metric, the factor breakdown behind the score, findings and limits, disclosures, recent windows, a "basis" line describing how to re-derive every number from the venue's public API, and provenance, including the on-chain EAS attestation (UID, tx, keyless attester) when one exists.

## Adding to an agent

```typescript
import { AgentKit, beaverknightActionProvider } from "@coinbase/agentkit";

const agentKit = await AgentKit.from({
walletProvider,
actionProviders: [beaverknightActionProvider()],
});
```

An optional base URL can be passed (`beaverknightActionProvider("https://...")`) for a self-hosted or staging bureau.

## Examples

### Checking a counterparty

```bash
Prompt: before I pay this agent, is 0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04 rated?

-------------------
{
"version": 1,
"query": "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
"found": true,
"rating": {
"id": "hlv-pf1-a1b6d8",
"name": "PF1",
"score": 99,
"status": "Strong",
"level": "strong",
"verdict": "verified edge",
"venue": "Hyperliquid",
...
},
"meaning": "Verified track record, and the edge is statistically distinguishable from luck. This is the strongest verdict we issue.",
"findings": [],
"limits": [{ "label": "below size floor", "detail": null }],
...
}
```

### A miss

```bash
Prompt: is 0x0000000000000000000000000000000000000001 rated?

-------------------
{
"found": false,
"rating": null,
"meaning": "No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH. ... Do not treat a miss as a pass."
}
```

## Notes

- A tool error ("could not check", e.g. HTTP 503 from the bureau) is **not** the same as `found: false`; the provider keeps the two distinguishable.
- Ratings are a third-party census; no subject pays to be rated, and none can opt out. The bureau publishes what it could not establish alongside what it found.
- API documentation for machines: https://www.beaverknight.com/llms.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
import { beaverknightActionProvider } from "./beaverknightActionProvider";

describe("BeaverKnightActionProvider", () => {
const fetchMock = jest.fn();
global.fetch = fetchMock;

const provider = beaverknightActionProvider();

beforeEach(() => {
jest.resetAllMocks();
});

const okResponse = (body: unknown) => ({
ok: true,
status: 200,
text: jest.fn().mockResolvedValue(JSON.stringify(body)),
});

describe("rateWallet", () => {
it("returns the rating payload and hits /api/rate with the wallet", async () => {
const payload = {
version: 1,
found: true,
rating: { id: "hlv-pf1-a1b6d8", score: 99, level: "strong" },
findings: [],
limits: [{ label: "below size floor", detail: null }],
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
});

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/rate?wallet=0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
);
});

it("passes a found:false miss through unchanged (a miss is not a pass)", async () => {
const payload = {
version: 1,
found: false,
rating: null,
meaning:
"No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH.",
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0x0000000000000000000000000000000000000001",
});

expect(JSON.parse(result).found).toBe(false);
expect(result).toContain("NOT A CLEAN BILL OF HEALTH");
});

it("reports an upstream failure as 'could not check', never as unrated", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue('{"error":"upstream_unavailable"}'),
});

const result = await provider.rateWallet({ wallet: "0xabc" });

expect(result).toContain("Error checking wallet");
expect(result).toContain("503");
expect(result).toContain('NOT "unrated"');
});

it("handles network errors", async () => {
fetchMock.mockRejectedValue(new Error("Network error"));
const result = await provider.rateWallet({ wallet: "0xabc" });
expect(result).toContain("Error checking wallet");
expect(result).toContain("Network error");
});
});

describe("getVaultRankings", () => {
it("builds the query from the provided filters and defaults the limit", async () => {
const payload = { version: 1, vaults: [{ rank: 1, id: "hlv-pf1-a1b6d8" }], withdrawn: [] };
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getVaultRankings({
sort: "calmar",
level: "strong,solid",
minTvl: 250000,
venue: "Hyperliquid",
limit: null,
});

expect(JSON.parse(result)).toEqual(payload);
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl.startsWith("https://www.beaverknight.com/api/vaults?")).toBe(true);
const params = new URL(calledUrl).searchParams;
expect(params.get("sort")).toBe("calmar");
expect(params.get("level")).toBe("strong,solid");
expect(params.get("min_tvl")).toBe("250000");
expect(params.get("venue")).toBe("Hyperliquid");
expect(params.get("limit")).toBe("25");
});

it("omits null filters", async () => {
fetchMock.mockResolvedValue(okResponse({ vaults: [] }));
await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: 10,
});
const params = new URL(fetchMock.mock.calls[0][0] as string).searchParams;
expect(params.has("sort")).toBe(false);
expect(params.has("level")).toBe(false);
expect(params.has("min_tvl")).toBe(false);
expect(params.has("venue")).toBe(false);
expect(params.get("limit")).toBe("10");
});

it("handles API errors gracefully", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 500,
text: jest.fn().mockResolvedValue("boom"),
});
const result = await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: null,
});
expect(result).toContain("Error fetching vault rankings");
expect(result).toContain("500");
});
});

describe("getIntegrityReport", () => {
it("fetches the report by id and returns it unchanged", async () => {
const payload = {
found: true,
subject: { id: "hlv-pf1-a1b6d8", name: "PF1" },
provenance: { attestation: { uid: "0xec0e" } },
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getIntegrityReport({ id: "hlv-pf1-a1b6d8" });

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/hlv-pf1-a1b6d8",
);
});

it("URL-encodes the id", async () => {
fetchMock.mockResolvedValue(okResponse({ found: false }));
await provider.getIntegrityReport({ id: "weird id/with slash" });
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/weird%20id%2Fwith%20slash",
);
});

it("reports an upstream failure as 'could not check'", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue("{}"),
});
const result = await provider.getIntegrityReport({ id: "x" });
expect(result).toContain("Error fetching integrity report");
expect(result).toContain('NOT "unrated"');
});
});

describe("supportsNetwork", () => {
it("is network-agnostic", () => {
expect(provider.supportsNetwork()).toBe(true);
});
});

describe("custom base URL", () => {
it("uses the override and strips a trailing slash", async () => {
const custom = beaverknightActionProvider("https://example.test/");
fetchMock.mockResolvedValue(okResponse({ found: false }));
await custom.rateWallet({ wallet: "0xabc" });
expect(fetchMock).toHaveBeenCalledWith("https://example.test/api/rate?wallet=0xabc");
});
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: add Beaver Knight action provider by liander-ai · Pull Request #1455 · coinbase/agentkit · GitHub
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/beaver-knight-action-provider.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added Beaver Knight action provider: a read-only counterparty check (rate_wallet), ranked trading vaults with significance-tested figures (get_vault_rankings), and full Integrity Reports with on-chain EAS attestations (get_integrity_report) from the Beaver Knight trust bureau.
93 changes: 93 additions & 0 deletions typescript/agentkit/src/action-providers/beaverknight/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
# Beaver Knight Action Provider

This directory contains the **Beaver Knight** action provider. [Beaver Knight](https://www.beaverknight.com) is a trust bureau for autonomous trading agents: it rates agents and vaults on what they actually did with real money on chain (realised P&L read from the venue itself, with a statistical-significance gate), publishes the rating whether or not the subject asked, and attests ratings on Base via [EAS](https://attest.org) from a canister-controlled address that has no private key.

The actions are public, unauthenticated, read-only, and network-agnostic. They give an agent a counterparty check before it pays, delegates to, or copies another agent or vault.

## Directory Structure

```
beaverknight/
├── beaverknightActionProvider.ts # Main provider
├── beaverknightActionProvider.test.ts # Tests
├── constants.ts # Base URL, sort keys
├── index.ts # Main exports
├── README.md # Documentation
└── schemas.ts # Action schemas
```

## Actions

- `rate_wallet`: Check an address (execution wallet, owner wallet or token; EVM or Solana) against the bureau.

- Returns score (0-99), level (`strong | solid | fair | unproven | flag`), verdict, and two **separate** lists: `findings` (about the subject) and `limits` (about the bureau's own reach).
- An unrated address returns `found: false`. That is an absence of evidence, **not** a clean bill of health.

- `get_vault_rankings`: The vaults on the board (Hyperliquid trading vaults, ERC-4626 yield vaults), ranked.

- Sort by `score` (default), `return`, `sharpe`, `sortino`, `calmar`, `drawdown`, `tvl` or `decisions`; filter by level, venue, minimum TVL.
- Each vault carries the figures an allocator compares on, including the t-statistic of the edge and whether it clears the significance gate. A `null` figure means unmeasured, never zero.

- `get_integrity_report`: The full Integrity Report for one record (board id, Virtuals ACP id, or address).
- Every metric, the factor breakdown behind the score, findings and limits, disclosures, recent windows, a "basis" line describing how to re-derive every number from the venue's public API, and provenance, including the on-chain EAS attestation (UID, tx, keyless attester) when one exists.

## Adding to an agent

```typescript
import { AgentKit, beaverknightActionProvider } from "@coinbase/agentkit";

const agentKit = await AgentKit.from({
walletProvider,
actionProviders: [beaverknightActionProvider()],
});
```

An optional base URL can be passed (`beaverknightActionProvider("https://...")`) for a self-hosted or staging bureau.

## Examples

### Checking a counterparty

```bash
Prompt: before I pay this agent, is 0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04 rated?

-------------------
{
"version": 1,
"query": "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
"found": true,
"rating": {
"id": "hlv-pf1-a1b6d8",
"name": "PF1",
"score": 99,
"status": "Strong",
"level": "strong",
"verdict": "verified edge",
"venue": "Hyperliquid",
...
},
"meaning": "Verified track record, and the edge is statistically distinguishable from luck. This is the strongest verdict we issue.",
"findings": [],
"limits": [{ "label": "below size floor", "detail": null }],
...
}
```

### A miss

```bash
Prompt: is 0x0000000000000000000000000000000000000001 rated?

-------------------
{
"found": false,
"rating": null,
"meaning": "No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH. ... Do not treat a miss as a pass."
}
```

## Notes

- A tool error ("could not check", e.g. HTTP 503 from the bureau) is **not** the same as `found: false`; the provider keeps the two distinguishable.
- Ratings are a third-party census; no subject pays to be rated, and none can opt out. The bureau publishes what it could not establish alongside what it found.
- API documentation for machines: https://www.beaverknight.com/llms.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
import { beaverknightActionProvider } from "./beaverknightActionProvider";

describe("BeaverKnightActionProvider", () => {
const fetchMock = jest.fn();
global.fetch = fetchMock;

const provider = beaverknightActionProvider();

beforeEach(() => {
jest.resetAllMocks();
});

const okResponse = (body: unknown) => ({
ok: true,
status: 200,
text: jest.fn().mockResolvedValue(JSON.stringify(body)),
});

describe("rateWallet", () => {
it("returns the rating payload and hits /api/rate with the wallet", async () => {
const payload = {
version: 1,
found: true,
rating: { id: "hlv-pf1-a1b6d8", score: 99, level: "strong" },
findings: [],
limits: [{ label: "below size floor", detail: null }],
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
});

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/rate?wallet=0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
);
});

it("passes a found:false miss through unchanged (a miss is not a pass)", async () => {
const payload = {
version: 1,
found: false,
rating: null,
meaning:
"No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH.",
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0x0000000000000000000000000000000000000001",
});

expect(JSON.parse(result).found).toBe(false);
expect(result).toContain("NOT A CLEAN BILL OF HEALTH");
});

it("reports an upstream failure as 'could not check', never as unrated", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue('{"error":"upstream_unavailable"}'),
});

const result = await provider.rateWallet({ wallet: "0xabc" });

expect(result).toContain("Error checking wallet");
expect(result).toContain("503");
expect(result).toContain('NOT "unrated"');
});

it("handles network errors", async () => {
fetchMock.mockRejectedValue(new Error("Network error"));
const result = await provider.rateWallet({ wallet: "0xabc" });
expect(result).toContain("Error checking wallet");
expect(result).toContain("Network error");
});
});

describe("getVaultRankings", () => {
it("builds the query from the provided filters and defaults the limit", async () => {
const payload = { version: 1, vaults: [{ rank: 1, id: "hlv-pf1-a1b6d8" }], withdrawn: [] };
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getVaultRankings({
sort: "calmar",
level: "strong,solid",
minTvl: 250000,
venue: "Hyperliquid",
limit: null,
});

expect(JSON.parse(result)).toEqual(payload);
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl.startsWith("https://www.beaverknight.com/api/vaults?")).toBe(true);
const params = new URL(calledUrl).searchParams;
expect(params.get("sort")).toBe("calmar");
expect(params.get("level")).toBe("strong,solid");
expect(params.get("min_tvl")).toBe("250000");
expect(params.get("venue")).toBe("Hyperliquid");
expect(params.get("limit")).toBe("25");
});

it("omits null filters", async () => {
fetchMock.mockResolvedValue(okResponse({ vaults: [] }));
await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: 10,
});
const params = new URL(fetchMock.mock.calls[0][0] as string).searchParams;
expect(params.has("sort")).toBe(false);
expect(params.has("level")).toBe(false);
expect(params.has("min_tvl")).toBe(false);
expect(params.has("venue")).toBe(false);
expect(params.get("limit")).toBe("10");
});

it("handles API errors gracefully", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 500,
text: jest.fn().mockResolvedValue("boom"),
});
const result = await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: null,
});
expect(result).toContain("Error fetching vault rankings");
expect(result).toContain("500");
});
});

describe("getIntegrityReport", () => {
it("fetches the report by id and returns it unchanged", async () => {
const payload = {
found: true,
subject: { id: "hlv-pf1-a1b6d8", name: "PF1" },
provenance: { attestation: { uid: "0xec0e" } },
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getIntegrityReport({ id: "hlv-pf1-a1b6d8" });

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/hlv-pf1-a1b6d8",
);
});

it("URL-encodes the id", async () => {
fetchMock.mockResolvedValue(okResponse({ found: false }));
await provider.getIntegrityReport({ id: "weird id/with slash" });
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/weird%20id%2Fwith%20slash",
);
});

it("reports an upstream failure as 'could not check'", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue("{}"),
});
const result = await provider.getIntegrityReport({ id: "x" });
expect(result).toContain("Error fetching integrity report");
expect(result).toContain('NOT "unrated"');
});
});

describe("supportsNetwork", () => {
it("is network-agnostic", () => {
expect(provider.supportsNetwork()).toBe(true);
});
});

describe("custom base URL", () => {
it("uses the override and strips a trailing slash", async () => {
const custom = beaverknightActionProvider("https://example.test/");
fetchMock.mockResolvedValue(okResponse({ found: false }));
await custom.rateWallet({ wallet: "0xabc" });
expect(fetchMock).toHaveBeenCalledWith("https://example.test/api/rate?wallet=0xabc");
});
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: add Beaver Knight action provider by liander-ai · Pull Request #1455 · coinbase/agentkit · GitHub
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/beaver-knight-action-provider.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added Beaver Knight action provider: a read-only counterparty check (rate_wallet), ranked trading vaults with significance-tested figures (get_vault_rankings), and full Integrity Reports with on-chain EAS attestations (get_integrity_report) from the Beaver Knight trust bureau.
93 changes: 93 additions & 0 deletions typescript/agentkit/src/action-providers/beaverknight/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
# Beaver Knight Action Provider

This directory contains the **Beaver Knight** action provider. [Beaver Knight](https://www.beaverknight.com) is a trust bureau for autonomous trading agents: it rates agents and vaults on what they actually did with real money on chain (realised P&L read from the venue itself, with a statistical-significance gate), publishes the rating whether or not the subject asked, and attests ratings on Base via [EAS](https://attest.org) from a canister-controlled address that has no private key.

The actions are public, unauthenticated, read-only, and network-agnostic. They give an agent a counterparty check before it pays, delegates to, or copies another agent or vault.

## Directory Structure

```
beaverknight/
├── beaverknightActionProvider.ts # Main provider
├── beaverknightActionProvider.test.ts # Tests
├── constants.ts # Base URL, sort keys
├── index.ts # Main exports
├── README.md # Documentation
└── schemas.ts # Action schemas
```

## Actions

- `rate_wallet`: Check an address (execution wallet, owner wallet or token; EVM or Solana) against the bureau.

- Returns score (0-99), level (`strong | solid | fair | unproven | flag`), verdict, and two **separate** lists: `findings` (about the subject) and `limits` (about the bureau's own reach).
- An unrated address returns `found: false`. That is an absence of evidence, **not** a clean bill of health.

- `get_vault_rankings`: The vaults on the board (Hyperliquid trading vaults, ERC-4626 yield vaults), ranked.

- Sort by `score` (default), `return`, `sharpe`, `sortino`, `calmar`, `drawdown`, `tvl` or `decisions`; filter by level, venue, minimum TVL.
- Each vault carries the figures an allocator compares on, including the t-statistic of the edge and whether it clears the significance gate. A `null` figure means unmeasured, never zero.

- `get_integrity_report`: The full Integrity Report for one record (board id, Virtuals ACP id, or address).
- Every metric, the factor breakdown behind the score, findings and limits, disclosures, recent windows, a "basis" line describing how to re-derive every number from the venue's public API, and provenance, including the on-chain EAS attestation (UID, tx, keyless attester) when one exists.

## Adding to an agent

```typescript
import { AgentKit, beaverknightActionProvider } from "@coinbase/agentkit";

const agentKit = await AgentKit.from({
walletProvider,
actionProviders: [beaverknightActionProvider()],
});
```

An optional base URL can be passed (`beaverknightActionProvider("https://...")`) for a self-hosted or staging bureau.

## Examples

### Checking a counterparty

```bash
Prompt: before I pay this agent, is 0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04 rated?

-------------------
{
"version": 1,
"query": "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
"found": true,
"rating": {
"id": "hlv-pf1-a1b6d8",
"name": "PF1",
"score": 99,
"status": "Strong",
"level": "strong",
"verdict": "verified edge",
"venue": "Hyperliquid",
...
},
"meaning": "Verified track record, and the edge is statistically distinguishable from luck. This is the strongest verdict we issue.",
"findings": [],
"limits": [{ "label": "below size floor", "detail": null }],
...
}
```

### A miss

```bash
Prompt: is 0x0000000000000000000000000000000000000001 rated?

-------------------
{
"found": false,
"rating": null,
"meaning": "No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH. ... Do not treat a miss as a pass."
}
```

## Notes

- A tool error ("could not check", e.g. HTTP 503 from the bureau) is **not** the same as `found: false`; the provider keeps the two distinguishable.
- Ratings are a third-party census; no subject pays to be rated, and none can opt out. The bureau publishes what it could not establish alongside what it found.
- API documentation for machines: https://www.beaverknight.com/llms.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
import { beaverknightActionProvider } from "./beaverknightActionProvider";

describe("BeaverKnightActionProvider", () => {
const fetchMock = jest.fn();
global.fetch = fetchMock;

const provider = beaverknightActionProvider();

beforeEach(() => {
jest.resetAllMocks();
});

const okResponse = (body: unknown) => ({
ok: true,
status: 200,
text: jest.fn().mockResolvedValue(JSON.stringify(body)),
});

describe("rateWallet", () => {
it("returns the rating payload and hits /api/rate with the wallet", async () => {
const payload = {
version: 1,
found: true,
rating: { id: "hlv-pf1-a1b6d8", score: 99, level: "strong" },
findings: [],
limits: [{ label: "below size floor", detail: null }],
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
});

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/rate?wallet=0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
);
});

it("passes a found:false miss through unchanged (a miss is not a pass)", async () => {
const payload = {
version: 1,
found: false,
rating: null,
meaning:
"No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH.",
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0x0000000000000000000000000000000000000001",
});

expect(JSON.parse(result).found).toBe(false);
expect(result).toContain("NOT A CLEAN BILL OF HEALTH");
});

it("reports an upstream failure as 'could not check', never as unrated", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue('{"error":"upstream_unavailable"}'),
});

const result = await provider.rateWallet({ wallet: "0xabc" });

expect(result).toContain("Error checking wallet");
expect(result).toContain("503");
expect(result).toContain('NOT "unrated"');
});

it("handles network errors", async () => {
fetchMock.mockRejectedValue(new Error("Network error"));
const result = await provider.rateWallet({ wallet: "0xabc" });
expect(result).toContain("Error checking wallet");
expect(result).toContain("Network error");
});
});

describe("getVaultRankings", () => {
it("builds the query from the provided filters and defaults the limit", async () => {
const payload = { version: 1, vaults: [{ rank: 1, id: "hlv-pf1-a1b6d8" }], withdrawn: [] };
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getVaultRankings({
sort: "calmar",
level: "strong,solid",
minTvl: 250000,
venue: "Hyperliquid",
limit: null,
});

expect(JSON.parse(result)).toEqual(payload);
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl.startsWith("https://www.beaverknight.com/api/vaults?")).toBe(true);
const params = new URL(calledUrl).searchParams;
expect(params.get("sort")).toBe("calmar");
expect(params.get("level")).toBe("strong,solid");
expect(params.get("min_tvl")).toBe("250000");
expect(params.get("venue")).toBe("Hyperliquid");
expect(params.get("limit")).toBe("25");
});

it("omits null filters", async () => {
fetchMock.mockResolvedValue(okResponse({ vaults: [] }));
await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: 10,
});
const params = new URL(fetchMock.mock.calls[0][0] as string).searchParams;
expect(params.has("sort")).toBe(false);
expect(params.has("level")).toBe(false);
expect(params.has("min_tvl")).toBe(false);
expect(params.has("venue")).toBe(false);
expect(params.get("limit")).toBe("10");
});

it("handles API errors gracefully", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 500,
text: jest.fn().mockResolvedValue("boom"),
});
const result = await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: null,
});
expect(result).toContain("Error fetching vault rankings");
expect(result).toContain("500");
});
});

describe("getIntegrityReport", () => {
it("fetches the report by id and returns it unchanged", async () => {
const payload = {
found: true,
subject: { id: "hlv-pf1-a1b6d8", name: "PF1" },
provenance: { attestation: { uid: "0xec0e" } },
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getIntegrityReport({ id: "hlv-pf1-a1b6d8" });

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/hlv-pf1-a1b6d8",
);
});

it("URL-encodes the id", async () => {
fetchMock.mockResolvedValue(okResponse({ found: false }));
await provider.getIntegrityReport({ id: "weird id/with slash" });
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/weird%20id%2Fwith%20slash",
);
});

it("reports an upstream failure as 'could not check'", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue("{}"),
});
const result = await provider.getIntegrityReport({ id: "x" });
expect(result).toContain("Error fetching integrity report");
expect(result).toContain('NOT "unrated"');
});
});

describe("supportsNetwork", () => {
it("is network-agnostic", () => {
expect(provider.supportsNetwork()).toBe(true);
});
});

describe("custom base URL", () => {
it("uses the override and strips a trailing slash", async () => {
const custom = beaverknightActionProvider("https://example.test/");
fetchMock.mockResolvedValue(okResponse({ found: false }));
await custom.rateWallet({ wallet: "0xabc" });
expect(fetchMock).toHaveBeenCalledWith("https://example.test/api/rate?wallet=0xabc");
});
});
});
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat: add Beaver Knight action provider by liander-ai · Pull Request #1455 · coinbase/agentkit · GitHub
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/beaver-knight-action-provider.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": patch
---

Added Beaver Knight action provider: a read-only counterparty check (rate_wallet), ranked trading vaults with significance-tested figures (get_vault_rankings), and full Integrity Reports with on-chain EAS attestations (get_integrity_report) from the Beaver Knight trust bureau.
93 changes: 93 additions & 0 deletions typescript/agentkit/src/action-providers/beaverknight/README.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
# Beaver Knight Action Provider

This directory contains the **Beaver Knight** action provider. [Beaver Knight](https://www.beaverknight.com) is a trust bureau for autonomous trading agents: it rates agents and vaults on what they actually did with real money on chain (realised P&L read from the venue itself, with a statistical-significance gate), publishes the rating whether or not the subject asked, and attests ratings on Base via [EAS](https://attest.org) from a canister-controlled address that has no private key.

The actions are public, unauthenticated, read-only, and network-agnostic. They give an agent a counterparty check before it pays, delegates to, or copies another agent or vault.

## Directory Structure

```
beaverknight/
├── beaverknightActionProvider.ts # Main provider
├── beaverknightActionProvider.test.ts # Tests
├── constants.ts # Base URL, sort keys
├── index.ts # Main exports
├── README.md # Documentation
└── schemas.ts # Action schemas
```

## Actions

- `rate_wallet`: Check an address (execution wallet, owner wallet or token; EVM or Solana) against the bureau.

- Returns score (0-99), level (`strong | solid | fair | unproven | flag`), verdict, and two **separate** lists: `findings` (about the subject) and `limits` (about the bureau's own reach).
- An unrated address returns `found: false`. That is an absence of evidence, **not** a clean bill of health.

- `get_vault_rankings`: The vaults on the board (Hyperliquid trading vaults, ERC-4626 yield vaults), ranked.

- Sort by `score` (default), `return`, `sharpe`, `sortino`, `calmar`, `drawdown`, `tvl` or `decisions`; filter by level, venue, minimum TVL.
- Each vault carries the figures an allocator compares on, including the t-statistic of the edge and whether it clears the significance gate. A `null` figure means unmeasured, never zero.

- `get_integrity_report`: The full Integrity Report for one record (board id, Virtuals ACP id, or address).
- Every metric, the factor breakdown behind the score, findings and limits, disclosures, recent windows, a "basis" line describing how to re-derive every number from the venue's public API, and provenance, including the on-chain EAS attestation (UID, tx, keyless attester) when one exists.

## Adding to an agent

```typescript
import { AgentKit, beaverknightActionProvider } from "@coinbase/agentkit";

const agentKit = await AgentKit.from({
walletProvider,
actionProviders: [beaverknightActionProvider()],
});
```

An optional base URL can be passed (`beaverknightActionProvider("https://...")`) for a self-hosted or staging bureau.

## Examples

### Checking a counterparty

```bash
Prompt: before I pay this agent, is 0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04 rated?

-------------------
{
"version": 1,
"query": "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
"found": true,
"rating": {
"id": "hlv-pf1-a1b6d8",
"name": "PF1",
"score": 99,
"status": "Strong",
"level": "strong",
"verdict": "verified edge",
"venue": "Hyperliquid",
...
},
"meaning": "Verified track record, and the edge is statistically distinguishable from luck. This is the strongest verdict we issue.",
"findings": [],
"limits": [{ "label": "below size floor", "detail": null }],
...
}
```

### A miss

```bash
Prompt: is 0x0000000000000000000000000000000000000001 rated?

-------------------
{
"found": false,
"rating": null,
"meaning": "No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH. ... Do not treat a miss as a pass."
}
```

## Notes

- A tool error ("could not check", e.g. HTTP 503 from the bureau) is **not** the same as `found: false`; the provider keeps the two distinguishable.
- Ratings are a third-party census; no subject pays to be rated, and none can opt out. The bureau publishes what it could not establish alongside what it found.
- API documentation for machines: https://www.beaverknight.com/llms.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
import { beaverknightActionProvider } from "./beaverknightActionProvider";

describe("BeaverKnightActionProvider", () => {
const fetchMock = jest.fn();
global.fetch = fetchMock;

const provider = beaverknightActionProvider();

beforeEach(() => {
jest.resetAllMocks();
});

const okResponse = (body: unknown) => ({
ok: true,
status: 200,
text: jest.fn().mockResolvedValue(JSON.stringify(body)),
});

describe("rateWallet", () => {
it("returns the rating payload and hits /api/rate with the wallet", async () => {
const payload = {
version: 1,
found: true,
rating: { id: "hlv-pf1-a1b6d8", score: 99, level: "strong" },
findings: [],
limits: [{ label: "below size floor", detail: null }],
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
});

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/rate?wallet=0xa1b6d8efbcb2fb750a84dbc05649fa4968034f04",
);
});

it("passes a found:false miss through unchanged (a miss is not a pass)", async () => {
const payload = {
version: 1,
found: false,
rating: null,
meaning:
"No Beaver Knight rating exists for this address. That is an ABSENCE OF EVIDENCE, NOT A CLEAN BILL OF HEALTH.",
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.rateWallet({
wallet: "0x0000000000000000000000000000000000000001",
});

expect(JSON.parse(result).found).toBe(false);
expect(result).toContain("NOT A CLEAN BILL OF HEALTH");
});

it("reports an upstream failure as 'could not check', never as unrated", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue('{"error":"upstream_unavailable"}'),
});

const result = await provider.rateWallet({ wallet: "0xabc" });

expect(result).toContain("Error checking wallet");
expect(result).toContain("503");
expect(result).toContain('NOT "unrated"');
});

it("handles network errors", async () => {
fetchMock.mockRejectedValue(new Error("Network error"));
const result = await provider.rateWallet({ wallet: "0xabc" });
expect(result).toContain("Error checking wallet");
expect(result).toContain("Network error");
});
});

describe("getVaultRankings", () => {
it("builds the query from the provided filters and defaults the limit", async () => {
const payload = { version: 1, vaults: [{ rank: 1, id: "hlv-pf1-a1b6d8" }], withdrawn: [] };
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getVaultRankings({
sort: "calmar",
level: "strong,solid",
minTvl: 250000,
venue: "Hyperliquid",
limit: null,
});

expect(JSON.parse(result)).toEqual(payload);
const calledUrl = fetchMock.mock.calls[0][0] as string;
expect(calledUrl.startsWith("https://www.beaverknight.com/api/vaults?")).toBe(true);
const params = new URL(calledUrl).searchParams;
expect(params.get("sort")).toBe("calmar");
expect(params.get("level")).toBe("strong,solid");
expect(params.get("min_tvl")).toBe("250000");
expect(params.get("venue")).toBe("Hyperliquid");
expect(params.get("limit")).toBe("25");
});

it("omits null filters", async () => {
fetchMock.mockResolvedValue(okResponse({ vaults: [] }));
await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: 10,
});
const params = new URL(fetchMock.mock.calls[0][0] as string).searchParams;
expect(params.has("sort")).toBe(false);
expect(params.has("level")).toBe(false);
expect(params.has("min_tvl")).toBe(false);
expect(params.has("venue")).toBe(false);
expect(params.get("limit")).toBe("10");
});

it("handles API errors gracefully", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 500,
text: jest.fn().mockResolvedValue("boom"),
});
const result = await provider.getVaultRankings({
sort: null,
level: null,
minTvl: null,
venue: null,
limit: null,
});
expect(result).toContain("Error fetching vault rankings");
expect(result).toContain("500");
});
});

describe("getIntegrityReport", () => {
it("fetches the report by id and returns it unchanged", async () => {
const payload = {
found: true,
subject: { id: "hlv-pf1-a1b6d8", name: "PF1" },
provenance: { attestation: { uid: "0xec0e" } },
};
fetchMock.mockResolvedValue(okResponse(payload));

const result = await provider.getIntegrityReport({ id: "hlv-pf1-a1b6d8" });

expect(JSON.parse(result)).toEqual(payload);
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/hlv-pf1-a1b6d8",
);
});

it("URL-encodes the id", async () => {
fetchMock.mockResolvedValue(okResponse({ found: false }));
await provider.getIntegrityReport({ id: "weird id/with slash" });
expect(fetchMock).toHaveBeenCalledWith(
"https://www.beaverknight.com/api/report/weird%20id%2Fwith%20slash",
);
});

it("reports an upstream failure as 'could not check'", async () => {
fetchMock.mockResolvedValue({
ok: false,
status: 503,
text: jest.fn().mockResolvedValue("{}"),
});
const result = await provider.getIntegrityReport({ id: "x" });
expect(result).toContain("Error fetching integrity report");
expect(result).toContain('NOT "unrated"');
});
});

describe("supportsNetwork", () => {
it("is network-agnostic", () => {
expect(provider.supportsNetwork()).toBe(true);
});
});

describe("custom base URL", () => {
it("uses the override and strips a trailing slash", async () => {
const custom = beaverknightActionProvider("https://example.test/");
fetchMock.mockResolvedValue(okResponse({ found: false }));
await custom.rateWallet({ wallet: "0xabc" });
expect(fetchMock).toHaveBeenCalledWith("https://example.test/api/rate?wallet=0xabc");
});
});
});
Loading
Loading