veriql-agentkit 0.0.0-stage → 0.1.0
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 +21 -0
- package/README.md +65 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/veriql/constants.d.ts +14 -0
- package/dist/veriql/constants.js +14 -0
- package/dist/veriql/index.d.ts +2 -0
- package/dist/veriql/index.js +2 -0
- package/dist/veriql/schemas.d.ts +22 -0
- package/dist/veriql/schemas.js +28 -0
- package/dist/veriql/veriqlActionProvider.d.ts +130 -0
- package/dist/veriql/veriqlActionProvider.js +339 -0
- package/package.json +54 -4
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
|
-
#
|
|
1
|
+
# veriql-agentkit
|
|
2
2
|
|
|
3
|
-
|
|
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
|
package/dist/index.d.ts
ADDED
|
@@ -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,14 @@
|
|
|
1
|
+
export declare const VERIQL_BASE_URL = "https://veriql.app";
|
|
2
|
+
/**
|
|
3
|
+
* Veriql charges $0.005 per call. The default cap leaves room for a price
|
|
4
|
+
* change without letting a misconfigured endpoint charge an agent real money:
|
|
5
|
+
* any payment option above it is dropped before anything is signed.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_MAX_PAYMENT_USDC = 0.01;
|
|
8
|
+
/** USDC has 6 decimals on every network Veriql accepts (Base, Solana). */
|
|
9
|
+
export declare const USDC_DECIMALS = 6;
|
|
10
|
+
/**
|
|
11
|
+
* The free denylist changes once a day; an hour keeps an agent from
|
|
12
|
+
* re-downloading it on every lookup.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DENYLIST_TTL_MS: number;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const VERIQL_BASE_URL = "https://veriql.app";
|
|
2
|
+
/**
|
|
3
|
+
* Veriql charges $0.005 per call. The default cap leaves room for a price
|
|
4
|
+
* change without letting a misconfigured endpoint charge an agent real money:
|
|
5
|
+
* any payment option above it is dropped before anything is signed.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_MAX_PAYMENT_USDC = 0.01;
|
|
8
|
+
/** USDC has 6 decimals on every network Veriql accepts (Base, Solana). */
|
|
9
|
+
export const USDC_DECIMALS = 6;
|
|
10
|
+
/**
|
|
11
|
+
* The free denylist changes once a day; an hour keeps an agent from
|
|
12
|
+
* re-downloading it on every lookup.
|
|
13
|
+
*/
|
|
14
|
+
export const DENYLIST_TTL_MS = 60 * 60 * 1000;
|
|
@@ -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,339 @@
|
|
|
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, VERIQL_BASE_URL, } from "./constants.js";
|
|
17
|
+
/**
|
|
18
|
+
* Which denylist family an address belongs to, by shape alone. Tron is checked
|
|
19
|
+
* before Solana because a 34-character Tron address also passes a base58
|
|
20
|
+
* length check.
|
|
21
|
+
*
|
|
22
|
+
* @param address - The address to classify
|
|
23
|
+
* @returns The address family, or null if the shape is not recognised
|
|
24
|
+
*/
|
|
25
|
+
export function familyOf(address) {
|
|
26
|
+
const a = address.trim();
|
|
27
|
+
if (/^0x[0-9a-fA-F]{40}$/.test(a))
|
|
28
|
+
return "evm";
|
|
29
|
+
if (/^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(a))
|
|
30
|
+
return "tron";
|
|
31
|
+
if (/^(bc1[02-9ac-hj-np-z]{11,71}|[13][1-9A-HJ-NP-Za-km-z]{25,34})$/.test(a))
|
|
32
|
+
return "btc";
|
|
33
|
+
if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(a))
|
|
34
|
+
return "solana";
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Converts a whole-USDC cap to atomic units without floating-point drift:
|
|
39
|
+
* `0.29 * 1e6` is `289999.99999999994`, which truncates to the wrong amount.
|
|
40
|
+
*
|
|
41
|
+
* @param usdc - The amount in whole USDC
|
|
42
|
+
* @returns The amount in atomic units (6 decimals)
|
|
43
|
+
*/
|
|
44
|
+
export function usdcToAtomic(usdc) {
|
|
45
|
+
return BigInt(Math.round(usdc * 10 ** USDC_DECIMALS));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Drops every payment option priced above `capAtomic`. x402 v2 carries the
|
|
49
|
+
* price as `amount`, v1 as `maxAmountRequired`; an option carrying neither is
|
|
50
|
+
* dropped too, because a price we cannot read is not one we can agree to.
|
|
51
|
+
*
|
|
52
|
+
* @param capAtomic - The highest acceptable price, in atomic USDC units
|
|
53
|
+
* @returns An x402 payment policy that filters the offered requirements
|
|
54
|
+
*/
|
|
55
|
+
export function priceCapPolicy(capAtomic) {
|
|
56
|
+
return (_version, reqs) => reqs.filter(r => {
|
|
57
|
+
const rec = r;
|
|
58
|
+
const raw = rec.amount ?? rec.maxAmountRequired;
|
|
59
|
+
if (typeof raw !== "string" && typeof raw !== "number")
|
|
60
|
+
return false;
|
|
61
|
+
try {
|
|
62
|
+
return BigInt(raw) <= capAtomic;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Veriql checks for agents that move money.
|
|
71
|
+
*
|
|
72
|
+
* - `check_payee` before sending funds: OFAC SDN, the Drain Watch scam list,
|
|
73
|
+
* and whether Tether or Circle has frozen the address (read from the issuer's
|
|
74
|
+
* own contract).
|
|
75
|
+
* - `check_signature` before signing: decodes approvals, Permit/Permit2,
|
|
76
|
+
* EIP-7702 delegations, transfers and revokes, and checks who gains the power.
|
|
77
|
+
* - `check_denylist`: a free lookup against the same lists, no payment.
|
|
78
|
+
*
|
|
79
|
+
* Read-only: Veriql never asks for a key, never signs and never moves funds.
|
|
80
|
+
*/
|
|
81
|
+
export class VeriqlActionProvider extends ActionProvider {
|
|
82
|
+
apiKey;
|
|
83
|
+
capAtomic;
|
|
84
|
+
baseUrl;
|
|
85
|
+
denylist = new Map();
|
|
86
|
+
/**
|
|
87
|
+
* Creates a new VeriqlActionProvider.
|
|
88
|
+
*
|
|
89
|
+
* @param config - Optional API key, payment cap and base URL
|
|
90
|
+
*/
|
|
91
|
+
constructor(config = {}) {
|
|
92
|
+
super("veriql", []);
|
|
93
|
+
this.apiKey = config.apiKey ?? process.env.VERIQL_API_KEY ?? undefined;
|
|
94
|
+
const envCap = Number(process.env.VERIQL_MAX_PAYMENT_USDC);
|
|
95
|
+
const cap = config.maxPaymentUsdc ?? (Number.isFinite(envCap) && envCap > 0 ? envCap : DEFAULT_MAX_PAYMENT_USDC);
|
|
96
|
+
this.capAtomic = usdcToAtomic(cap);
|
|
97
|
+
this.baseUrl = (config.baseUrl ?? VERIQL_BASE_URL).replace(/\/+$/, "");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Checks a payment recipient before funds are sent.
|
|
101
|
+
*
|
|
102
|
+
* @param walletProvider - The wallet that pays for the call over x402
|
|
103
|
+
* @param args - The recipient address
|
|
104
|
+
* @returns The verdict as a JSON string, or an error telling the agent to treat it as unverified
|
|
105
|
+
*/
|
|
106
|
+
async checkPayee(walletProvider, args) {
|
|
107
|
+
const url = `${this.baseUrl}/api/v1/payee?address=${encodeURIComponent(args.address.trim())}`;
|
|
108
|
+
return this.call(walletProvider, url, { method: "GET" });
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Checks a signing request before it is signed.
|
|
112
|
+
*
|
|
113
|
+
* @param walletProvider - The wallet that pays for the call over x402
|
|
114
|
+
* @param args - The payload the agent was asked to sign
|
|
115
|
+
* @returns The decision as a JSON string, or an error telling the agent to treat it as unverified
|
|
116
|
+
*/
|
|
117
|
+
async checkSignature(walletProvider, args) {
|
|
118
|
+
// Typed data arrives as a JSON string from a model; send it as the object
|
|
119
|
+
// it describes so the server decodes the structure, not a quoted string.
|
|
120
|
+
let payload = args.payload.trim();
|
|
121
|
+
if (typeof payload === "string" && /^[[{]/.test(payload)) {
|
|
122
|
+
try {
|
|
123
|
+
payload = JSON.parse(payload);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Not JSON after all: the server reports it as undecodable (block).
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return this.call(walletProvider, `${this.baseUrl}/api/v1/signature`, {
|
|
130
|
+
method: "POST",
|
|
131
|
+
headers: { "content-type": "application/json" },
|
|
132
|
+
body: JSON.stringify({ payload }),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Looks an address up in the free Veriql denylist.
|
|
137
|
+
*
|
|
138
|
+
* @param args - The address to look up
|
|
139
|
+
* @returns Whether the address is listed, as a JSON string
|
|
140
|
+
*/
|
|
141
|
+
async checkDenylist(args) {
|
|
142
|
+
const address = args.address.trim();
|
|
143
|
+
const family = familyOf(address);
|
|
144
|
+
if (!family) {
|
|
145
|
+
return JSON.stringify({ error: true, message: "Not a recognised EVM, Solana, Bitcoin or Tron address." });
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const values = await this.loadDenylist(family);
|
|
149
|
+
const needle = family === "evm" ? address.toLowerCase() : address;
|
|
150
|
+
const listed = values.has(needle);
|
|
151
|
+
return JSON.stringify({
|
|
152
|
+
address,
|
|
153
|
+
family,
|
|
154
|
+
listed,
|
|
155
|
+
verdict: listed ? "do_not_pay" : "not_listed",
|
|
156
|
+
note: listed
|
|
157
|
+
? "Listed on OFAC SDN or Drain Watch. Do not send funds. Run check_payee for the reasons."
|
|
158
|
+
: "No listing found. This is not a statement that the address is safe.",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
return JSON.stringify({
|
|
163
|
+
error: true,
|
|
164
|
+
message: `Denylist unavailable: ${error instanceof Error ? error.message : String(error)}. Treat the address as unverified.`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Veriql is a read-only API, so every network is supported.
|
|
170
|
+
*
|
|
171
|
+
* @returns Always true
|
|
172
|
+
*/
|
|
173
|
+
supportsNetwork = () => true;
|
|
174
|
+
/**
|
|
175
|
+
* Loads one family of the denylist, cached for an hour. A failed refresh
|
|
176
|
+
* keeps the previous copy rather than reading as an empty list.
|
|
177
|
+
*
|
|
178
|
+
* @param family - The address family to load
|
|
179
|
+
* @returns The listed addresses, EVM ones lowercased
|
|
180
|
+
*/
|
|
181
|
+
async loadDenylist(family) {
|
|
182
|
+
const hit = this.denylist.get(family);
|
|
183
|
+
if (hit && Date.now() - hit.at < DENYLIST_TTL_MS)
|
|
184
|
+
return hit.values;
|
|
185
|
+
const res = await fetch(`${this.baseUrl}/api/denylist?family=${family}`);
|
|
186
|
+
if (!res.ok) {
|
|
187
|
+
// The feed answers 503 rather than an empty list; keep a stale copy.
|
|
188
|
+
if (hit)
|
|
189
|
+
return hit.values;
|
|
190
|
+
throw new Error(`HTTP ${res.status}`);
|
|
191
|
+
}
|
|
192
|
+
const body = (await res.json());
|
|
193
|
+
if (!Array.isArray(body.items)) {
|
|
194
|
+
if (hit)
|
|
195
|
+
return hit.values;
|
|
196
|
+
throw new Error("unexpected response");
|
|
197
|
+
}
|
|
198
|
+
const values = new Set();
|
|
199
|
+
for (const item of body.items) {
|
|
200
|
+
if (typeof item?.value !== "string")
|
|
201
|
+
continue;
|
|
202
|
+
values.add(family === "evm" ? item.value.toLowerCase() : item.value);
|
|
203
|
+
}
|
|
204
|
+
this.denylist.set(family, { at: Date.now(), values });
|
|
205
|
+
return values;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Calls a paid Veriql endpoint with the API key, or over x402 from the wallet.
|
|
209
|
+
*
|
|
210
|
+
* @param walletProvider - The wallet that pays when no API key is set
|
|
211
|
+
* @param url - The endpoint URL
|
|
212
|
+
* @param init - The request options
|
|
213
|
+
* @returns The response body as a JSON string, or an error object
|
|
214
|
+
*/
|
|
215
|
+
async call(walletProvider, url, init) {
|
|
216
|
+
try {
|
|
217
|
+
let res;
|
|
218
|
+
if (this.apiKey) {
|
|
219
|
+
res = await fetch(url, { ...init, headers: { ...init.headers, "x-api-key": this.apiKey } });
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const client = await this.createX402Client(walletProvider);
|
|
223
|
+
if (!client) {
|
|
224
|
+
return JSON.stringify({
|
|
225
|
+
error: true,
|
|
226
|
+
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",
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
res = await wrapFetchWithPayment(fetch, client)(url, init);
|
|
230
|
+
}
|
|
231
|
+
const text = await res.text();
|
|
232
|
+
let data = text;
|
|
233
|
+
try {
|
|
234
|
+
data = JSON.parse(text);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Leave non-JSON bodies as text.
|
|
238
|
+
}
|
|
239
|
+
if (!res.ok) {
|
|
240
|
+
return JSON.stringify({
|
|
241
|
+
error: true,
|
|
242
|
+
status: res.status,
|
|
243
|
+
message: res.status === 402
|
|
244
|
+
? `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.`
|
|
245
|
+
: `Veriql answered ${res.status}. Treat the address or payload as unverified.`,
|
|
246
|
+
details: data,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
const settlement = res.headers.get("payment-response") ?? res.headers.get("x-payment-response");
|
|
250
|
+
return JSON.stringify(settlement ? { ...data, paid: true } : data);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
return JSON.stringify({
|
|
254
|
+
error: true,
|
|
255
|
+
message: `Veriql request failed: ${error instanceof Error ? error.message : String(error)}. Treat the address or payload as unverified.`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Creates an x402 client for the wallet, capped at the configured price.
|
|
261
|
+
*
|
|
262
|
+
* @param walletProvider - The wallet that signs the payment
|
|
263
|
+
* @returns The client, or null if the wallet cannot pay over x402
|
|
264
|
+
*/
|
|
265
|
+
async createX402Client(walletProvider) {
|
|
266
|
+
const client = new x402Client();
|
|
267
|
+
if (walletProvider instanceof EvmWalletProvider) {
|
|
268
|
+
registerExactEvmScheme(client, { signer: walletProvider.toSigner() });
|
|
269
|
+
}
|
|
270
|
+
else if (walletProvider instanceof SvmWalletProvider) {
|
|
271
|
+
registerExactSvmScheme(client, { signer: await walletProvider.toSigner() });
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
client.registerPolicy(priceCapPolicy(this.capAtomic));
|
|
277
|
+
return client;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
__decorate([
|
|
281
|
+
CreateAction({
|
|
282
|
+
name: "check_payee",
|
|
283
|
+
description: `Check an address BEFORE sending it any money (a transfer, a payment, a payout).
|
|
284
|
+
Checks the US Treasury OFAC sanctions list, the Veriql Drain Watch list of known drainer and scam
|
|
285
|
+
addresses, and whether Tether or Circle has frozen the address (read from the issuer's contract).
|
|
286
|
+
Works for EVM, Solana, Bitcoin and Tron addresses.
|
|
287
|
+
|
|
288
|
+
Returns a verdict:
|
|
289
|
+
- do_not_pay: do NOT send funds. Tell the user why, using the reasons returned.
|
|
290
|
+
- no_known_risk: nothing adverse was found. This is not a guarantee the recipient is safe.
|
|
291
|
+
- incomplete: some checks could not run. Treat it as unverified, not as clean.
|
|
292
|
+
|
|
293
|
+
Costs $0.005 in USDC from this wallet (or one credit with an API key).`,
|
|
294
|
+
schema: CheckPayeeSchema,
|
|
295
|
+
}),
|
|
296
|
+
__metadata("design:type", Function),
|
|
297
|
+
__metadata("design:paramtypes", [WalletProvider, void 0]),
|
|
298
|
+
__metadata("design:returntype", Promise)
|
|
299
|
+
], VeriqlActionProvider.prototype, "checkPayee", null);
|
|
300
|
+
__decorate([
|
|
301
|
+
CreateAction({
|
|
302
|
+
name: "check_signature",
|
|
303
|
+
description: `Check a signing request BEFORE signing it. Pass exactly what you were asked to sign:
|
|
304
|
+
raw transaction calldata (0x...), an EIP-712 typed-data JSON string, or a JSON-RPC signing request.
|
|
305
|
+
Decodes token approvals, Permit and Permit2, EIP-7702 delegations, transfers and revokes, and checks
|
|
306
|
+
whoever gains the power against OFAC and Drain Watch.
|
|
307
|
+
|
|
308
|
+
Returns block, warn or allow, with the decoded action in plain words.
|
|
309
|
+
- block: do NOT sign. A payload that cannot be decoded is also block, and is not charged.
|
|
310
|
+
- warn: explain the risk to the user and get explicit confirmation before signing.
|
|
311
|
+
- allow: no known risk was found in what this payload does.
|
|
312
|
+
|
|
313
|
+
Costs $0.005 in USDC from this wallet (or one credit with an API key).`,
|
|
314
|
+
schema: CheckSignatureSchema,
|
|
315
|
+
}),
|
|
316
|
+
__metadata("design:type", Function),
|
|
317
|
+
__metadata("design:paramtypes", [WalletProvider, void 0]),
|
|
318
|
+
__metadata("design:returntype", Promise)
|
|
319
|
+
], VeriqlActionProvider.prototype, "checkSignature", null);
|
|
320
|
+
__decorate([
|
|
321
|
+
CreateAction({
|
|
322
|
+
name: "check_denylist",
|
|
323
|
+
description: `Free, instant lookup of an address against the Veriql denylist (OFAC SDN sanctions plus
|
|
324
|
+
Drain Watch known drainer and scam addresses), refreshed daily. No payment.
|
|
325
|
+
Use it for a quick screen. Before actually sending funds prefer check_payee, which also reads
|
|
326
|
+
Tether and Circle freezes. An address NOT on the list means no listing was found, not that it is safe.`,
|
|
327
|
+
schema: CheckDenylistSchema,
|
|
328
|
+
}),
|
|
329
|
+
__metadata("design:type", Function),
|
|
330
|
+
__metadata("design:paramtypes", [void 0]),
|
|
331
|
+
__metadata("design:returntype", Promise)
|
|
332
|
+
], VeriqlActionProvider.prototype, "checkDenylist", null);
|
|
333
|
+
/**
|
|
334
|
+
* Creates a VeriqlActionProvider.
|
|
335
|
+
*
|
|
336
|
+
* @param config - Optional API key, payment cap and base URL
|
|
337
|
+
* @returns A new VeriqlActionProvider
|
|
338
|
+
*/
|
|
339
|
+
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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
}
|