@agentpay-ai/shared 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/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@agentpay-ai/shared",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./src/index.ts"
7
+ },
8
+ "files": [
9
+ "src/account-admin.ts",
10
+ "src/approval.ts",
11
+ "src/balance.ts",
12
+ "src/chains.ts",
13
+ "src/index.ts",
14
+ "src/invoice.ts",
15
+ "src/payment-intent.ts",
16
+ "src/payment-tracking.ts",
17
+ "src/tokens.ts",
18
+ "src/wallet-setup.ts",
19
+ "src/x402.ts"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "scripts": {
25
+ "test": "tsx --test \"src/**/*.test.ts\"",
26
+ "typecheck": "tsc -p ../../tsconfig.json --noEmit"
27
+ },
28
+ "dependencies": {
29
+ "@noble/hashes": "^2.2.0",
30
+ "zod": "^4.4.3"
31
+ }
32
+ }
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+
3
+ import { evmAddressSchema } from "./payment-intent.ts";
4
+
5
+ const positiveIntegerStringSchema = z.string().regex(/^[1-9]\d*$/, "Expected a positive integer string");
6
+
7
+ export const prepareAccountAdminTransactionInputSchema = z.discriminatedUnion("action", [
8
+ z.object({
9
+ action: z.literal("PAUSE"),
10
+ }),
11
+ z.object({
12
+ action: z.literal("UNPAUSE"),
13
+ }),
14
+ z.object({
15
+ action: z.literal("SET_EXECUTOR"),
16
+ newExecutorAddress: evmAddressSchema,
17
+ }),
18
+ z.object({
19
+ action: z.literal("CANCEL_NONCE"),
20
+ nonce: positiveIntegerStringSchema,
21
+ }),
22
+ z.object({
23
+ action: z.literal("SET_ALLOWED_TOKEN"),
24
+ tokenAddress: evmAddressSchema,
25
+ allowed: z.boolean(),
26
+ }),
27
+ z.object({
28
+ action: z.literal("WITHDRAW_NATIVE"),
29
+ toAddress: evmAddressSchema,
30
+ amountAtomic: positiveIntegerStringSchema,
31
+ }),
32
+ z.object({
33
+ action: z.literal("WITHDRAW_TOKEN"),
34
+ tokenAddress: evmAddressSchema,
35
+ toAddress: evmAddressSchema,
36
+ amountAtomic: positiveIntegerStringSchema,
37
+ }),
38
+ ]);
39
+
40
+ export type PrepareAccountAdminTransactionInput = z.infer<typeof prepareAccountAdminTransactionInputSchema>;
@@ -0,0 +1,7 @@
1
+ export function createApprovalPhrase(paymentIntentId: string): string {
2
+ return `APPROVE ${paymentIntentId}`;
3
+ }
4
+
5
+ export function createApprovalInstruction(paymentIntentId: string): string {
6
+ return `To approve, reply exactly:\n${createApprovalPhrase(paymentIntentId)}`;
7
+ }
package/src/balance.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+
3
+ import { STABLE_TOKEN_SYMBOLS, stableTokenSymbolSchema } from "./tokens.ts";
4
+
5
+ export const getBalanceInputSchema = z.object({
6
+ tokenSymbols: z.array(stableTokenSymbolSchema).min(1).default([...STABLE_TOKEN_SYMBOLS]),
7
+ });
8
+
9
+ export type GetBalanceInput = z.input<typeof getBalanceInputSchema>;
10
+ export type ParsedGetBalanceInput = z.output<typeof getBalanceInputSchema>;
package/src/chains.ts ADDED
@@ -0,0 +1,55 @@
1
+ export const SUPPORTED_CHAINS = {
2
+ 56: {
3
+ id: 56,
4
+ name: "BNB Chain",
5
+ nativeCurrency: {
6
+ symbol: "BNB",
7
+ decimals: 18,
8
+ },
9
+ },
10
+ 97: {
11
+ id: 97,
12
+ name: "BNB Chain Testnet",
13
+ nativeCurrency: {
14
+ symbol: "tBNB",
15
+ decimals: 18,
16
+ },
17
+ },
18
+ 8453: {
19
+ id: 8453,
20
+ name: "Base",
21
+ nativeCurrency: {
22
+ symbol: "ETH",
23
+ decimals: 18,
24
+ },
25
+ },
26
+ } as const;
27
+
28
+ export type SupportedChainId = keyof typeof SUPPORTED_CHAINS;
29
+ export type NativeCurrency = (typeof SUPPORTED_CHAINS)[SupportedChainId]["nativeCurrency"];
30
+
31
+ export function getChainName(chainId: number): string {
32
+ return SUPPORTED_CHAINS[chainId as SupportedChainId]?.name ?? `Chain ${chainId}`;
33
+ }
34
+
35
+ export function getNativeCurrency(chainId: number): NativeCurrency {
36
+ const nativeCurrency = SUPPORTED_CHAINS[chainId as SupportedChainId]?.nativeCurrency;
37
+
38
+ if (!nativeCurrency) {
39
+ throw new Error(`Unsupported chain ${chainId}.`);
40
+ }
41
+
42
+ return nativeCurrency;
43
+ }
44
+
45
+ export function formatNativeAmount(atomicAmount: string, chainId: number): string {
46
+ const nativeCurrency = getNativeCurrency(chainId);
47
+ return `${atomicToDecimal(BigInt(atomicAmount), nativeCurrency.decimals)} ${nativeCurrency.symbol}`;
48
+ }
49
+
50
+ function atomicToDecimal(amount: bigint, decimals: number): string {
51
+ const padded = amount.toString().padStart(decimals + 1, "0");
52
+ const whole = padded.slice(0, -decimals);
53
+ const fractional = padded.slice(-decimals).replace(/0+$/, "");
54
+ return fractional ? `${whole}.${fractional}` : whole;
55
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export * from "./approval.ts";
2
+ export * from "./account-admin.ts";
3
+ export * from "./balance.ts";
4
+ export * from "./chains.ts";
5
+ export * from "./invoice.ts";
6
+ export * from "./payment-intent.ts";
7
+ export * from "./payment-tracking.ts";
8
+ export * from "./tokens.ts";
9
+ export * from "./wallet-setup.ts";
10
+ export * from "./x402.ts";
package/src/invoice.ts ADDED
@@ -0,0 +1,144 @@
1
+ import { z } from "zod";
2
+
3
+ import { getChainName } from "./chains.ts";
4
+ import { preparePaymentInputSchema } from "./payment-intent.ts";
5
+ import { stableTokenSymbolSchema } from "./tokens.ts";
6
+
7
+ export const parseInvoicePaymentInputSchema = z.object({
8
+ invoice: z.string().trim().min(1),
9
+ sourceTokenSymbol: stableTokenSymbolSchema.default("USDT"),
10
+ });
11
+
12
+ export type ParseInvoicePaymentInput = z.input<typeof parseInvoicePaymentInputSchema>;
13
+
14
+ export interface ParsedInvoicePayment {
15
+ invoiceId?: string;
16
+ recipientAddress: string;
17
+ destinationChainId: number;
18
+ destinationChain: string;
19
+ destinationTokenSymbol: "USDC" | "USDT";
20
+ amountOut: string;
21
+ purpose: string;
22
+ sourceTokenSymbol: "USDC" | "USDT";
23
+ paymentType: "INVOICE_PAYMENT";
24
+ }
25
+
26
+ export function parseInvoicePayment(rawInput: ParseInvoicePaymentInput): ParsedInvoicePayment {
27
+ const input = parseInvoicePaymentInputSchema.parse(rawInput);
28
+ const invoice = parseInvoiceFields(input.invoice);
29
+ const invoiceId = readField(invoice, ["invoiceid", "id", "invoice"]);
30
+ const purpose = readField(invoice, ["purpose", "memo", "description", "note"]) ?? invoicePurpose(invoiceId);
31
+ const candidate = {
32
+ recipientAddress: readField(invoice, ["recipientaddress", "recipient", "to", "payto"]),
33
+ destinationChainId: parseDestinationChainId(
34
+ readField(invoice, ["destinationchainid", "destinationchain", "chainid", "chain"]),
35
+ ),
36
+ destinationTokenSymbol: parseToken(readField(invoice, ["destinationtokensymbol", "token", "currency"])),
37
+ amountOut: readField(invoice, ["amountout", "amount", "amountdue", "total"]),
38
+ purpose,
39
+ sourceTokenSymbol: parseToken(readField(invoice, ["sourcetokensymbol", "sourcetoken"]) ?? input.sourceTokenSymbol),
40
+ };
41
+
42
+ const parsed = parsePaymentFields(candidate);
43
+
44
+ return {
45
+ invoiceId,
46
+ recipientAddress: parsed.recipientAddress,
47
+ destinationChainId: parsed.destinationChainId,
48
+ destinationChain: getChainName(parsed.destinationChainId),
49
+ destinationTokenSymbol: parsed.destinationTokenSymbol,
50
+ amountOut: parsed.amountOut,
51
+ purpose: parsed.purpose,
52
+ sourceTokenSymbol: parsed.sourceTokenSymbol,
53
+ paymentType: "INVOICE_PAYMENT",
54
+ };
55
+ }
56
+
57
+ function parseInvoiceFields(invoice: string): Record<string, unknown> {
58
+ const trimmed = invoice.trim();
59
+
60
+ if (trimmed.startsWith("{")) {
61
+ const parsed = JSON.parse(trimmed) as unknown;
62
+
63
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
64
+ throw new Error("Invalid invoice: expected a JSON object.");
65
+ }
66
+
67
+ return Object.fromEntries(
68
+ Object.entries(parsed as Record<string, unknown>).map(([key, value]) => [normalizeKey(key), value]),
69
+ );
70
+ }
71
+
72
+ return Object.fromEntries(
73
+ trimmed
74
+ .split(/\r?\n|;/)
75
+ .map((line) => line.match(/^\s*([^:=]+)\s*[:=]\s*(.+?)\s*$/))
76
+ .filter((match): match is RegExpMatchArray => Boolean(match))
77
+ .map((match) => [normalizeKey(match[1]), match[2].trim()]),
78
+ );
79
+ }
80
+
81
+ function readField(fields: Record<string, unknown>, aliases: string[]): string | undefined {
82
+ for (const alias of aliases) {
83
+ const value = fields[alias];
84
+
85
+ if (value !== undefined && value !== null && String(value).trim() !== "") {
86
+ return String(value).trim();
87
+ }
88
+ }
89
+
90
+ return undefined;
91
+ }
92
+
93
+ function parseDestinationChainId(value: string | undefined): number | undefined {
94
+ if (!value) {
95
+ return undefined;
96
+ }
97
+
98
+ if (/^\d+$/.test(value)) {
99
+ return Number(value);
100
+ }
101
+
102
+ const normalized = normalizeKey(value);
103
+ const knownChains: Record<string, number> = {
104
+ base: 8453,
105
+ bnbchain: 56,
106
+ bnb: 56,
107
+ bsc: 56,
108
+ };
109
+
110
+ return knownChains[normalized];
111
+ }
112
+
113
+ function parseToken(value: string | undefined): "USDC" | "USDT" | undefined {
114
+ if (!value) {
115
+ return undefined;
116
+ }
117
+
118
+ return stableTokenSymbolSchema.parse(value.trim().toUpperCase());
119
+ }
120
+
121
+ function parsePaymentFields(candidate: {
122
+ recipientAddress: string | undefined;
123
+ destinationChainId: number | undefined;
124
+ destinationTokenSymbol: "USDC" | "USDT" | undefined;
125
+ amountOut: string | undefined;
126
+ purpose: string | undefined;
127
+ sourceTokenSymbol: "USDC" | "USDT" | undefined;
128
+ }) {
129
+ const parsed = preparePaymentInputSchema.safeParse(candidate);
130
+
131
+ if (!parsed.success) {
132
+ throw new Error(`Invalid invoice payment fields: ${parsed.error.issues.map((issue) => issue.path.join(".")).join(", ")}`);
133
+ }
134
+
135
+ return parsed.data;
136
+ }
137
+
138
+ function invoicePurpose(invoiceId: string | undefined): string | undefined {
139
+ return invoiceId ? `Invoice ${invoiceId}` : undefined;
140
+ }
141
+
142
+ function normalizeKey(value: string): string {
143
+ return value.toLowerCase().replace(/[^a-z0-9]/g, "");
144
+ }
@@ -0,0 +1,171 @@
1
+ import { keccak_256 } from "@noble/hashes/sha3.js";
2
+ import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
3
+ import { z } from "zod";
4
+
5
+ import { getChainName } from "./chains.ts";
6
+ import { getStableTokenMetadata, stableTokenSymbolSchema } from "./tokens.ts";
7
+
8
+ const addressPattern = /^0x[a-fA-F0-9]{40}$/;
9
+ const hexPattern = /^0x(?:[a-fA-F0-9]{2})*$/;
10
+ const positiveDecimalPattern = /^(?:0|[1-9]\d*)(?:\.\d+)?$/;
11
+
12
+ export const evmAddressSchema = z.string().regex(addressPattern, "Expected an EVM address");
13
+
14
+ export const hexDataSchema = z.string().regex(hexPattern, "Expected 0x-prefixed even-length hex data");
15
+
16
+ export const positiveDecimalStringSchema = z.string().refine(
17
+ (value) => positiveDecimalPattern.test(value) && Number(value) > 0,
18
+ "Expected a positive decimal string",
19
+ );
20
+
21
+ export const paymentIntentStatusSchema = z.enum([
22
+ "AWAITING_APPROVAL",
23
+ "APPROVED",
24
+ "EXECUTING",
25
+ "COMPLETED",
26
+ "FAILED",
27
+ "EXPIRED",
28
+ "CANCELLED",
29
+ ]);
30
+
31
+ export type PaymentIntentStatus = z.infer<typeof paymentIntentStatusSchema>;
32
+
33
+ export const paymentTypeSchema = z.enum(["WALLET_PAYMENT", "INVOICE_PAYMENT", "X402_PAYMENT", "CONTRACT_CALL"]);
34
+
35
+ export type PaymentType = z.infer<typeof paymentTypeSchema>;
36
+
37
+ export const stablecoinPaymentTypeSchema = z.enum(["WALLET_PAYMENT", "INVOICE_PAYMENT", "X402_PAYMENT"]);
38
+
39
+ export type StablecoinPaymentType = z.infer<typeof stablecoinPaymentTypeSchema>;
40
+
41
+ export const preparePaymentInputSchema = z.object({
42
+ recipientAddress: evmAddressSchema,
43
+ destinationChainId: z.number().int().positive(),
44
+ destinationTokenSymbol: stableTokenSymbolSchema,
45
+ amountOut: positiveDecimalStringSchema,
46
+ purpose: z.string().trim().min(1).max(280),
47
+ sourceTokenSymbol: stableTokenSymbolSchema.default("USDT"),
48
+ paymentType: stablecoinPaymentTypeSchema.default("WALLET_PAYMENT"),
49
+ });
50
+
51
+ export const quotePaymentRouteInputSchema = preparePaymentInputSchema.omit({ purpose: true, paymentType: true });
52
+
53
+ export type QuotePaymentRouteInput = z.input<typeof quotePaymentRouteInputSchema>;
54
+ export type ParsedQuotePaymentRouteInput = z.output<typeof quotePaymentRouteInputSchema>;
55
+
56
+ export type PreparePaymentInput = z.input<typeof preparePaymentInputSchema>;
57
+ export type ParsedPreparePaymentInput = z.output<typeof preparePaymentInputSchema>;
58
+
59
+ export const executePaymentInputSchema = z.object({
60
+ paymentIntentId: z.string().trim().min(1),
61
+ approvalText: z.string().min(1),
62
+ });
63
+
64
+ export type ExecutePaymentInput = z.infer<typeof executePaymentInputSchema>;
65
+
66
+ export const prepareContractCallInputSchema = z.object({
67
+ targetAddress: evmAddressSchema,
68
+ callData: hexDataSchema.refine((value) => value !== "0x", "Expected non-empty calldata"),
69
+ sourceTokenSymbol: stableTokenSymbolSchema.default("USDT"),
70
+ maxTokenSpend: positiveDecimalStringSchema,
71
+ maxNativeFee: z.string().regex(/^(?:0|[1-9]\d*)$/, "Expected a non-negative integer string").default("0"),
72
+ purpose: z.string().trim().min(1).max(280),
73
+ });
74
+
75
+ export type PrepareContractCallInput = z.input<typeof prepareContractCallInputSchema>;
76
+ export type ParsedPrepareContractCallInput = z.output<typeof prepareContractCallInputSchema>;
77
+
78
+ export const routeProviderSchema = z.enum(["DIRECT", "LI.FI", "CONTRACT_CALL"]);
79
+
80
+ export type RouteProvider = z.infer<typeof routeProviderSchema>;
81
+
82
+ export const DIRECT_PAYMENT_ROUTE_TARGET = "0x0000000000000000000000000000000000000000";
83
+ export const DIRECT_PAYMENT_ROUTE_CALLDATA = "0x";
84
+
85
+ export interface RouteQuote {
86
+ routeProvider: RouteProvider;
87
+ sourceTokenAddress: string;
88
+ destinationTokenAddress: string;
89
+ maxAmountIn: string;
90
+ maxNativeFee: string;
91
+ routeTarget: string;
92
+ routeCalldata: string;
93
+ routeCalldataHash: string;
94
+ routeSummary: string;
95
+ estimatedFee?: string;
96
+ estimatedEtaSeconds?: number;
97
+ }
98
+
99
+ export function isDirectPaymentRoute(
100
+ sourceChainId: number,
101
+ destinationChainId: number,
102
+ sourceTokenSymbol: string,
103
+ destinationTokenSymbol: string,
104
+ ): boolean {
105
+ return sourceChainId === destinationChainId && sourceTokenSymbol === destinationTokenSymbol;
106
+ }
107
+
108
+ export function createDirectPaymentRouteQuote(request: {
109
+ chainId: number;
110
+ tokenSymbol: string;
111
+ amountOut: string;
112
+ }): RouteQuote {
113
+ const token = getStableTokenMetadata(request.chainId, request.tokenSymbol);
114
+ const routeCalldata = DIRECT_PAYMENT_ROUTE_CALLDATA;
115
+
116
+ return {
117
+ routeProvider: "DIRECT",
118
+ sourceTokenAddress: token.address,
119
+ destinationTokenAddress: token.address,
120
+ maxAmountIn: request.amountOut,
121
+ maxNativeFee: "0",
122
+ routeTarget: DIRECT_PAYMENT_ROUTE_TARGET,
123
+ routeCalldata,
124
+ routeCalldataHash: createRouteCalldataHash(routeCalldata),
125
+ routeSummary: `Direct ${request.amountOut} ${token.symbol} transfer on ${getChainName(request.chainId)}.`,
126
+ estimatedFee: "0",
127
+ estimatedEtaSeconds: 0,
128
+ };
129
+ }
130
+
131
+ export interface PaymentIntentRecord {
132
+ id: string;
133
+ accountAddress: string;
134
+ ownerAddress: string;
135
+ status: PaymentIntentStatus;
136
+ paymentType: PaymentType;
137
+ sourceChainId: number;
138
+ destinationChainId: number;
139
+ sourceTokenAddress: string;
140
+ sourceTokenSymbol: string;
141
+ destinationTokenAddress: string;
142
+ destinationTokenSymbol: string;
143
+ recipientAddress: string;
144
+ amountOut: string;
145
+ maxAmountIn: string;
146
+ maxNativeFee: string;
147
+ routeProvider: RouteProvider;
148
+ routeTarget: string;
149
+ routeCalldata: string;
150
+ routeCalldataHash: string;
151
+ routeSummary: string;
152
+ estimatedFee?: string;
153
+ estimatedEtaSeconds?: number;
154
+ nonce: string;
155
+ deadline: string;
156
+ purpose: string;
157
+ approvalPhrase: string;
158
+ approvedAt?: string;
159
+ sourceTxHash?: string;
160
+ destinationTxHash?: string;
161
+ lifiTrackingId?: string;
162
+ errorCode?: string;
163
+ errorMessage?: string;
164
+ createdAt?: string;
165
+ completedAt?: string;
166
+ }
167
+
168
+ export function createRouteCalldataHash(routeCalldata: string): string {
169
+ const parsed = hexDataSchema.parse(routeCalldata);
170
+ return `0x${bytesToHex(keccak_256(hexToBytes(parsed.slice(2))))}`;
171
+ }
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+
3
+ export const trackPaymentInputSchema = z.object({
4
+ paymentIntentId: z.string().trim().min(1),
5
+ });
6
+
7
+ export type TrackPaymentInput = z.infer<typeof trackPaymentInputSchema>;
8
+
9
+ export const listTransactionsInputSchema = z.object({
10
+ limit: z.number().int().min(1).max(50).default(10),
11
+ });
12
+
13
+ export type ListTransactionsInput = z.input<typeof listTransactionsInputSchema>;
14
+ export type ParsedListTransactionsInput = z.output<typeof listTransactionsInputSchema>;
15
+
16
+ export const listPaymentEventsInputSchema = z.object({
17
+ paymentIntentId: z.string().trim().min(1),
18
+ limit: z.number().int().min(1).max(50).default(20),
19
+ });
20
+
21
+ export type ListPaymentEventsInput = z.input<typeof listPaymentEventsInputSchema>;
22
+ export type ParsedListPaymentEventsInput = z.output<typeof listPaymentEventsInputSchema>;
23
+
24
+ export interface PaymentEventRecord {
25
+ id: string;
26
+ paymentIntentId: string;
27
+ eventType: string;
28
+ message?: string;
29
+ metadata: Record<string, unknown>;
30
+ createdAt: string;
31
+ }
package/src/tokens.ts ADDED
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+
3
+ export const STABLE_TOKEN_SYMBOLS = ["USDC", "USDT"] as const;
4
+
5
+ export const stableTokenSymbolSchema = z.enum(STABLE_TOKEN_SYMBOLS);
6
+
7
+ export type StableTokenSymbol = z.infer<typeof stableTokenSymbolSchema>;
8
+
9
+ export const STABLE_TOKEN_DECIMALS: Record<StableTokenSymbol, number> = {
10
+ USDC: 6,
11
+ USDT: 6,
12
+ };
13
+
14
+ export interface StableTokenMetadata {
15
+ symbol: StableTokenSymbol;
16
+ address: string;
17
+ decimals: number;
18
+ }
19
+
20
+ export type StableTokenMetadataOverrides = Partial<
21
+ Record<number, Partial<Record<StableTokenSymbol, Partial<Pick<StableTokenMetadata, "address" | "decimals">>>>>
22
+ >;
23
+
24
+ let configuredStableTokenMetadataOverrides: StableTokenMetadataOverrides = {};
25
+
26
+ export const STABLE_TOKENS_BY_CHAIN: Record<number, Record<StableTokenSymbol, StableTokenMetadata>> = {
27
+ 56: {
28
+ USDC: {
29
+ symbol: "USDC",
30
+ address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
31
+ decimals: 18,
32
+ },
33
+ USDT: {
34
+ symbol: "USDT",
35
+ address: "0x55d398326f99059fF775485246999027B3197955",
36
+ decimals: 18,
37
+ },
38
+ },
39
+ 97: {
40
+ USDC: {
41
+ symbol: "USDC",
42
+ address: "0xEC1C60D64a06896Df296438c12edD14E974FDE47",
43
+ decimals: 6,
44
+ },
45
+ USDT: {
46
+ symbol: "USDT",
47
+ address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",
48
+ decimals: 18,
49
+ },
50
+ },
51
+ 8453: {
52
+ USDC: {
53
+ symbol: "USDC",
54
+ address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
55
+ decimals: 6,
56
+ },
57
+ USDT: {
58
+ symbol: "USDT",
59
+ address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
60
+ decimals: 6,
61
+ },
62
+ },
63
+ };
64
+
65
+ export function configureStableTokenMetadataOverrides(overrides: StableTokenMetadataOverrides): void {
66
+ configuredStableTokenMetadataOverrides = { ...overrides };
67
+ }
68
+
69
+ export function getStableTokenDecimals(symbol: string): number {
70
+ const decimals = STABLE_TOKEN_DECIMALS[symbol as StableTokenSymbol];
71
+ if (decimals === undefined) {
72
+ throw new Error(`Unsupported stable token symbol: ${symbol}`);
73
+ }
74
+ return decimals;
75
+ }
76
+
77
+ export function getStableTokenMetadata(chainId: number, symbol: string): StableTokenMetadata {
78
+ const parsedSymbol = stableTokenSymbolSchema.parse(symbol);
79
+ const metadata = STABLE_TOKENS_BY_CHAIN[chainId]?.[parsedSymbol];
80
+
81
+ if (!metadata) {
82
+ throw new Error(`Unsupported stable token ${parsedSymbol} on chain ${chainId}.`);
83
+ }
84
+
85
+ const override = configuredStableTokenMetadataOverrides[chainId]?.[parsedSymbol];
86
+
87
+ return {
88
+ ...metadata,
89
+ ...override,
90
+ symbol: parsedSymbol,
91
+ };
92
+ }
93
+
94
+ export function getStableTokenAddress(chainId: number, symbol: string): string {
95
+ return getStableTokenMetadata(chainId, symbol).address;
96
+ }
97
+
98
+ export function getStableTokenDecimalsForChain(chainId: number, symbol: string): number {
99
+ return getStableTokenMetadata(chainId, symbol).decimals;
100
+ }
@@ -0,0 +1,59 @@
1
+ import { z } from "zod";
2
+
3
+ import { evmAddressSchema } from "./payment-intent.ts";
4
+
5
+ export const setupIntentStatusSchema = z.enum(["PENDING", "SIGNED", "DEPLOYING", "COMPLETED", "EXPIRED", "FAILED"]);
6
+
7
+ export type SetupIntentStatus = z.infer<typeof setupIntentStatusSchema>;
8
+
9
+ export const prepareWalletCreationInputSchema = z.object({
10
+ ownerAddress: evmAddressSchema.optional(),
11
+ });
12
+
13
+ export type PrepareWalletCreationInput = z.input<typeof prepareWalletCreationInputSchema>;
14
+
15
+ export const checkWalletCreationInputSchema = z.object({
16
+ setupIntentId: z.string().trim().min(1),
17
+ });
18
+
19
+ export type CheckWalletCreationInput = z.infer<typeof checkWalletCreationInputSchema>;
20
+
21
+ export const getAgentWalletInputSchema = z.object({});
22
+
23
+ export type GetAgentWalletInput = z.infer<typeof getAgentWalletInputSchema>;
24
+
25
+ export const prepareRouteTargetAllowanceInputSchema = z.object({
26
+ routeTarget: evmAddressSchema,
27
+ allowed: z.boolean().default(true),
28
+ });
29
+
30
+ export type PrepareRouteTargetAllowanceInput = z.input<typeof prepareRouteTargetAllowanceInputSchema>;
31
+
32
+ export const checkRouteTargetAllowanceInputSchema = z.object({
33
+ routeTarget: evmAddressSchema,
34
+ });
35
+
36
+ export type CheckRouteTargetAllowanceInput = z.infer<typeof checkRouteTargetAllowanceInputSchema>;
37
+
38
+ export const setupSignatureSchema = z.string().regex(/^0x[a-fA-F0-9]{130}$/, "Expected an EVM signature");
39
+
40
+ export const completeWalletSetupInputSchema = z.object({
41
+ setupIntentId: z.string().trim().min(1),
42
+ signature: setupSignatureSchema,
43
+ });
44
+
45
+ export type CompleteWalletSetupInput = z.infer<typeof completeWalletSetupInputSchema>;
46
+
47
+ export interface SetupIntentRecord {
48
+ id: string;
49
+ ownerAddress?: string;
50
+ executorAddress: string;
51
+ messageToSign: string;
52
+ signature?: string;
53
+ status: SetupIntentStatus;
54
+ expiresAt: string;
55
+ accountAddress?: string;
56
+ errorCode?: string;
57
+ errorMessage?: string;
58
+ completedAt?: string;
59
+ }
package/src/x402.ts ADDED
@@ -0,0 +1,204 @@
1
+ import { z } from "zod";
2
+
3
+ import { getChainName } from "./chains.ts";
4
+ import { evmAddressSchema, preparePaymentInputSchema } from "./payment-intent.ts";
5
+ import { getStableTokenMetadata, STABLE_TOKEN_SYMBOLS, stableTokenSymbolSchema } from "./tokens.ts";
6
+ import type { StableTokenSymbol } from "./tokens.ts";
7
+
8
+ const positiveIntegerStringSchema = z.string().regex(/^[1-9]\d*$/, "Expected a positive integer string");
9
+
10
+ export const parseX402PaymentRequiredInputSchema = z.object({
11
+ paymentRequired: z.union([z.string().trim().min(1), z.record(z.string(), z.unknown())]),
12
+ sourceTokenSymbol: stableTokenSymbolSchema.default("USDT"),
13
+ });
14
+
15
+ export type ParseX402PaymentRequiredInput = z.input<typeof parseX402PaymentRequiredInputSchema>;
16
+
17
+ const x402ResourceInfoSchema = z
18
+ .object({
19
+ url: z.string().url(),
20
+ description: z.string().optional(),
21
+ mimeType: z.string().optional(),
22
+ serviceName: z.string().optional(),
23
+ })
24
+ .passthrough();
25
+
26
+ const x402PaymentRequirementSchema = z
27
+ .object({
28
+ scheme: z.string(),
29
+ network: z.string(),
30
+ amount: positiveIntegerStringSchema,
31
+ asset: z.string(),
32
+ payTo: evmAddressSchema,
33
+ maxTimeoutSeconds: z.number().int().positive(),
34
+ extra: z.record(z.string(), z.unknown()).optional(),
35
+ })
36
+ .passthrough();
37
+
38
+ const x402PaymentRequiredSchema = z
39
+ .object({
40
+ x402Version: z.literal(2),
41
+ error: z.string().optional(),
42
+ resource: x402ResourceInfoSchema,
43
+ accepts: z.array(x402PaymentRequirementSchema).min(1),
44
+ extensions: z.record(z.string(), z.unknown()).optional(),
45
+ })
46
+ .passthrough();
47
+
48
+ export interface ParsedX402PaymentRequired {
49
+ x402Version: 2;
50
+ resource: {
51
+ url: string;
52
+ description?: string;
53
+ serviceName?: string;
54
+ mimeType?: string;
55
+ };
56
+ selectedRequirement: {
57
+ scheme: "exact";
58
+ network: string;
59
+ chainId: number;
60
+ chain: string;
61
+ asset: string;
62
+ tokenSymbol: StableTokenSymbol;
63
+ payTo: string;
64
+ amountAtomic: string;
65
+ amount: string;
66
+ maxTimeoutSeconds: number;
67
+ };
68
+ paymentInput: {
69
+ recipientAddress: string;
70
+ destinationChainId: number;
71
+ destinationChain: string;
72
+ destinationTokenSymbol: StableTokenSymbol;
73
+ amountOut: string;
74
+ purpose: string;
75
+ sourceTokenSymbol: StableTokenSymbol;
76
+ paymentType: "X402_PAYMENT";
77
+ };
78
+ standardX402SignatureRequired: true;
79
+ }
80
+
81
+ export function parseX402PaymentRequired(rawInput: ParseX402PaymentRequiredInput): ParsedX402PaymentRequired {
82
+ const input = parseX402PaymentRequiredInputSchema.parse(rawInput);
83
+ const paymentRequired = x402PaymentRequiredSchema.parse(decodePaymentRequired(input.paymentRequired));
84
+ const selected = paymentRequired.accepts.map(toSupportedRequirement).find((requirement) => requirement !== null);
85
+
86
+ if (!selected) {
87
+ throw new Error("No AgentPay-supported x402 payment requirement was found.");
88
+ }
89
+
90
+ const purpose = createX402Purpose(paymentRequired.resource);
91
+ const paymentInput = preparePaymentInputSchema.parse({
92
+ recipientAddress: selected.payTo,
93
+ destinationChainId: selected.chainId,
94
+ destinationTokenSymbol: selected.tokenSymbol,
95
+ amountOut: selected.amount,
96
+ purpose,
97
+ sourceTokenSymbol: input.sourceTokenSymbol,
98
+ paymentType: "X402_PAYMENT",
99
+ });
100
+
101
+ return {
102
+ x402Version: 2,
103
+ resource: {
104
+ url: paymentRequired.resource.url,
105
+ description: paymentRequired.resource.description,
106
+ serviceName: paymentRequired.resource.serviceName,
107
+ mimeType: paymentRequired.resource.mimeType,
108
+ },
109
+ selectedRequirement: selected,
110
+ paymentInput: {
111
+ recipientAddress: paymentInput.recipientAddress,
112
+ destinationChainId: paymentInput.destinationChainId,
113
+ destinationChain: getChainName(paymentInput.destinationChainId),
114
+ destinationTokenSymbol: paymentInput.destinationTokenSymbol,
115
+ amountOut: paymentInput.amountOut,
116
+ purpose: paymentInput.purpose,
117
+ sourceTokenSymbol: paymentInput.sourceTokenSymbol,
118
+ paymentType: "X402_PAYMENT",
119
+ },
120
+ standardX402SignatureRequired: true,
121
+ };
122
+ }
123
+
124
+ function decodePaymentRequired(paymentRequired: string | Record<string, unknown>): unknown {
125
+ if (typeof paymentRequired !== "string") {
126
+ return paymentRequired;
127
+ }
128
+
129
+ const trimmed = paymentRequired.trim();
130
+ const json = trimmed.startsWith("{") ? trimmed : Buffer.from(trimmed, "base64").toString("utf8");
131
+
132
+ return JSON.parse(json) as unknown;
133
+ }
134
+
135
+ function toSupportedRequirement(requirement: z.infer<typeof x402PaymentRequirementSchema>):
136
+ | ParsedX402PaymentRequired["selectedRequirement"]
137
+ | null {
138
+ if (requirement.scheme !== "exact") {
139
+ return null;
140
+ }
141
+
142
+ const chainId = parseEip155Network(requirement.network);
143
+
144
+ if (!chainId) {
145
+ return null;
146
+ }
147
+
148
+ const tokenSymbol = findStableTokenSymbolByAddress(chainId, requirement.asset);
149
+
150
+ if (!tokenSymbol) {
151
+ return null;
152
+ }
153
+
154
+ const token = getStableTokenMetadata(chainId, tokenSymbol);
155
+
156
+ return {
157
+ scheme: "exact",
158
+ network: requirement.network,
159
+ chainId,
160
+ chain: getChainName(chainId),
161
+ asset: requirement.asset,
162
+ tokenSymbol,
163
+ payTo: requirement.payTo,
164
+ amountAtomic: requirement.amount,
165
+ amount: atomicToDecimal(BigInt(requirement.amount), token.decimals),
166
+ maxTimeoutSeconds: requirement.maxTimeoutSeconds,
167
+ };
168
+ }
169
+
170
+ function parseEip155Network(network: string): number | null {
171
+ const match = network.match(/^eip155:(\d+)$/);
172
+
173
+ return match ? Number(match[1]) : null;
174
+ }
175
+
176
+ function findStableTokenSymbolByAddress(chainId: number, asset: string): StableTokenSymbol | null {
177
+ const normalizedAsset = asset.toLowerCase();
178
+
179
+ for (const symbol of STABLE_TOKEN_SYMBOLS) {
180
+ try {
181
+ if (getStableTokenMetadata(chainId, symbol).address.toLowerCase() === normalizedAsset) {
182
+ return symbol;
183
+ }
184
+ } catch {
185
+ continue;
186
+ }
187
+ }
188
+
189
+ return null;
190
+ }
191
+
192
+ function atomicToDecimal(amount: bigint, decimals: number): string {
193
+ const padded = amount.toString().padStart(decimals + 1, "0");
194
+ const whole = padded.slice(0, -decimals);
195
+ const fractional = padded.slice(-decimals).replace(/0+$/, "");
196
+
197
+ return fractional ? `${whole}.${fractional}` : whole;
198
+ }
199
+
200
+ function createX402Purpose(resource: z.infer<typeof x402ResourceInfoSchema>): string {
201
+ const details = [resource.serviceName, resource.description].filter(Boolean).join(": ") || resource.url;
202
+
203
+ return `x402 payment for ${details}`.slice(0, 280);
204
+ }