Skip to content
Open

Large diffs are not rendered by default.

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions typescript/agentkit/src/action-providers/basepay/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export { BasePayActionProvider, basePayActionProvider } from "./basepayActionProvider";
export type { BasePayConfig } from "./basepayActionProvider";
export {
SendUsdcSchema,
SendUsdcGaslessSchema,
BatchPayUsdcSchema,
CreateEscrowSchema,
SubscribeSchema,
} from "./schemas";

91 changes: 91 additions & 0 deletions typescript/agentkit/src/action-providers/basepay/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { z } from "zod";

const ethAddress = z
.string()
.regex(/^0x[0-9a-fA-F]{40}$/, "Must be a valid 0x Ethereum address");

export const SendUsdcSchema = z.object({
to: ethAddress.describe("Recipient address on Base Mainnet"),
amount: z
.string()
.describe(
'Amount of USDC to send, as a human-readable decimal (e.g. "10.5" for 10.5 USDC)',
),
});

export const SendUsdcGaslessSchema = z.object({
to: ethAddress.describe("Recipient address on Base Mainnet"),
amount: z
.string()
.describe(
'Amount of USDC to send gaslessly via EIP-3009, as a decimal (e.g. "5" for 5 USDC). ' +
"The BasePay relay pays the ETH gas — the agent wallet needs no ETH for this action.",
),
});

export const BatchPayUsdcSchema = z.object({
recipients: z
.array(
z.object({
address: ethAddress.describe("Recipient wallet address"),
amount: z
.string()
.describe('USDC amount for this recipient (e.g. "10.5")'),
}),
)
.min(1)
.max(200)
.describe("List of recipient address and USDC amount pairs (max 200 entries)."),
memo: z
.string()
.max(64)
.default("")
.describe("Optional note recorded on-chain with the batch payment"),
});

export const CreateEscrowSchema = z.object({
payee: ethAddress.describe(
"Address of the escrow beneficiary who can claim the USDC after the lock period expires",
),
amount: z
.string()
.describe('Amount of USDC to lock in escrow (e.g. "100" for 100 USDC)'),
unlockAfterSeconds: z
.number()
.int()
.min(60)
.describe(
"Seconds until the payee can claim, or the payer can reclaim. " +
"Examples: 86400 = 1 day, 604800 = 1 week, 2592000 = 30 days",
),
memo: z
.string()
.max(64)
.default("")
.describe("Optional note recorded on-chain with the escrow"),
});

export const SubscribeSchema = z.object({
payee: ethAddress.describe(
"Address that receives USDC at each billing interval",
),
amount: z
.string()
.describe(
'USDC amount charged per interval (e.g. "9.99" for $9.99 per period)',
),
intervalSeconds: z
.number()
.int()
.min(3600)
.describe(
"Seconds between each recurring charge. " +
"Examples: 604800 = weekly, 2592000 = monthly, 31536000 = yearly",
),
memo: z
.string()
.max(64)
.default("")
.describe("Optional description of the subscription recorded on-chain"),
});

1 change: 1 addition & 0 deletions typescript/agentkit/src/action-providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ export * from "./zerion";
export * from "./zerodev";
export * from "./zeroX";
export * from "./zora";
export * from "./basepay";
3 changes: 3 additions & 0 deletions typescript/agentkit/src/policy/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./interfaces";
export * from "./utils";

29 changes: 29 additions & 0 deletions typescript/agentkit/src/policy/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface ActionContext {
action: string;
to?: string;
amount_usdc?: string;
aggregate_usdc?: string;
recipient_count?: number;
recipient_allocation_hash?: string;
per_recipient_max?: string;
transfer_mechanism?: 'direct' | 'eip3009' | 'permit' | 'x402';
creates_recurring_obligation?: boolean;
creates_commitment?: boolean;
}

export interface PolicyDecision {
allowed: boolean;
reason_codes?: string[];
signal_refs?: Record<string, string>;
policy_version: string;
action_context_hash: string;
decision_ref: string;
issued_at_ms: number;
expires_at_ms: number;
signature?: string;
}

export interface PolicyProvider {
evaluate(ctx: ActionContext): Promise<PolicyDecision>;
}

34 changes: 34 additions & 0 deletions typescript/agentkit/src/policy/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import canonicalize from "canonicalize";
import { ActionContext } from "./interfaces";

/**
* SHA-256 hash of a string, using the Web Crypto API.
*/
export async function sha256(text: string): Promise<string> {
const msgUint8 = new TextEncoder().encode(text);
const hashBuffer = await crypto.subtle.digest("SHA-256", msgUint8);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
}

/**
* recipientAllocationHash: RFC 8785 JCS hash over sorted address+amount pairs.
* Catches address substitution, amount redistribution, and silent reordering.
*/
export async function recipientAllocationHash(
recipients: Array<{ address: string; amount: bigint }>,
): Promise<string> {
const normalized = recipients
.map((r) => ({ to: r.address.toLowerCase(), amount_atomic: r.amount.toString() }))
.sort((a, b) => a.to.localeCompare(b.to) || a.amount_atomic.localeCompare(b.amount_atomic));
return sha256(canonicalize(normalized) ?? "{}");
}

/**
* actionContextHash: RFC 8785 JCS hash of an ActionContext.
* Policy-independent content identifier for cross-implementation join keys.
*/
export async function actionContextHash(ctx: ActionContext): Promise<string> {
return sha256(canonicalize(ctx) ?? "{}");
}

Loading