veriql-agentkit 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Veriql
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,66 @@
1
- # Temporary Holding Version
1
+ # veriql-agentkit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Veriql checks for [Coinbase AgentKit](https://github.com/coinbase/agentkit) agents that move money.
4
+
5
+ | Action | When the agent should call it | Cost |
6
+ |---|---|---|
7
+ | `check_payee` | Before sending funds to any address. OFAC SDN sanctions, the Drain Watch list of known drainer and scam addresses, and whether Tether or Circle has frozen the address (read from the issuer's own contract). EVM, Solana, Bitcoin, Tron. | $0.005 |
8
+ | `check_signature` | Before signing anything. Decodes approvals, Permit and Permit2, EIP-7702 delegations, transfers and revokes, and checks whoever gains the power. Answers `block`, `warn` or `allow`. An undecodable payload is `block` and is not charged. | $0.005 |
9
+ | `check_denylist` | A free, instant screen against OFAC SDN plus Drain Watch, cached for an hour. | free |
10
+
11
+ Veriql is read-only. It never asks for a key, never signs and never moves funds.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install veriql-agentkit
17
+ ```
18
+
19
+ AgentKit 0.10 itself needs Node 22 or later.
20
+
21
+ ```ts
22
+ import { AgentKit } from "@coinbase/agentkit";
23
+ import { veriqlActionProvider } from "veriql-agentkit";
24
+
25
+ const agentkit = await AgentKit.from({
26
+ walletProvider,
27
+ actionProviders: [veriqlActionProvider()],
28
+ });
29
+ ```
30
+
31
+ ## Paying for calls
32
+
33
+ There are two ways to pay. Pick one:
34
+
35
+ - **x402, no account (the default).** The agent pays each call itself in USDC on Base or Solana, from the wallet AgentKit already holds. A payment option priced above `maxPaymentUsdc` (default `0.01`, env `VERIQL_MAX_PAYMENT_USDC`) is refused before anything is signed.
36
+ - **An API key.** Pass `apiKey` or set `VERIQL_API_KEY`. Calls then spend prepaid credits and the agent never signs a payment. A free key with 250 credits is at <https://veriql.app/api/checkout?pack=free>.
37
+
38
+ ```ts
39
+ veriqlActionProvider({ apiKey: process.env.VERIQL_API_KEY, maxPaymentUsdc: 0.01 });
40
+ ```
41
+
42
+ ## What the answers mean
43
+
44
+ - `no_known_risk` or `allow` means nothing adverse was found when the check ran. It is not a guarantee.
45
+ - `incomplete` means a check could not run. Treat the address as unverified, not as clean.
46
+ - Any error comes back as `{"error": true, ...}` and tells the agent to treat the input as unverified. A failure is never reported as clean.
47
+
48
+ ## Tests
49
+
50
+ ```bash
51
+ npm test # builds with tsc, then runs node --test on the compiled output (Node 22+)
52
+ ```
53
+
54
+ The tests run against `dist/`, not the TypeScript sources. AgentKit's `@CreateAction` decides whether an action receives the wallet by reading the parameter-type metadata that only `tsc` emits (`emitDecoratorMetadata`). A runner built on esbuild would silently register every action as walletless.
55
+
56
+ ## Contributing this upstream
57
+
58
+ `src/veriql/` follows the layout of AgentKit's own `typescript/agentkit/src/action-providers/<name>/` folders: `schemas.ts`, `constants.ts`, `veriqlActionProvider.ts` and `index.ts`. To move it into the AgentKit repo:
59
+
60
+ 1. Import `ActionProvider`, `CreateAction` and the wallet providers relatively (`../actionProvider`, `../actionDecorator`, `../../wallet-providers`) instead of from `@coinbase/agentkit`.
61
+ 2. Drop the `.js` import suffixes.
62
+ 3. Add `export * from "./veriql";` to `action-providers/index.ts`.
63
+
64
+ ## Licence
65
+
66
+ MIT
@@ -0,0 +1 @@
1
+ export * from "./veriql/index.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from "./veriql/index.js";
@@ -0,0 +1,19 @@
1
+ export declare const VERIQL_BASE_URL = "https://veriql.app";
2
+ /**
3
+ * Sent on every request so Veriql can tell which framework a call came
4
+ * through. Kept equal to package.json by a test.
5
+ */
6
+ export declare const USER_AGENT = "veriql-agentkit/0.1.1";
7
+ /**
8
+ * Veriql charges $0.005 per call. The default cap leaves room for a price
9
+ * change without letting a misconfigured endpoint charge an agent real money:
10
+ * any payment option above it is dropped before anything is signed.
11
+ */
12
+ export declare const DEFAULT_MAX_PAYMENT_USDC = 0.01;
13
+ /** USDC has 6 decimals on every network Veriql accepts (Base, Solana). */
14
+ export declare const USDC_DECIMALS = 6;
15
+ /**
16
+ * The free denylist changes once a day; an hour keeps an agent from
17
+ * re-downloading it on every lookup.
18
+ */
19
+ export declare const DENYLIST_TTL_MS: number;
@@ -0,0 +1,19 @@
1
+ export const VERIQL_BASE_URL = "https://veriql.app";
2
+ /**
3
+ * Sent on every request so Veriql can tell which framework a call came
4
+ * through. Kept equal to package.json by a test.
5
+ */
6
+ export const USER_AGENT = "veriql-agentkit/0.1.1";
7
+ /**
8
+ * Veriql charges $0.005 per call. The default cap leaves room for a price
9
+ * change without letting a misconfigured endpoint charge an agent real money:
10
+ * any payment option above it is dropped before anything is signed.
11
+ */
12
+ export const DEFAULT_MAX_PAYMENT_USDC = 0.01;
13
+ /** USDC has 6 decimals on every network Veriql accepts (Base, Solana). */
14
+ export const USDC_DECIMALS = 6;
15
+ /**
16
+ * The free denylist changes once a day; an hour keeps an agent from
17
+ * re-downloading it on every lookup.
18
+ */
19
+ export const DENYLIST_TTL_MS = 60 * 60 * 1000;
@@ -0,0 +1,2 @@
1
+ export * from "./veriqlActionProvider.js";
2
+ export * from "./schemas.js";
@@ -0,0 +1,2 @@
1
+ export * from "./veriqlActionProvider.js";
2
+ export * from "./schemas.js";
@@ -0,0 +1,22 @@
1
+ import { z } from "zod";
2
+ export declare const CheckPayeeSchema: z.ZodObject<{
3
+ address: z.ZodString;
4
+ }, "strict", z.ZodTypeAny, {
5
+ address: string;
6
+ }, {
7
+ address: string;
8
+ }>;
9
+ export declare const CheckSignatureSchema: z.ZodObject<{
10
+ payload: z.ZodString;
11
+ }, "strict", z.ZodTypeAny, {
12
+ payload: string;
13
+ }, {
14
+ payload: string;
15
+ }>;
16
+ export declare const CheckDenylistSchema: z.ZodObject<{
17
+ address: z.ZodString;
18
+ }, "strict", z.ZodTypeAny, {
19
+ address: string;
20
+ }, {
21
+ address: string;
22
+ }>;
@@ -0,0 +1,28 @@
1
+ import { z } from "zod";
2
+ export const CheckPayeeSchema = z
3
+ .object({
4
+ address: z
5
+ .string()
6
+ .min(1)
7
+ .describe("The address you are about to send money to: EVM (0x...), Solana, Bitcoin or Tron."),
8
+ })
9
+ .strict()
10
+ .describe("Input for checking a payment recipient before sending funds");
11
+ export const CheckSignatureSchema = z
12
+ .object({
13
+ payload: z
14
+ .string()
15
+ .min(1)
16
+ .describe("What you were asked to sign: raw transaction calldata (0x...), an EIP-712 typed-data JSON string, or a JSON-RPC signing request as a JSON string."),
17
+ })
18
+ .strict()
19
+ .describe("Input for checking a signing request before signing it");
20
+ export const CheckDenylistSchema = z
21
+ .object({
22
+ address: z
23
+ .string()
24
+ .min(1)
25
+ .describe("The address to look up: EVM (0x...), Solana, Bitcoin or Tron."),
26
+ })
27
+ .strict()
28
+ .describe("Input for a free lookup against the Veriql denylist");
@@ -0,0 +1,130 @@
1
+ import { z } from "zod";
2
+ import { ActionProvider, WalletProvider } from "@coinbase/agentkit";
3
+ import { CheckDenylistSchema, CheckPayeeSchema, CheckSignatureSchema } from "./schemas.js";
4
+ export interface VeriqlActionProviderConfig {
5
+ /**
6
+ * A Veriql API key (`vq_live_...`). With one, calls spend prepaid credits
7
+ * and no payment is ever signed. Falls back to `VERIQL_API_KEY`.
8
+ */
9
+ apiKey?: string;
10
+ /**
11
+ * Without a key, calls are paid per request over x402 from the agent's own
12
+ * wallet. Any payment option above this many USDC is refused before
13
+ * signing. Falls back to `VERIQL_MAX_PAYMENT_USDC`, then 0.01.
14
+ */
15
+ maxPaymentUsdc?: number;
16
+ /** Override for testing against a non-production deployment. */
17
+ baseUrl?: string;
18
+ }
19
+ type Family = "evm" | "solana" | "tron" | "btc";
20
+ /**
21
+ * Which denylist family an address belongs to, by shape alone. Tron is checked
22
+ * before Solana because a 34-character Tron address also passes a base58
23
+ * length check.
24
+ *
25
+ * @param address - The address to classify
26
+ * @returns The address family, or null if the shape is not recognised
27
+ */
28
+ export declare function familyOf(address: string): Family | null;
29
+ /**
30
+ * Converts a whole-USDC cap to atomic units without floating-point drift:
31
+ * `0.29 * 1e6` is `289999.99999999994`, which truncates to the wrong amount.
32
+ *
33
+ * @param usdc - The amount in whole USDC
34
+ * @returns The amount in atomic units (6 decimals)
35
+ */
36
+ export declare function usdcToAtomic(usdc: number): bigint;
37
+ /**
38
+ * Drops every payment option priced above `capAtomic`. x402 v2 carries the
39
+ * price as `amount`, v1 as `maxAmountRequired`; an option carrying neither is
40
+ * dropped too, because a price we cannot read is not one we can agree to.
41
+ *
42
+ * @param capAtomic - The highest acceptable price, in atomic USDC units
43
+ * @returns An x402 payment policy that filters the offered requirements
44
+ */
45
+ export declare function priceCapPolicy(capAtomic: bigint): <T extends object>(_version: number, reqs: T[]) => T[];
46
+ /**
47
+ * Veriql checks for agents that move money.
48
+ *
49
+ * - `check_payee` before sending funds: OFAC SDN, the Drain Watch scam list,
50
+ * and whether Tether or Circle has frozen the address (read from the issuer's
51
+ * own contract).
52
+ * - `check_signature` before signing: decodes approvals, Permit/Permit2,
53
+ * EIP-7702 delegations, transfers and revokes, and checks who gains the power.
54
+ * - `check_denylist`: a free lookup against the same lists, no payment.
55
+ *
56
+ * Read-only: Veriql never asks for a key, never signs and never moves funds.
57
+ */
58
+ export declare class VeriqlActionProvider extends ActionProvider<WalletProvider> {
59
+ private readonly apiKey?;
60
+ private readonly capAtomic;
61
+ private readonly baseUrl;
62
+ private denylist;
63
+ /**
64
+ * Creates a new VeriqlActionProvider.
65
+ *
66
+ * @param config - Optional API key, payment cap and base URL
67
+ */
68
+ constructor(config?: VeriqlActionProviderConfig);
69
+ /**
70
+ * Checks a payment recipient before funds are sent.
71
+ *
72
+ * @param walletProvider - The wallet that pays for the call over x402
73
+ * @param args - The recipient address
74
+ * @returns The verdict as a JSON string, or an error telling the agent to treat it as unverified
75
+ */
76
+ checkPayee(walletProvider: WalletProvider, args: z.infer<typeof CheckPayeeSchema>): Promise<string>;
77
+ /**
78
+ * Checks a signing request before it is signed.
79
+ *
80
+ * @param walletProvider - The wallet that pays for the call over x402
81
+ * @param args - The payload the agent was asked to sign
82
+ * @returns The decision as a JSON string, or an error telling the agent to treat it as unverified
83
+ */
84
+ checkSignature(walletProvider: WalletProvider, args: z.infer<typeof CheckSignatureSchema>): Promise<string>;
85
+ /**
86
+ * Looks an address up in the free Veriql denylist.
87
+ *
88
+ * @param args - The address to look up
89
+ * @returns Whether the address is listed, as a JSON string
90
+ */
91
+ checkDenylist(args: z.infer<typeof CheckDenylistSchema>): Promise<string>;
92
+ /**
93
+ * Veriql is a read-only API, so every network is supported.
94
+ *
95
+ * @returns Always true
96
+ */
97
+ supportsNetwork: () => boolean;
98
+ /**
99
+ * Loads one family of the denylist, cached for an hour. A failed refresh
100
+ * keeps the previous copy rather than reading as an empty list.
101
+ *
102
+ * @param family - The address family to load
103
+ * @returns The listed addresses, EVM ones lowercased
104
+ */
105
+ private loadDenylist;
106
+ /**
107
+ * Calls a paid Veriql endpoint with the API key, or over x402 from the wallet.
108
+ *
109
+ * @param walletProvider - The wallet that pays when no API key is set
110
+ * @param url - The endpoint URL
111
+ * @param init - The request options
112
+ * @returns The response body as a JSON string, or an error object
113
+ */
114
+ private call;
115
+ /**
116
+ * Creates an x402 client for the wallet, capped at the configured price.
117
+ *
118
+ * @param walletProvider - The wallet that signs the payment
119
+ * @returns The client, or null if the wallet cannot pay over x402
120
+ */
121
+ private createX402Client;
122
+ }
123
+ /**
124
+ * Creates a VeriqlActionProvider.
125
+ *
126
+ * @param config - Optional API key, payment cap and base URL
127
+ * @returns A new VeriqlActionProvider
128
+ */
129
+ export declare const veriqlActionProvider: (config?: VeriqlActionProviderConfig) => VeriqlActionProvider;
130
+ export {};
@@ -0,0 +1,359 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { z } from "zod";
11
+ import { ActionProvider, CreateAction, EvmWalletProvider, SvmWalletProvider, WalletProvider, } from "@coinbase/agentkit";
12
+ import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
13
+ import { registerExactEvmScheme } from "@x402/evm/exact/client";
14
+ import { registerExactSvmScheme } from "@x402/svm/exact/client";
15
+ import { CheckDenylistSchema, CheckPayeeSchema, CheckSignatureSchema } from "./schemas.js";
16
+ import { DEFAULT_MAX_PAYMENT_USDC, DENYLIST_TTL_MS, USDC_DECIMALS, USER_AGENT, VERIQL_BASE_URL, } from "./constants.js";
17
+ /**
18
+ * The global fetch, with our user-agent added unless the caller set one.
19
+ * Resolved per call so a fetch swapped in later (tests, polyfills) is used.
20
+ *
21
+ * @param input - The URL or Request
22
+ * @param init - The request options
23
+ * @returns The response
24
+ */
25
+ const veriqlFetch = (input, init) => {
26
+ if (input instanceof Request) {
27
+ const headers = new Headers(input.headers);
28
+ if (!headers.has("user-agent"))
29
+ headers.set("user-agent", USER_AGENT);
30
+ return fetch(new Request(input, { ...init, headers }));
31
+ }
32
+ const headers = new Headers(init?.headers);
33
+ if (!headers.has("user-agent"))
34
+ headers.set("user-agent", USER_AGENT);
35
+ return fetch(input, { ...init, headers });
36
+ };
37
+ /**
38
+ * Which denylist family an address belongs to, by shape alone. Tron is checked
39
+ * before Solana because a 34-character Tron address also passes a base58
40
+ * length check.
41
+ *
42
+ * @param address - The address to classify
43
+ * @returns The address family, or null if the shape is not recognised
44
+ */
45
+ export function familyOf(address) {
46
+ const a = address.trim();
47
+ if (/^0x[0-9a-fA-F]{40}$/.test(a))
48
+ return "evm";
49
+ if (/^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(a))
50
+ return "tron";
51
+ if (/^(bc1[02-9ac-hj-np-z]{11,71}|[13][1-9A-HJ-NP-Za-km-z]{25,34})$/.test(a))
52
+ return "btc";
53
+ if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(a))
54
+ return "solana";
55
+ return null;
56
+ }
57
+ /**
58
+ * Converts a whole-USDC cap to atomic units without floating-point drift:
59
+ * `0.29 * 1e6` is `289999.99999999994`, which truncates to the wrong amount.
60
+ *
61
+ * @param usdc - The amount in whole USDC
62
+ * @returns The amount in atomic units (6 decimals)
63
+ */
64
+ export function usdcToAtomic(usdc) {
65
+ return BigInt(Math.round(usdc * 10 ** USDC_DECIMALS));
66
+ }
67
+ /**
68
+ * Drops every payment option priced above `capAtomic`. x402 v2 carries the
69
+ * price as `amount`, v1 as `maxAmountRequired`; an option carrying neither is
70
+ * dropped too, because a price we cannot read is not one we can agree to.
71
+ *
72
+ * @param capAtomic - The highest acceptable price, in atomic USDC units
73
+ * @returns An x402 payment policy that filters the offered requirements
74
+ */
75
+ export function priceCapPolicy(capAtomic) {
76
+ return (_version, reqs) => reqs.filter(r => {
77
+ const rec = r;
78
+ const raw = rec.amount ?? rec.maxAmountRequired;
79
+ if (typeof raw !== "string" && typeof raw !== "number")
80
+ return false;
81
+ try {
82
+ return BigInt(raw) <= capAtomic;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ });
88
+ }
89
+ /**
90
+ * Veriql checks for agents that move money.
91
+ *
92
+ * - `check_payee` before sending funds: OFAC SDN, the Drain Watch scam list,
93
+ * and whether Tether or Circle has frozen the address (read from the issuer's
94
+ * own contract).
95
+ * - `check_signature` before signing: decodes approvals, Permit/Permit2,
96
+ * EIP-7702 delegations, transfers and revokes, and checks who gains the power.
97
+ * - `check_denylist`: a free lookup against the same lists, no payment.
98
+ *
99
+ * Read-only: Veriql never asks for a key, never signs and never moves funds.
100
+ */
101
+ export class VeriqlActionProvider extends ActionProvider {
102
+ apiKey;
103
+ capAtomic;
104
+ baseUrl;
105
+ denylist = new Map();
106
+ /**
107
+ * Creates a new VeriqlActionProvider.
108
+ *
109
+ * @param config - Optional API key, payment cap and base URL
110
+ */
111
+ constructor(config = {}) {
112
+ super("veriql", []);
113
+ this.apiKey = config.apiKey ?? process.env.VERIQL_API_KEY ?? undefined;
114
+ const envCap = Number(process.env.VERIQL_MAX_PAYMENT_USDC);
115
+ const cap = config.maxPaymentUsdc ?? (Number.isFinite(envCap) && envCap > 0 ? envCap : DEFAULT_MAX_PAYMENT_USDC);
116
+ this.capAtomic = usdcToAtomic(cap);
117
+ this.baseUrl = (config.baseUrl ?? VERIQL_BASE_URL).replace(/\/+$/, "");
118
+ }
119
+ /**
120
+ * Checks a payment recipient before funds are sent.
121
+ *
122
+ * @param walletProvider - The wallet that pays for the call over x402
123
+ * @param args - The recipient address
124
+ * @returns The verdict as a JSON string, or an error telling the agent to treat it as unverified
125
+ */
126
+ async checkPayee(walletProvider, args) {
127
+ const url = `${this.baseUrl}/api/v1/payee?address=${encodeURIComponent(args.address.trim())}`;
128
+ return this.call(walletProvider, url, { method: "GET" });
129
+ }
130
+ /**
131
+ * Checks a signing request before it is signed.
132
+ *
133
+ * @param walletProvider - The wallet that pays for the call over x402
134
+ * @param args - The payload the agent was asked to sign
135
+ * @returns The decision as a JSON string, or an error telling the agent to treat it as unverified
136
+ */
137
+ async checkSignature(walletProvider, args) {
138
+ // Typed data arrives as a JSON string from a model; send it as the object
139
+ // it describes so the server decodes the structure, not a quoted string.
140
+ let payload = args.payload.trim();
141
+ if (typeof payload === "string" && /^[[{]/.test(payload)) {
142
+ try {
143
+ payload = JSON.parse(payload);
144
+ }
145
+ catch {
146
+ // Not JSON after all: the server reports it as undecodable (block).
147
+ }
148
+ }
149
+ return this.call(walletProvider, `${this.baseUrl}/api/v1/signature`, {
150
+ method: "POST",
151
+ headers: { "content-type": "application/json" },
152
+ body: JSON.stringify({ payload }),
153
+ });
154
+ }
155
+ /**
156
+ * Looks an address up in the free Veriql denylist.
157
+ *
158
+ * @param args - The address to look up
159
+ * @returns Whether the address is listed, as a JSON string
160
+ */
161
+ async checkDenylist(args) {
162
+ const address = args.address.trim();
163
+ const family = familyOf(address);
164
+ if (!family) {
165
+ return JSON.stringify({ error: true, message: "Not a recognised EVM, Solana, Bitcoin or Tron address." });
166
+ }
167
+ try {
168
+ const values = await this.loadDenylist(family);
169
+ const needle = family === "evm" ? address.toLowerCase() : address;
170
+ const listed = values.has(needle);
171
+ return JSON.stringify({
172
+ address,
173
+ family,
174
+ listed,
175
+ verdict: listed ? "do_not_pay" : "not_listed",
176
+ note: listed
177
+ ? "Listed on OFAC SDN or Drain Watch. Do not send funds. Run check_payee for the reasons."
178
+ : "No listing found. This is not a statement that the address is safe.",
179
+ });
180
+ }
181
+ catch (error) {
182
+ return JSON.stringify({
183
+ error: true,
184
+ message: `Denylist unavailable: ${error instanceof Error ? error.message : String(error)}. Treat the address as unverified.`,
185
+ });
186
+ }
187
+ }
188
+ /**
189
+ * Veriql is a read-only API, so every network is supported.
190
+ *
191
+ * @returns Always true
192
+ */
193
+ supportsNetwork = () => true;
194
+ /**
195
+ * Loads one family of the denylist, cached for an hour. A failed refresh
196
+ * keeps the previous copy rather than reading as an empty list.
197
+ *
198
+ * @param family - The address family to load
199
+ * @returns The listed addresses, EVM ones lowercased
200
+ */
201
+ async loadDenylist(family) {
202
+ const hit = this.denylist.get(family);
203
+ if (hit && Date.now() - hit.at < DENYLIST_TTL_MS)
204
+ return hit.values;
205
+ const res = await veriqlFetch(`${this.baseUrl}/api/denylist?family=${family}`);
206
+ if (!res.ok) {
207
+ // The feed answers 503 rather than an empty list; keep a stale copy.
208
+ if (hit)
209
+ return hit.values;
210
+ throw new Error(`HTTP ${res.status}`);
211
+ }
212
+ const body = (await res.json());
213
+ if (!Array.isArray(body.items)) {
214
+ if (hit)
215
+ return hit.values;
216
+ throw new Error("unexpected response");
217
+ }
218
+ const values = new Set();
219
+ for (const item of body.items) {
220
+ if (typeof item?.value !== "string")
221
+ continue;
222
+ values.add(family === "evm" ? item.value.toLowerCase() : item.value);
223
+ }
224
+ this.denylist.set(family, { at: Date.now(), values });
225
+ return values;
226
+ }
227
+ /**
228
+ * Calls a paid Veriql endpoint with the API key, or over x402 from the wallet.
229
+ *
230
+ * @param walletProvider - The wallet that pays when no API key is set
231
+ * @param url - The endpoint URL
232
+ * @param init - The request options
233
+ * @returns The response body as a JSON string, or an error object
234
+ */
235
+ async call(walletProvider, url, init) {
236
+ try {
237
+ let res;
238
+ if (this.apiKey) {
239
+ res = await veriqlFetch(url, { ...init, headers: { ...init.headers, "x-api-key": this.apiKey } });
240
+ }
241
+ else {
242
+ const client = await this.createX402Client(walletProvider);
243
+ if (!client) {
244
+ return JSON.stringify({
245
+ error: true,
246
+ message: "This wallet cannot pay over x402 (needs an EVM or Solana wallet holding USDC on Base or Solana). Set VERIQL_API_KEY instead: a free key is at https://veriql.app/api/checkout?pack=free",
247
+ });
248
+ }
249
+ res = await wrapFetchWithPayment(veriqlFetch, client)(url, init);
250
+ }
251
+ const text = await res.text();
252
+ let data = text;
253
+ try {
254
+ data = JSON.parse(text);
255
+ }
256
+ catch {
257
+ // Leave non-JSON bodies as text.
258
+ }
259
+ if (!res.ok) {
260
+ return JSON.stringify({
261
+ error: true,
262
+ status: res.status,
263
+ message: res.status === 402
264
+ ? `Payment was not completed. Veriql's price may be above this agent's cap (${Number(this.capAtomic) / 10 ** USDC_DECIMALS} USDC), or the wallet lacks USDC on Base or Solana.`
265
+ : `Veriql answered ${res.status}. Treat the address or payload as unverified.`,
266
+ details: data,
267
+ });
268
+ }
269
+ const settlement = res.headers.get("payment-response") ?? res.headers.get("x-payment-response");
270
+ return JSON.stringify(settlement ? { ...data, paid: true } : data);
271
+ }
272
+ catch (error) {
273
+ return JSON.stringify({
274
+ error: true,
275
+ message: `Veriql request failed: ${error instanceof Error ? error.message : String(error)}. Treat the address or payload as unverified.`,
276
+ });
277
+ }
278
+ }
279
+ /**
280
+ * Creates an x402 client for the wallet, capped at the configured price.
281
+ *
282
+ * @param walletProvider - The wallet that signs the payment
283
+ * @returns The client, or null if the wallet cannot pay over x402
284
+ */
285
+ async createX402Client(walletProvider) {
286
+ const client = new x402Client();
287
+ if (walletProvider instanceof EvmWalletProvider) {
288
+ registerExactEvmScheme(client, { signer: walletProvider.toSigner() });
289
+ }
290
+ else if (walletProvider instanceof SvmWalletProvider) {
291
+ registerExactSvmScheme(client, { signer: await walletProvider.toSigner() });
292
+ }
293
+ else {
294
+ return null;
295
+ }
296
+ client.registerPolicy(priceCapPolicy(this.capAtomic));
297
+ return client;
298
+ }
299
+ }
300
+ __decorate([
301
+ CreateAction({
302
+ name: "check_payee",
303
+ description: `Check an address BEFORE sending it any money (a transfer, a payment, a payout).
304
+ Checks the US Treasury OFAC sanctions list, the Veriql Drain Watch list of known drainer and scam
305
+ addresses, and whether Tether or Circle has frozen the address (read from the issuer's contract).
306
+ Works for EVM, Solana, Bitcoin and Tron addresses.
307
+
308
+ Returns a verdict:
309
+ - do_not_pay: do NOT send funds. Tell the user why, using the reasons returned.
310
+ - no_known_risk: nothing adverse was found. This is not a guarantee the recipient is safe.
311
+ - incomplete: some checks could not run. Treat it as unverified, not as clean.
312
+
313
+ Costs $0.005 in USDC from this wallet (or one credit with an API key).`,
314
+ schema: CheckPayeeSchema,
315
+ }),
316
+ __metadata("design:type", Function),
317
+ __metadata("design:paramtypes", [WalletProvider, void 0]),
318
+ __metadata("design:returntype", Promise)
319
+ ], VeriqlActionProvider.prototype, "checkPayee", null);
320
+ __decorate([
321
+ CreateAction({
322
+ name: "check_signature",
323
+ description: `Check a signing request BEFORE signing it. Pass exactly what you were asked to sign:
324
+ raw transaction calldata (0x...), an EIP-712 typed-data JSON string, or a JSON-RPC signing request.
325
+ Decodes token approvals, Permit and Permit2, EIP-7702 delegations, transfers and revokes, and checks
326
+ whoever gains the power against OFAC and Drain Watch.
327
+
328
+ Returns block, warn or allow, with the decoded action in plain words.
329
+ - block: do NOT sign. A payload that cannot be decoded is also block, and is not charged.
330
+ - warn: explain the risk to the user and get explicit confirmation before signing.
331
+ - allow: no known risk was found in what this payload does.
332
+
333
+ Costs $0.005 in USDC from this wallet (or one credit with an API key).`,
334
+ schema: CheckSignatureSchema,
335
+ }),
336
+ __metadata("design:type", Function),
337
+ __metadata("design:paramtypes", [WalletProvider, void 0]),
338
+ __metadata("design:returntype", Promise)
339
+ ], VeriqlActionProvider.prototype, "checkSignature", null);
340
+ __decorate([
341
+ CreateAction({
342
+ name: "check_denylist",
343
+ description: `Free, instant lookup of an address against the Veriql denylist (OFAC SDN sanctions plus
344
+ Drain Watch known drainer and scam addresses), refreshed daily. No payment.
345
+ Use it for a quick screen. Before actually sending funds prefer check_payee, which also reads
346
+ Tether and Circle freezes. An address NOT on the list means no listing was found, not that it is safe.`,
347
+ schema: CheckDenylistSchema,
348
+ }),
349
+ __metadata("design:type", Function),
350
+ __metadata("design:paramtypes", [void 0]),
351
+ __metadata("design:returntype", Promise)
352
+ ], VeriqlActionProvider.prototype, "checkDenylist", null);
353
+ /**
354
+ * Creates a VeriqlActionProvider.
355
+ *
356
+ * @param config - Optional API key, payment cap and base URL
357
+ * @returns A new VeriqlActionProvider
358
+ */
359
+ export const veriqlActionProvider = (config = {}) => new VeriqlActionProvider(config);
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "veriql-agentkit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Veriql for Coinbase AgentKit: check a recipient (OFAC, Drain Watch, Tether/Circle freeze) before an agent pays, decode a signing request before it signs, and load a free denylist for wallet policies.",
5
+ "keywords": [
6
+ "agentkit",
7
+ "coinbase",
8
+ "x402",
9
+ "ai-agents",
10
+ "sanctions",
11
+ "ofac",
12
+ "wallet-security",
13
+ "signature",
14
+ "permit2",
15
+ "eip-7702",
16
+ "solana",
17
+ "base"
18
+ ],
19
+ "homepage": "https://veriql.app/api",
20
+ "license": "MIT",
21
+ "type": "module",
22
+ "main": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "engines": {
36
+ "node": ">=22"
37
+ },
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.json",
40
+ "test": "npm run build && node --test \"test/*.test.mjs\"",
41
+ "prepublishOnly": "npm run build"
42
+ },
43
+ "peerDependencies": {
44
+ "@coinbase/agentkit": ">=0.10.0"
45
+ },
46
+ "dependencies": {
47
+ "@x402/evm": "^2.0.0",
48
+ "@x402/fetch": "^2.0.0",
49
+ "@x402/svm": "^2.0.0",
50
+ "zod": "^3.23.8"
51
+ },
52
+ "devDependencies": {
53
+ "@coinbase/agentkit": "0.10.4",
54
+ "typescript": "^5.6.0"
55
+ }
56
+ }