diff --git a/.changeset/paladintrust.md b/.changeset/paladintrust.md new file mode 100644 index 000000000..93d26a4af --- /dev/null +++ b/.changeset/paladintrust.md @@ -0,0 +1,5 @@ +--- +"@coinbase/agentkit": patch +--- + +Added paladinTrust action provider for pre-swap token-risk evaluation on Base. diff --git a/typescript/agentkit/src/action-providers/index.ts b/typescript/agentkit/src/action-providers/index.ts index 9f7164086..d03f7fec3 100644 --- a/typescript/agentkit/src/action-providers/index.ts +++ b/typescript/agentkit/src/action-providers/index.ts @@ -23,6 +23,7 @@ export * from "./pyth"; export * from "./moonwell"; export * from "./morpho"; export * from "./opensea"; +export * from "./paladinTrust"; export * from "./spl"; export * from "./superfluid"; export * from "./sushi"; diff --git a/typescript/agentkit/src/action-providers/paladinTrust/README.md b/typescript/agentkit/src/action-providers/paladinTrust/README.md new file mode 100644 index 000000000..37b38047e --- /dev/null +++ b/typescript/agentkit/src/action-providers/paladinTrust/README.md @@ -0,0 +1,60 @@ +# PaladinFi Trust Action Provider + +This directory contains the **PaladinTrustActionProvider** implementation, which provides an action to query the **PaladinFi Trust Check API** for pre-swap token-risk evaluation on Base. + +## Directory Structure + +``` +paladinTrust/ +├── paladinTrustActionProvider.ts # Main provider with trust-check functionality +├── paladinTrustActionProvider.test.ts # Tests +├── schemas.ts # Action input schemas +├── index.ts # Main exports +└── README.md # This file +``` + +## Actions + +- `check_token_risk`: Get a composed-risk recommendation for a token contract on Base + - Returns `recommendation` (`sample-allow` | `sample-warn` | `sample-block` on the preview endpoint this provider calls) plus per-factor breakdown (OFAC SDN, GoPlus, Etherscan source verification, anomaly heuristics) + - Does not execute any transactions and does not require a wallet signature + - Calls `POST /v1/trust-check/preview` (free, sample-fixture response — recommendation is `sample-` prefixed and every factor has `real: false`) + +For paid mode with x402 settlement ($0.001 USDC per live evaluation on Base), use the external [`@paladinfi/agentkit-actions`](https://www.npmjs.com/package/@paladinfi/agentkit-actions) package. + +## Adding New Actions + +To add new PaladinFi Trust actions: + +1. Define your action schema in `schemas.ts` +2. Implement the action in `paladinTrustActionProvider.ts` +3. Add tests in `paladinTrustActionProvider.test.ts` + +## Network Support + +The PaladinFi Trust provider supports Base mainnet (`chainId 8453`) only. `supportsNetwork` rejects all other networks. + +## Configuration + +The provider takes optional configuration: + +```typescript +const provider = paladinTrustActionProvider({ + apiBase: "https://swap.paladinfi.com", // optional; defaults to the production endpoint. Override only for local testing (http://localhost[:port]). + sendTaker: false, // optional; default false. When true, the wallet's address is sent to the API as the "taker" field for anomaly heuristics. +}); +``` + +Configuration is constructor-only. There are no environment-variable fallbacks (this matches the in-tree `zeroX` / `sushi` / `enso` convention). + +## Privacy + +By default, this provider does **not** send the agent's wallet address to `swap.paladinfi.com`. The request body contains only `{chainId, address}`. Set `sendTaker: true` in the constructor if you want the API to include the wallet address as the `taker` field — this improves the anomaly heuristic signal at the cost of sharing the wallet address with the API on every call. + +## Notes + +- This action provider is **decision-only**. It does not sign or send any swap. Compose it with AgentKit's `zeroX`, `enso`, or `sushi` providers to actually execute a swap. +- The preview endpoint returns a sample fixture — `recommendation` is one of `sample-allow` / `sample-warn` / `sample-block`, and every factor has `real: false`. The "sample-" prefix exists so the response cannot be cropped into looking like a real evaluation. +- On the paid endpoint (available via `@paladinfi/agentkit-actions`), when all upstream sources are temporarily unreachable, the API returns `recommendation: "warn"` (fail-closed, never silent-allow). Clients keying off `allow` should treat anything else as not-allowed. + +For more information on the **PaladinFi Trust Check API**, visit [paladinfi.com/trust-check/](https://paladinfi.com/trust-check/). diff --git a/typescript/agentkit/src/action-providers/paladinTrust/index.ts b/typescript/agentkit/src/action-providers/paladinTrust/index.ts new file mode 100644 index 000000000..921c6c43e --- /dev/null +++ b/typescript/agentkit/src/action-providers/paladinTrust/index.ts @@ -0,0 +1 @@ +export * from "./paladinTrustActionProvider"; diff --git a/typescript/agentkit/src/action-providers/paladinTrust/paladinTrustActionProvider.test.ts b/typescript/agentkit/src/action-providers/paladinTrust/paladinTrustActionProvider.test.ts new file mode 100644 index 000000000..abb4534e4 --- /dev/null +++ b/typescript/agentkit/src/action-providers/paladinTrust/paladinTrustActionProvider.test.ts @@ -0,0 +1,320 @@ +import { paladinTrustActionProvider } from "./paladinTrustActionProvider"; +import { CheckTokenRiskSchema } from "./schemas"; +import { EvmWalletProvider } from "../../wallet-providers"; + +// Mock the fetch function +global.fetch = jest.fn(); + +describe("PaladinTrust Schema Validation", () => { + it("should validate CheckTokenRisk schema with valid input", () => { + const validInput = { + chainId: 8453, + tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + }; + + const result = CheckTokenRiskSchema.safeParse(validInput); + expect(result.success).toBe(true); + }); + + it("should fail CheckTokenRisk validation with invalid token address", () => { + const invalidInput = { + chainId: 8453, + tokenAddress: "not-an-address", + }; + + const result = CheckTokenRiskSchema.safeParse(invalidInput); + expect(result.success).toBe(false); + }); + + it("should reject a non-positive chainId", () => { + const input = { + chainId: 0, + tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + }; + + const result = CheckTokenRiskSchema.safeParse(input); + expect(result.success).toBe(false); + }); + + it("should reject a non-integer chainId", () => { + const input = { + chainId: 8453.5, + tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + }; + + const result = CheckTokenRiskSchema.safeParse(input); + expect(result.success).toBe(false); + }); + + it("should reject input with extra unknown fields ignored gracefully", () => { + // Zod strips unknown by default; this just confirms the parse still succeeds + // on the canonical pair. + const input = { + chainId: 8453, + tokenAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", + }; + + const result = CheckTokenRiskSchema.safeParse(input); + expect(result.success).toBe(true); + }); +}); + +describe("PaladinTrust Action Provider", () => { + let provider: ReturnType; + let mockWalletProvider: jest.Mocked; + + const MOCK_TOKEN = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; + const MOCK_TAKER = "0xabcdef1234567890abcdef1234567890abcdef12"; + const MOCK_CHAIN_ID = 8453; + + // Fixture mirroring the live /v1/trust-check/preview response shape verified + // on 2026-05-22 against https://swap.paladinfi.com/v1/trust-check/preview for + // USDC on Base (chainId 8453, address 0x833589fC...02913). + const PREVIEW_FIXTURE = { + address: MOCK_TOKEN, + chainId: MOCK_CHAIN_ID, + taker: null, + request_id: "4a6228c7-d5f7-40da-9d83-3f266b17493a", + trust: { + risk_score: null, + risk_score_scale: "0-100 (lower = safer); null on preview because no live evaluation runs", + recommendation: "sample-allow", + recommendation_enum: ["allow", "warn", "block"], + factors: [ + { source: "ofac", signal: "not_listed", details: "Live on paid endpoint; not evaluated on preview", real: false }, + { source: "etherscan_source", signal: "verified", details: "SAMPLE — illustrative only", real: false }, + { source: "goplus", signal: "ok", details: "SAMPLE — illustrative only", real: false }, + { source: "anomaly", signal: "ok", details: "SAMPLE — illustrative only", real: false }, + ], + version: "1.1", + _preview: true, + _message: + "Preview response — request shape was validated by Pydantic. The trust block is a SAMPLE FIXTURE with no live data evaluation. recommendation is 'sample-' prefixed so this response cannot be cropped into looking like a real assessment. POST /v1/trust-check (x402-paid, $0.001/call) for live OFAC SDN screening (refreshed daily from Treasury XML), GoPlus token security, Etherscan verification, and anomaly heuristics.", + }, + }; + + beforeEach(() => { + provider = paladinTrustActionProvider(); + + mockWalletProvider = { + getAddress: jest.fn().mockReturnValue(MOCK_TAKER), + getNetwork: jest.fn().mockReturnValue({ + chainId: MOCK_CHAIN_ID.toString(), + protocolFamily: "evm", + networkId: "base-mainnet", + }), + } as unknown as jest.Mocked; + + (global.fetch as jest.Mock).mockReset(); + }); + + describe("checkTokenRisk", () => { + it("should POST to the preview endpoint and parse the response", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: jest.fn().mockResolvedValueOnce(PREVIEW_FIXTURE), + }); + + const args = { + chainId: MOCK_CHAIN_ID, + tokenAddress: MOCK_TOKEN, + }; + + const response = await provider.checkTokenRisk(mockWalletProvider, args); + const parsed = JSON.parse(response); + + expect(global.fetch).toHaveBeenCalledTimes(1); + const [url, init] = (global.fetch as jest.Mock).mock.calls[0]; + expect(url).toBe("https://swap.paladinfi.com/v1/trust-check/preview"); + expect(init.method).toBe("POST"); + // Default sendTaker is false → body must NOT include `taker`. + const bodyParsed = JSON.parse(init.body); + expect(bodyParsed).toEqual({ + chainId: MOCK_CHAIN_ID, + address: MOCK_TOKEN, + }); + expect(bodyParsed.taker).toBeUndefined(); + + expect(parsed.success).toBe(true); + expect(parsed.recommendation).toBe("sample-allow"); + expect(parsed.version).toBe("1.1"); + expect(parsed.factors).toHaveLength(4); + expect(parsed.factors[0].source).toBe("ofac"); + expect(parsed.response.trust._message).toContain("Preview response"); + }); + + it("should include taker in the body when constructed with sendTaker: true", async () => { + const takerProvider = paladinTrustActionProvider({ sendTaker: true }); + + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: jest.fn().mockResolvedValueOnce(PREVIEW_FIXTURE), + }); + + const args = { + chainId: MOCK_CHAIN_ID, + tokenAddress: MOCK_TOKEN, + }; + + await takerProvider.checkTokenRisk(mockWalletProvider, args); + + const [, init] = (global.fetch as jest.Mock).mock.calls[0]; + const bodyParsed = JSON.parse(init.body); + expect(bodyParsed).toEqual({ + chainId: MOCK_CHAIN_ID, + address: MOCK_TOKEN, + taker: MOCK_TAKER, + }); + }); + + it("should omit taker if sendTaker: true but walletProvider.getAddress throws", async () => { + const takerProvider = paladinTrustActionProvider({ sendTaker: true }); + const throwingWallet = { + getAddress: jest.fn().mockImplementation(() => { + throw new Error("wallet not connected"); + }), + getNetwork: jest.fn().mockReturnValue({ + chainId: MOCK_CHAIN_ID.toString(), + protocolFamily: "evm", + networkId: "base-mainnet", + }), + } as unknown as jest.Mocked; + + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: jest.fn().mockResolvedValueOnce(PREVIEW_FIXTURE), + }); + + await takerProvider.checkTokenRisk(throwingWallet, { + chainId: MOCK_CHAIN_ID, + tokenAddress: MOCK_TOKEN, + }); + + const [, init] = (global.fetch as jest.Mock).mock.calls[0]; + const bodyParsed = JSON.parse(init.body); + expect(bodyParsed.taker).toBeUndefined(); + expect(bodyParsed).toEqual({ + chainId: MOCK_CHAIN_ID, + address: MOCK_TOKEN, + }); + }); + + it("should surface HTTP errors as success: false WITHOUT leaking response body", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + status: 503, + statusText: "Service Unavailable", + text: jest.fn().mockResolvedValueOnce("upstream Etherscan key XYZ123 leaked"), + }); + + const response = await provider.checkTokenRisk(mockWalletProvider, { + chainId: MOCK_CHAIN_ID, + tokenAddress: MOCK_TOKEN, + }); + const parsed = JSON.parse(response); + + expect(parsed.success).toBe(false); + expect(parsed.error).toContain("HTTP 503"); + expect(parsed.error).toContain("Service Unavailable"); + expect(parsed.error).toContain("PaladinFi Trust API"); + // Body content must NOT be passed through. + expect(parsed.error).not.toContain("XYZ123"); + expect(parsed.error).not.toContain("Etherscan"); + }); + + it("should surface network errors as success: false", async () => { + (global.fetch as jest.Mock).mockRejectedValueOnce(new Error("network down")); + + const response = await provider.checkTokenRisk(mockWalletProvider, { + chainId: MOCK_CHAIN_ID, + tokenAddress: MOCK_TOKEN, + }); + const parsed = JSON.parse(response); + + expect(parsed.success).toBe(false); + expect(parsed.error).toContain("network down"); + }); + + it("should reject responses missing the 'trust' block", async () => { + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: jest.fn().mockResolvedValueOnce({ address: MOCK_TOKEN, chainId: MOCK_CHAIN_ID }), + }); + + const response = await provider.checkTokenRisk(mockWalletProvider, { + chainId: MOCK_CHAIN_ID, + tokenAddress: MOCK_TOKEN, + }); + const parsed = JSON.parse(response); + + expect(parsed.success).toBe(false); + expect(parsed.error).toContain("unexpected response shape"); + }); + }); + + describe("constructor validation", () => { + it("should accept https:// apiBase", () => { + expect(() => paladinTrustActionProvider({ apiBase: "https://swap.paladinfi.com" })).not.toThrow(); + }); + + it("should accept http://localhost (no port)", () => { + expect(() => paladinTrustActionProvider({ apiBase: "http://localhost" })).not.toThrow(); + }); + + it("should accept http://localhost:8000", () => { + expect(() => paladinTrustActionProvider({ apiBase: "http://localhost:8000" })).not.toThrow(); + }); + + it("should reject http://localhost.evil.com (subdomain hijack)", () => { + expect(() => paladinTrustActionProvider({ apiBase: "http://localhost.evil.com" })).toThrow( + /must use https/, + ); + }); + + it("should reject http:// non-localhost apiBase", () => { + expect(() => paladinTrustActionProvider({ apiBase: "http://swap.paladinfi.com" })).toThrow( + /must use https/, + ); + }); + }); + + describe("supportsNetwork", () => { + it("should return true for Base mainnet by chainId string", () => { + expect( + provider.supportsNetwork({ + protocolFamily: "evm", + chainId: "8453", + networkId: "base-mainnet", + }), + ).toBe(true); + }); + + it("should return true for Base mainnet by networkId only", () => { + expect( + provider.supportsNetwork({ + protocolFamily: "evm", + networkId: "base-mainnet", + }), + ).toBe(true); + }); + + it("should return false for other EVM networks", () => { + expect( + provider.supportsNetwork({ + protocolFamily: "evm", + chainId: "1", + networkId: "ethereum-mainnet", + }), + ).toBe(false); + }); + + it("should return false for non-evm networks", () => { + expect( + provider.supportsNetwork({ + protocolFamily: "svm", + networkId: "solana-mainnet", + }), + ).toBe(false); + }); + }); +}); diff --git a/typescript/agentkit/src/action-providers/paladinTrust/paladinTrustActionProvider.ts b/typescript/agentkit/src/action-providers/paladinTrust/paladinTrustActionProvider.ts new file mode 100644 index 000000000..2d61c196c --- /dev/null +++ b/typescript/agentkit/src/action-providers/paladinTrust/paladinTrustActionProvider.ts @@ -0,0 +1,229 @@ +import { z } from "zod"; +import { ActionProvider } from "../actionProvider"; +import { Network } from "../../network"; +import { CreateAction } from "../actionDecorator"; +import { CheckTokenRiskSchema } from "./schemas"; +import { EvmWalletProvider } from "../../wallet-providers"; + +const DEFAULT_API_BASE = "https://swap.paladinfi.com"; +const PREVIEW_ENDPOINT = "/v1/trust-check/preview"; +const PALADINFI_BASE_CHAIN_ID = 8453; + +/** + * Configuration for the PaladinTrustActionProvider. + */ +export interface PaladinTrustActionProviderConfig { + /** + * Base URL of the PaladinFi Trust Check API. Defaults to + * `https://swap.paladinfi.com`. Override only for local testing + * (e.g. `http://localhost:8000`). + */ + apiBase?: string; + + /** + * When `true`, the wallet's address is sent to the API as the `taker` + * field so the API can run anomaly heuristics on the taker↔contract + * relationship. Default `false` (privacy-default-on). Opt-in to improve + * the anomaly signal at the cost of sharing the agent's wallet address + * with `swap.paladinfi.com` on every call. + */ + sendTaker?: boolean; +} + +interface TrustFactor { + source: string; + signal: string; + details?: string; + real?: boolean; + weight?: number; +} + +interface TrustBlock { + recommendation: string; + recommendation_enum?: string[]; + factors: TrustFactor[]; + version?: string; + risk_score?: number | null; + risk_score_scale?: string; + _preview?: boolean; + _message?: string; +} + +interface TrustCheckResponse { + address: string; + chainId: number; + taker?: string | null; + request_id?: string; + trust: TrustBlock; + _mcp_paid_endpoint_info?: unknown; +} + +/** + * PaladinFi Trust Check action provider for pre-swap token-risk evaluation. + * + * Returns a composed recommendation (`sample-allow` | `sample-warn` | + * `sample-block` on the preview endpoint this provider calls) plus a + * per-factor breakdown (OFAC SDN, GoPlus, Etherscan source verification, + * anomaly heuristics) for a given token contract on Base. + * + * This provider is decision-only — it does not sign or send any swap. + * Compose it with `zeroX`, `enso`, `sushi`, or another swap-executing action + * provider to actually perform a trade. + * + * This in-tree provider calls the free preview endpoint only. For paid mode + * with x402 settlement ($0.001 USDC per live evaluation), use the external + * `@paladinfi/agentkit-actions` npm package. + */ +export class PaladinTrustActionProvider extends ActionProvider { + #apiBase: string; + #sendTaker: boolean; + + /** + * Constructor for the PaladinTrustActionProvider. + * + * @param config - Configuration for the provider. + */ + constructor(config: PaladinTrustActionProviderConfig = {}) { + // Lowercase provider name matches zeroX's convention (`super("zerox", [])`). + super("paladintrust", []); + const apiBase = config.apiBase ?? DEFAULT_API_BASE; + // Allow https://, plus localhost on http:// for tests. Tightened so + // `http://localhost.evil.com` no longer slips through a `startsWith` check. + const isLocalhost = + apiBase === "http://localhost" || apiBase.startsWith("http://localhost:"); + if (!apiBase.startsWith("https://") && !isLocalhost) { + throw new Error( + `PaladinFi Trust API base must use https:// (or http://localhost[:port] for tests); got "${apiBase}"`, + ); + } + this.#apiBase = apiBase; + this.#sendTaker = config.sendTaker ?? false; + } + + /** + * Calls the PaladinFi Trust Check API and returns the recommendation plus + * per-factor breakdown for a given token contract. + * + * @param walletProvider - The wallet provider. Used only when + * `sendTaker: true` was passed to the constructor; otherwise unused. + * @param args - The input arguments for the action. + * @returns A JSON-stringified response containing the recommendation, + * per-factor breakdown, and the raw API response. + */ + @CreateAction({ + name: "check_token_risk", + description: ` +This tool fetches a composed risk recommendation for a token contract using the PaladinFi Trust Check API (preview endpoint). + +It takes the following inputs: +- chainId: The chain ID of the network. PaladinFi currently supports Base mainnet (8453) only. +- tokenAddress: The contract address of the token to evaluate. + +Important notes: +- This tool does NOT execute any swap or transaction. It is a decision-only risk gate to call BEFORE composing a swap with another action provider (zeroX, enso, sushi, etc.). +- This in-tree provider calls the free preview endpoint only. The recommendation is prefixed with "sample-" (sample-allow / sample-warn / sample-block) and every factor has real: false. Use the response shape to wire up your risk gate; for live evaluation on real funds, use the external @paladinfi/agentkit-actions npm package which ships the x402-settled paid endpoint client. +- When all upstream sources are temporarily unreachable on the paid endpoint, the API returns recommendation: "warn" (fail-closed, never silent-allow). This in-tree provider's preview path is not subject to that condition because no live sources are queried. +`, + schema: CheckTokenRiskSchema, + }) + async checkTokenRisk( + walletProvider: EvmWalletProvider, + args: z.infer, + ): Promise { + try { + const response = await this.#fetchTrustCheck(walletProvider, args.chainId, args.tokenAddress); + return JSON.stringify({ + success: true, + recommendation: response.trust.recommendation, + version: response.trust.version ?? null, + factors: response.trust.factors, + riskScore: response.trust.risk_score ?? null, + response, + }); + } catch (error) { + return JSON.stringify({ + success: false, + error: `Error fetching token risk: ${error instanceof Error ? error.message : String(error)}`, + }); + } + } + + /** + * Issues the POST request to the PaladinFi Trust Check preview endpoint. + * + * The `taker` field is included in the body only if `sendTaker: true` was + * passed to the constructor AND `walletProvider.getAddress()` succeeds. + * Privacy-default-on: callers must opt-in to share the wallet address. + * + * @param walletProvider - The wallet provider. + * @param chainId - The chain ID to send in the request body. + * @param tokenAddress - The token contract address to evaluate. + * @returns The parsed TrustCheckResponse. + */ + async #fetchTrustCheck( + walletProvider: EvmWalletProvider, + chainId: number, + tokenAddress: string, + ): Promise { + const url = `${this.#apiBase}${PREVIEW_ENDPOINT}`; + + let taker: string | undefined = undefined; + if (this.#sendTaker) { + try { + taker = walletProvider.getAddress(); + } catch { + // walletProvider may not be available / may throw in some harness + // contexts; silently fall back to no-taker. The preview endpoint + // accepts requests with the field omitted. + taker = undefined; + } + } + + const bodyData: { chainId: number; address: string; taker?: string } = { + chainId, + address: tokenAddress, + }; + if (taker) bodyData.taker = taker; + const body = JSON.stringify(bodyData); + + const response = await fetch(url, { + method: "POST", + headers: { "content-type": "application/json" }, + body, + }); + + if (!response.ok) { + // Don't pass server error bodies through to the LLM — PaladinFi error + // responses may contain upstream API tokens / RPC URLs / stack traces. + // Surface only status. + throw new Error(`HTTP ${response.status} ${response.statusText} from PaladinFi Trust API`); + } + + const json = (await response.json()) as TrustCheckResponse; + if (!json || typeof json !== "object" || !("trust" in json) || !json.trust) { + throw new Error("PaladinFi Trust API returned unexpected response shape (missing 'trust' block)"); + } + return json; + } + + /** + * Checks if the PaladinTrust action provider supports the given network. + * PaladinFi currently supports Base mainnet (chainId 8453) only. + * + * @param network - The network to check. + * @returns True if the network is Base mainnet, false otherwise. + */ + supportsNetwork = (network: Network) => + network.protocolFamily === "evm" && + (network.chainId === PALADINFI_BASE_CHAIN_ID.toString() || + network.networkId === "base-mainnet"); +} + +/** + * Creates a new PaladinTrustActionProvider with the provided configuration. + * + * @param config - Optional configuration for the provider. + * @returns A new PaladinTrustActionProvider. + */ +export const paladinTrustActionProvider = (config: PaladinTrustActionProviderConfig = {}) => + new PaladinTrustActionProvider(config); diff --git a/typescript/agentkit/src/action-providers/paladinTrust/schemas.ts b/typescript/agentkit/src/action-providers/paladinTrust/schemas.ts new file mode 100644 index 000000000..c9509574c --- /dev/null +++ b/typescript/agentkit/src/action-providers/paladinTrust/schemas.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; + +/** + * Input schema for `check_token_risk`. Returns a composed-risk recommendation + * for a token contract by querying the PaladinFi Trust Check API (preview + * endpoint). + */ +export const CheckTokenRiskSchema = z + .object({ + chainId: z + .number() + .int() + .positive() + .describe( + "The chain ID of the network where the token lives. PaladinFi currently supports Base mainnet (8453) only.", + ), + tokenAddress: z + .string() + .regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format") + .describe("The contract address of the token to evaluate"), + }) + .describe( + "Evaluate a token contract against PaladinFi's composed risk gate (OFAC SDN, GoPlus, Etherscan source verification, anomaly heuristics) via the free preview endpoint.", + );