@armory-sh/base 0.2.12 → 0.2.13

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/dist/eip712.d.ts CHANGED
@@ -19,6 +19,7 @@ export interface TransferWithAuthorization {
19
19
  validBefore: bigint;
20
20
  nonce: bigint;
21
21
  }
22
+ export type TransferWithAuthorizationRecord = TransferWithAuthorization & Record<string, unknown>;
22
23
  export type TypedDataField = {
23
24
  name: string;
24
25
  type: string;
@@ -57,5 +58,5 @@ export declare const createTransferWithAuthorization: (params: Omit<TransferWith
57
58
  validAfter: bigint | number;
58
59
  validBefore: bigint | number;
59
60
  nonce: bigint | number;
60
- }) => TransferWithAuthorization;
61
+ }) => TransferWithAuthorizationRecord;
61
62
  export declare const validateTransferWithAuthorization: (message: TransferWithAuthorization) => boolean;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Test Configuration
3
+ *
4
+ * Shared configuration for E2E tests including network configs,
5
+ * test keys, and facilitator URLs.
6
+ */
7
+ export declare const TEST_PRIVATE_KEY: `0x${string}`;
8
+ export declare const TEST_PAYER_ADDRESS: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1";
9
+ export declare const TEST_PAY_TO_ADDRESS: "0x1234567890123456789012345678901234567890";
10
+ export declare const TEST_CONTRACT_ADDRESS: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
11
+ export declare const TEST_NETWORK: "base-sepolia";
12
+ export declare const TEST_CHAIN_ID: 84532;
13
+ export declare const TEST_CAIP2_NETWORK: "eip155:84532";
14
+ export declare const TEST_ASSET_ID: "eip155:84532/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
15
+ export declare const TEST_AMOUNT: "1000000";
16
+ export declare const TEST_AMOUNT_DECIMAL: "1.0";
17
+ export declare const FACILITATOR_URL: string;
18
+ export declare const MOCK_FACILITATOR_PORT = 8899;
19
+ export declare const TEST_TIMEOUT_MS = 30000;
20
+ export declare const SERVER_STARTUP_DELAY_MS = 100;
21
+ export declare const SETTLEMENT_TIMEOUT_MS = 10000;
22
+ export declare const TEST_RPC_URL: string;
23
+ export interface TestPaymentConfig {
24
+ payTo: `0x${string}`;
25
+ amount: string;
26
+ network: string;
27
+ defaultVersion: 1 | 2;
28
+ accept: {
29
+ networks: string[];
30
+ tokens: string[];
31
+ facilitators: Array<{
32
+ url: string;
33
+ }>;
34
+ };
35
+ }
36
+ export declare const DEFAULT_PAYMENT_CONFIG: TestPaymentConfig;
37
+ /**
38
+ * Get a random port for test servers
39
+ */
40
+ export declare const getRandomPort: () => number;
41
+ /**
42
+ * Create a test nonce with timestamp
43
+ */
44
+ export declare const createTestNonce: (prefix: string) => string;
45
+ /**
46
+ * Sleep for specified milliseconds
47
+ */
48
+ export declare const sleep: (ms: number) => Promise<void>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Mock Payment Payloads for Testing
3
+ */
4
+ import type { X402PaymentPayloadV1, X402PayloadV2, PaymentPayloadV2 } from "@armory-sh/base";
5
+ export declare const TEST_PRIVATE_KEY = "0x0000000000000000000000000000000000000000000000000000000000000000000001";
6
+ export declare const TEST_PAYER_ADDRESS = "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1";
7
+ export declare const TEST_PAY_TO_ADDRESS = "0x1234567890123456789012345678901234567890";
8
+ export declare const TEST_CONTRACT_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
9
+ export declare const createX402V1Payload: (nonce?: string) => X402PaymentPayloadV1;
10
+ export declare const createFaremeterPayload: (nonce?: string) => X402PaymentPayloadV1;
11
+ export declare const createX402V2Payload: (nonce?: string) => PaymentPayloadV2;
12
+ export declare const createLegacyV2Payload: (nonce?: string) => X402PayloadV2;
13
+ export declare const INVALID_PAYLOADS: {
14
+ readonly missingFields: {};
15
+ readonly invalidAddress: PaymentPayloadV2;
16
+ readonly expired: PaymentPayloadV2;
17
+ readonly malformedSignature: PaymentPayloadV2;
18
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Mock Payment Requirements for Testing
3
+ */
4
+ import type { X402PaymentRequirementsV1, PaymentRequirementsV2 } from "@armory-sh/base";
5
+ export declare const MOCK_X402_V1_REQUIREMENTS: X402PaymentRequirementsV1;
6
+ export declare const createX402V1Requirements: (overrides?: Partial<X402PaymentRequirementsV1>) => X402PaymentRequirementsV1;
7
+ export declare const MOCK_X402_V2_REQUIREMENTS: PaymentRequirementsV2;
8
+ export declare const createX402V2Requirements: (overrides?: Partial<PaymentRequirementsV2>) => PaymentRequirementsV2;
9
+ export declare const INVALID_REQUIREMENTS: {
10
+ readonly missingAmount: {
11
+ scheme: "exact";
12
+ network: import("@armory-sh/base").CAIP2ChainId;
13
+ amount: string;
14
+ asset: import("@armory-sh/base").Address;
15
+ payTo: import("@armory-sh/base").Address;
16
+ maxTimeoutSeconds: number;
17
+ extra?: import("@armory-sh/base").Extensions;
18
+ };
19
+ readonly invalidNetwork: {
20
+ readonly scheme: "exact";
21
+ readonly network: `eip155:${string}`;
22
+ readonly amount: "1000000";
23
+ readonly asset: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
24
+ readonly payTo: "0x1234567890123456789012345678901234567890";
25
+ readonly maxTimeoutSeconds: 300;
26
+ };
27
+ readonly invalidAsset: {
28
+ readonly scheme: "exact";
29
+ readonly network: "eip155:84532";
30
+ readonly amount: "1000000";
31
+ readonly asset: `0x${string}`;
32
+ readonly payTo: "0x1234567890123456789012345678901234567890";
33
+ readonly maxTimeoutSeconds: 300;
34
+ };
35
+ };
package/dist/index.d.ts CHANGED
@@ -17,4 +17,4 @@ export type { TypedDataDomain, EIP712Domain, TransferWithAuthorization, TypedDat
17
17
  export { EIP712_TYPES, USDC_DOMAIN, createEIP712Domain, createTransferWithAuthorization, validateTransferWithAuthorization, } from "./eip712";
18
18
  export { resolveNetwork, resolveToken, resolveFacilitator, checkFacilitatorSupport, validatePaymentConfig, validateAcceptConfig, getAvailableNetworks, getAvailableTokens, isValidationError, isResolvedNetwork, isResolvedToken, createError, normalizeNetworkName, } from "./validation";
19
19
  export { encodePaymentV1, decodePaymentV1, encodeSettlementV1, decodeSettlementV1, encodePaymentV2, decodePaymentV2, encodeSettlementV2, decodeSettlementV2, detectPaymentVersion as detectPaymentVersionLegacy, decodePayment as decodePaymentLegacy, decodeSettlement as decodeSettlementLegacy, isPaymentV1, isPaymentV2, isSettlementV1, isSettlementV2, } from "./encoding";
20
- export type { NetworkId, TokenId, FacilitatorConfig, AcceptPaymentOptions, PricingConfig, PaymentResult, PaymentError, PaymentErrorCode, ArmoryPaymentResult, ResolvedNetwork, ResolvedToken, ResolvedFacilitator, ResolvedPaymentConfig, ValidationError, } from "./types/simple";
20
+ export type { NetworkId, TokenId, FacilitatorConfig, FacilitatorVerifyResult, FacilitatorSettleResult, SettlementMode, PayToAddress, AcceptPaymentOptions, PricingConfig, PaymentResult, PaymentError, PaymentErrorCode, ArmoryPaymentResult, ResolvedNetwork, ResolvedToken, ResolvedFacilitator, ResolvedPaymentConfig, ValidationError, } from "./types/simple";
package/dist/index.js CHANGED
@@ -354,7 +354,7 @@ function isPaymentRequiredV2(obj) {
354
354
  return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "resource" in obj && "accepts" in obj && Array.isArray(obj.accepts);
355
355
  }
356
356
  function isPaymentPayloadV2(obj) {
357
- return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "accepted" in obj && "payload" in obj;
357
+ return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "scheme" in obj && "network" in obj && "payload" in obj;
358
358
  }
359
359
  function assetIdToAddress(assetId) {
360
360
  const match = assetId.match(/\/erc20:(0x[a-fA-F0-9]{40})$/);
@@ -386,7 +386,7 @@ function isX402V1Payload(obj) {
386
386
  return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 1 && "scheme" in obj && "network" in obj && "payload" in obj;
387
387
  }
388
388
  function isX402V2Payload(obj) {
389
- return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "accepted" in obj && "payload" in obj;
389
+ return typeof obj === "object" && obj !== null && "x402Version" in obj && obj.x402Version === 2 && "scheme" in obj && "network" in obj && "payload" in obj;
390
390
  }
391
391
  function isLegacyV1Payload(obj) {
392
392
  return typeof obj === "object" && obj !== null && "v" in obj && "r" in obj && "s" in obj && "chainId" in obj && "contractAddress" in obj && !("x402Version" in obj);
@@ -462,172 +462,20 @@ function isV2(obj) {
462
462
  }
463
463
 
464
464
  // src/types/networks.ts
465
- var tokenRegistry = /* @__PURE__ */ new Map();
466
- var tokenKey = (chainId, contractAddress) => `${chainId}:${contractAddress.toLowerCase()}`;
467
- var NETWORKS = {
468
- ethereum: {
469
- name: "Ethereum Mainnet",
470
- chainId: 1,
471
- usdcAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
472
- rpcUrl: "https://eth.llamarpc.com",
473
- caip2Id: "eip155:1",
474
- caipAssetId: "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
475
- },
476
- base: {
477
- name: "Base Mainnet",
478
- chainId: 8453,
479
- usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
480
- rpcUrl: "https://mainnet.base.org",
481
- caip2Id: "eip155:8453",
482
- caipAssetId: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
483
- },
484
- "base-sepolia": {
485
- name: "Base Sepolia",
486
- chainId: 84532,
487
- usdcAddress: "0x036CbD53842c5426634e7929541eA237834d2D14",
488
- rpcUrl: "https://sepolia.base.org",
489
- caip2Id: "eip155:84532",
490
- caipAssetId: "eip155:84532/erc20:0x036CbD53842c5426634e7929541eA237834d2D14"
491
- },
492
- "skale-base": {
493
- name: "SKALE Base",
494
- chainId: 1187947933,
495
- usdcAddress: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
496
- rpcUrl: "https://skale-base.skalenodes.com/v1/base",
497
- caip2Id: "eip155:1187947933",
498
- caipAssetId: "eip155:1187947933/erc20:0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20"
499
- },
500
- "skale-base-sepolia": {
501
- name: "SKALE Base Sepolia",
502
- chainId: 324705682,
503
- usdcAddress: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
504
- rpcUrl: "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",
505
- caip2Id: "eip155:324705682",
506
- caipAssetId: "eip155:324705682/erc20:0x2e08028E3C4c2356572E096d8EF835cD5C6030bD"
507
- },
508
- "ethereum-sepolia": {
509
- name: "Ethereum Sepolia",
510
- chainId: 11155111,
511
- usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
512
- rpcUrl: "https://rpc.sepolia.org",
513
- caip2Id: "eip155:11155111",
514
- caipAssetId: "eip155:11155111/erc20:0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
515
- }
516
- };
517
- var getNetworkConfig = (name) => NETWORKS[name];
518
- var getNetworkByChainId = (chainId) => Object.values(NETWORKS).find((c) => c.chainId === chainId);
519
- var getMainnets = () => Object.values(NETWORKS).filter((c) => !c.name.toLowerCase().includes("sepolia"));
520
- var getTestnets = () => Object.values(NETWORKS).filter((c) => c.name.toLowerCase().includes("sepolia"));
521
- var registerToken = (token) => {
522
- tokenRegistry.set(tokenKey(token.chainId, token.contractAddress), token);
523
- return token;
524
- };
525
- var getCustomToken = (chainId, contractAddress) => tokenRegistry.get(tokenKey(chainId, contractAddress));
526
- var getAllCustomTokens = () => Array.from(tokenRegistry.values());
527
- var unregisterToken = (chainId, contractAddress) => tokenRegistry.delete(tokenKey(chainId, contractAddress));
528
- var isCustomToken = (chainId, contractAddress) => tokenRegistry.has(tokenKey(chainId, contractAddress));
529
-
530
- // src/abi/erc20.ts
531
- var ERC20_ABI = [
532
- {
533
- type: "function",
534
- name: "transferWithAuthorization",
535
- stateMutability: "nonpayable",
536
- inputs: [
537
- { name: "from", type: "address" },
538
- { name: "to", type: "address" },
539
- { name: "amount", type: "uint256" },
540
- { name: "validAfter", type: "uint256" },
541
- { name: "expiry", type: "uint256" },
542
- { name: "v", type: "uint8" },
543
- { name: "r", type: "bytes32" },
544
- { name: "s", type: "bytes32" }
545
- ],
546
- outputs: []
547
- },
548
- {
549
- type: "function",
550
- name: "receiveWithAuthorization",
551
- stateMutability: "nonpayable",
552
- inputs: [
553
- { name: "from", type: "address" },
554
- { name: "to", type: "address" },
555
- { name: "amount", type: "uint256" },
556
- { name: "validAfter", type: "uint256" },
557
- { name: "expiry", type: "uint256" },
558
- { name: "v", type: "uint8" },
559
- { name: "r", type: "bytes32" },
560
- { name: "s", type: "bytes32" }
561
- ],
562
- outputs: []
563
- },
564
- {
565
- type: "function",
566
- name: "balanceOf",
567
- stateMutability: "view",
568
- inputs: [{ name: "account", type: "address" }],
569
- outputs: [{ name: "balance", type: "uint256" }]
570
- },
571
- {
572
- type: "function",
573
- name: "name",
574
- stateMutability: "view",
575
- inputs: [],
576
- outputs: [{ name: "", type: "string" }]
577
- },
578
- {
579
- type: "function",
580
- name: "symbol",
581
- stateMutability: "view",
582
- inputs: [],
583
- outputs: [{ name: "", type: "string" }]
584
- }
585
- ];
586
-
587
- // src/eip712.ts
588
- var EIP712_TYPES = {
589
- TransferWithAuthorization: [
590
- { name: "from", type: "address" },
591
- { name: "to", type: "address" },
592
- { name: "value", type: "uint256" },
593
- { name: "validAfter", type: "uint256" },
594
- { name: "validBefore", type: "uint256" },
595
- { name: "nonce", type: "uint256" }
596
- ]
597
- };
598
- var USDC_DOMAIN = {
599
- NAME: "USD Coin",
600
- VERSION: "2"
601
- };
602
- var createEIP712Domain = (chainId, contractAddress) => ({
603
- name: USDC_DOMAIN.NAME,
604
- version: USDC_DOMAIN.VERSION,
605
- chainId,
606
- verifyingContract: contractAddress
607
- });
608
- var createTransferWithAuthorization = (params) => ({
609
- from: params.from,
610
- to: params.to,
611
- value: BigInt(params.value),
612
- validAfter: BigInt(params.validAfter),
613
- validBefore: BigInt(params.validBefore),
614
- nonce: BigInt(params.nonce)
615
- });
616
- var isAddress2 = (value) => /^0x[a-fA-F0-9]{40}$/.test(value);
617
- var validateTransferWithAuthorization = (message) => {
618
- if (!isAddress2(message.from)) throw new Error(`Invalid "from" address: ${message.from}`);
619
- if (!isAddress2(message.to)) throw new Error(`Invalid "to" address: ${message.to}`);
620
- if (message.value < 0n) throw new Error(`"value" must be non-negative: ${message.value}`);
621
- if (message.validAfter < 0n) throw new Error(`"validAfter" must be non-negative: ${message.validAfter}`);
622
- if (message.validBefore < 0n) throw new Error(`"validBefore" must be non-negative: ${message.validBefore}`);
623
- if (message.validAfter >= message.validBefore) {
624
- throw new Error(`"validAfter" (${message.validAfter}) must be before "validBefore" (${message.validBefore})`);
625
- }
626
- if (message.nonce < 0n) throw new Error(`"nonce" must be non-negative: ${message.nonce}`);
627
- return true;
628
- };
465
+ import { type as arkType2 } from "arktype";
629
466
 
630
467
  // src/validation.ts
468
+ import { type as arkType } from "arktype";
469
+ var CustomTokenSchema = arkType({
470
+ symbol: "string",
471
+ name: "string",
472
+ version: "string",
473
+ contractAddress: "/^0x[a-fA-F0-9]{40}$/",
474
+ chainId: "number.integer > 0",
475
+ decimals: "number.integer >= 0?"
476
+ });
477
+ var NetworkIdSchema = arkType("string | number | object");
478
+ var TokenIdSchema = arkType("string | number | object");
631
479
  var createError = (code, message, details) => ({
632
480
  code,
633
481
  message,
@@ -635,6 +483,14 @@ var createError = (code, message, details) => ({
635
483
  });
636
484
  var normalizeNetworkName = (name) => name.toLowerCase().replace(/\s+/g, "-").replace("-mainnet", "").replace(/-mainnet/, "").replace("mainnet-", "").replace(/-sepolia$/, "-sepolia").replace(/^-|-$/g, "");
637
485
  var resolveNetwork = (input) => {
486
+ const inputCheck = NetworkIdSchema(input);
487
+ if (inputCheck instanceof arkType.errors) {
488
+ return createError(
489
+ "VALIDATION_FAILED",
490
+ "Invalid network input type",
491
+ { value: input, validOptions: ["string (name/CAIP-2)", "number (chainId)", "NetworkConfig"] }
492
+ );
493
+ }
638
494
  if (typeof input === "object" && input !== null && "chainId" in input) {
639
495
  const config = input;
640
496
  if (!config.chainId || !config.usdcAddress || !config.caip2Id) {
@@ -701,6 +557,14 @@ var getAvailableNetworks = () => Object.keys(NETWORKS);
701
557
  var normalizeTokenSymbol = (symbol) => symbol.toUpperCase();
702
558
  var isEvmAddress = (value) => /^0x[a-fA-F0-9]{40}$/.test(value);
703
559
  var resolveToken = (input, network) => {
560
+ const inputCheck = TokenIdSchema(input);
561
+ if (inputCheck instanceof arkType.errors) {
562
+ return createError(
563
+ "VALIDATION_FAILED",
564
+ "Invalid token input type",
565
+ { value: input, validOptions: ["string (symbol/address/CAIP Asset)", "number (deprecated)", "CustomToken"] }
566
+ );
567
+ }
704
568
  if (typeof input === "object" && input !== null && "contractAddress" in input) {
705
569
  const config = input;
706
570
  if (!config.chainId || !config.contractAddress || !config.symbol) {
@@ -800,7 +664,7 @@ var resolveToken = (input, network) => {
800
664
  if (network) {
801
665
  const customTokens2 = getAllCustomTokens();
802
666
  const matchingToken2 = customTokens2.find(
803
- (t) => t.symbol.toUpperCase() === normalizedSymbol && t.chainId === network.config.chainId
667
+ (t) => t.symbol?.toUpperCase() === normalizedSymbol && t.chainId === network.config.chainId
804
668
  );
805
669
  if (matchingToken2) {
806
670
  return {
@@ -825,7 +689,7 @@ var resolveToken = (input, network) => {
825
689
  };
826
690
  }
827
691
  const customTokens = getAllCustomTokens();
828
- const matchingToken = customTokens.find((t) => t.symbol.toUpperCase() === normalizedSymbol);
692
+ const matchingToken = customTokens.find((t) => t.symbol?.toUpperCase() === normalizedSymbol);
829
693
  if (matchingToken) {
830
694
  const tokenNetwork = resolveNetwork(matchingToken.chainId);
831
695
  if ("code" in tokenNetwork) {
@@ -1042,6 +906,176 @@ var isResolvedToken = (value) => {
1042
906
  return typeof value === "object" && value !== null && "config" in value && "caipAsset" in value;
1043
907
  };
1044
908
 
909
+ // src/types/networks.ts
910
+ var tokenRegistry = /* @__PURE__ */ new Map();
911
+ var tokenKey = (chainId, contractAddress) => `${chainId}:${contractAddress.toLowerCase()}`;
912
+ var NETWORKS = {
913
+ ethereum: {
914
+ name: "Ethereum Mainnet",
915
+ chainId: 1,
916
+ usdcAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
917
+ rpcUrl: "https://eth.llamarpc.com",
918
+ caip2Id: "eip155:1",
919
+ caipAssetId: "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
920
+ },
921
+ base: {
922
+ name: "Base Mainnet",
923
+ chainId: 8453,
924
+ usdcAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
925
+ rpcUrl: "https://mainnet.base.org",
926
+ caip2Id: "eip155:8453",
927
+ caipAssetId: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
928
+ },
929
+ "base-sepolia": {
930
+ name: "Base Sepolia",
931
+ chainId: 84532,
932
+ usdcAddress: "0x036CbD53842c5426634e7929541eA237834d2D14",
933
+ rpcUrl: "https://sepolia.base.org",
934
+ caip2Id: "eip155:84532",
935
+ caipAssetId: "eip155:84532/erc20:0x036CbD53842c5426634e7929541eA237834d2D14"
936
+ },
937
+ "skale-base": {
938
+ name: "SKALE Base",
939
+ chainId: 1187947933,
940
+ usdcAddress: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
941
+ rpcUrl: "https://skale-base.skalenodes.com/v1/base",
942
+ caip2Id: "eip155:1187947933",
943
+ caipAssetId: "eip155:1187947933/erc20:0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20"
944
+ },
945
+ "skale-base-sepolia": {
946
+ name: "SKALE Base Sepolia",
947
+ chainId: 324705682,
948
+ usdcAddress: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
949
+ rpcUrl: "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",
950
+ caip2Id: "eip155:324705682",
951
+ caipAssetId: "eip155:324705682/erc20:0x2e08028E3C4c2356572E096d8EF835cD5C6030bD"
952
+ },
953
+ "ethereum-sepolia": {
954
+ name: "Ethereum Sepolia",
955
+ chainId: 11155111,
956
+ usdcAddress: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
957
+ rpcUrl: "https://rpc.sepolia.org",
958
+ caip2Id: "eip155:11155111",
959
+ caipAssetId: "eip155:11155111/erc20:0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
960
+ }
961
+ };
962
+ var getNetworkConfig = (name) => NETWORKS[name];
963
+ var getNetworkByChainId = (chainId) => Object.values(NETWORKS).find((c) => c.chainId === chainId);
964
+ var getMainnets = () => Object.values(NETWORKS).filter((c) => !c.name.toLowerCase().includes("sepolia"));
965
+ var getTestnets = () => Object.values(NETWORKS).filter((c) => c.name.toLowerCase().includes("sepolia"));
966
+ var registerToken = (token) => {
967
+ const validated = CustomTokenSchema(token);
968
+ if (validated instanceof arkType2.errors) {
969
+ throw new Error(`Invalid token: ${validated.summary}`);
970
+ }
971
+ tokenRegistry.set(tokenKey(validated.chainId, validated.contractAddress), validated);
972
+ return validated;
973
+ };
974
+ var getCustomToken = (chainId, contractAddress) => tokenRegistry.get(tokenKey(chainId, contractAddress));
975
+ var getAllCustomTokens = () => Array.from(tokenRegistry.values());
976
+ var unregisterToken = (chainId, contractAddress) => tokenRegistry.delete(tokenKey(chainId, contractAddress));
977
+ var isCustomToken = (chainId, contractAddress) => tokenRegistry.has(tokenKey(chainId, contractAddress));
978
+
979
+ // src/abi/erc20.ts
980
+ var ERC20_ABI = [
981
+ {
982
+ type: "function",
983
+ name: "transferWithAuthorization",
984
+ stateMutability: "nonpayable",
985
+ inputs: [
986
+ { name: "from", type: "address" },
987
+ { name: "to", type: "address" },
988
+ { name: "amount", type: "uint256" },
989
+ { name: "validAfter", type: "uint256" },
990
+ { name: "expiry", type: "uint256" },
991
+ { name: "v", type: "uint8" },
992
+ { name: "r", type: "bytes32" },
993
+ { name: "s", type: "bytes32" }
994
+ ],
995
+ outputs: []
996
+ },
997
+ {
998
+ type: "function",
999
+ name: "receiveWithAuthorization",
1000
+ stateMutability: "nonpayable",
1001
+ inputs: [
1002
+ { name: "from", type: "address" },
1003
+ { name: "to", type: "address" },
1004
+ { name: "amount", type: "uint256" },
1005
+ { name: "validAfter", type: "uint256" },
1006
+ { name: "expiry", type: "uint256" },
1007
+ { name: "v", type: "uint8" },
1008
+ { name: "r", type: "bytes32" },
1009
+ { name: "s", type: "bytes32" }
1010
+ ],
1011
+ outputs: []
1012
+ },
1013
+ {
1014
+ type: "function",
1015
+ name: "balanceOf",
1016
+ stateMutability: "view",
1017
+ inputs: [{ name: "account", type: "address" }],
1018
+ outputs: [{ name: "balance", type: "uint256" }]
1019
+ },
1020
+ {
1021
+ type: "function",
1022
+ name: "name",
1023
+ stateMutability: "view",
1024
+ inputs: [],
1025
+ outputs: [{ name: "", type: "string" }]
1026
+ },
1027
+ {
1028
+ type: "function",
1029
+ name: "symbol",
1030
+ stateMutability: "view",
1031
+ inputs: [],
1032
+ outputs: [{ name: "", type: "string" }]
1033
+ }
1034
+ ];
1035
+
1036
+ // src/eip712.ts
1037
+ var EIP712_TYPES = {
1038
+ TransferWithAuthorization: [
1039
+ { name: "from", type: "address" },
1040
+ { name: "to", type: "address" },
1041
+ { name: "value", type: "uint256" },
1042
+ { name: "validAfter", type: "uint256" },
1043
+ { name: "validBefore", type: "uint256" },
1044
+ { name: "nonce", type: "uint256" }
1045
+ ]
1046
+ };
1047
+ var USDC_DOMAIN = {
1048
+ NAME: "USD Coin",
1049
+ VERSION: "2"
1050
+ };
1051
+ var createEIP712Domain = (chainId, contractAddress) => ({
1052
+ name: USDC_DOMAIN.NAME,
1053
+ version: USDC_DOMAIN.VERSION,
1054
+ chainId,
1055
+ verifyingContract: contractAddress
1056
+ });
1057
+ var createTransferWithAuthorization = (params) => ({
1058
+ from: params.from,
1059
+ to: params.to,
1060
+ value: BigInt(params.value),
1061
+ validAfter: BigInt(params.validAfter),
1062
+ validBefore: BigInt(params.validBefore),
1063
+ nonce: BigInt(params.nonce)
1064
+ });
1065
+ var isAddress2 = (value) => /^0x[a-fA-F0-9]{40}$/.test(value);
1066
+ var validateTransferWithAuthorization = (message) => {
1067
+ if (!isAddress2(message.from)) throw new Error(`Invalid "from" address: ${message.from}`);
1068
+ if (!isAddress2(message.to)) throw new Error(`Invalid "to" address: ${message.to}`);
1069
+ if (message.value < 0n) throw new Error(`"value" must be non-negative: ${message.value}`);
1070
+ if (message.validAfter < 0n) throw new Error(`"validAfter" must be non-negative: ${message.validAfter}`);
1071
+ if (message.validBefore < 0n) throw new Error(`"validBefore" must be non-negative: ${message.validBefore}`);
1072
+ if (message.validAfter >= message.validBefore) {
1073
+ throw new Error(`"validAfter" (${message.validAfter}) must be before "validBefore" (${message.validBefore})`);
1074
+ }
1075
+ if (message.nonce < 0n) throw new Error(`"nonce" must be non-negative: ${message.nonce}`);
1076
+ return true;
1077
+ };
1078
+
1045
1079
  // src/encoding.ts
1046
1080
  var base64Encode = (data) => Buffer.from(JSON.stringify(data)).toString("base64");
1047
1081
  var base64Decode = (encoded) => JSON.parse(Buffer.from(encoded, "base64").toString("utf-8"));
File without changes
@@ -12,14 +12,14 @@ export interface CustomToken {
12
12
  version: string;
13
13
  contractAddress: `0x${string}`;
14
14
  chainId: number;
15
- decimals: number;
15
+ decimals?: number;
16
16
  }
17
17
  export declare const NETWORKS: Record<string, NetworkConfig>;
18
18
  export declare const getNetworkConfig: (name: string) => NetworkConfig | undefined;
19
19
  export declare const getNetworkByChainId: (chainId: number) => NetworkConfig | undefined;
20
20
  export declare const getMainnets: () => NetworkConfig[];
21
21
  export declare const getTestnets: () => NetworkConfig[];
22
- export declare const registerToken: (token: CustomToken) => CustomToken;
22
+ export declare const registerToken: (token: unknown) => CustomToken;
23
23
  export declare const getCustomToken: (chainId: number, contractAddress: string) => CustomToken | undefined;
24
24
  export declare const getAllCustomTokens: () => CustomToken[];
25
25
  export declare const unregisterToken: (chainId: number, contractAddress: string) => boolean;
@@ -4,7 +4,7 @@
4
4
  * Handles both x402 V1 and V2 formats along with legacy Armory formats
5
5
  */
6
6
  import type { X402PaymentPayloadV1, X402PaymentRequiredV1, X402PaymentRequirementsV1, X402SettlementResponseV1, LegacyPaymentPayloadV1, LegacyPaymentRequirementsV1, LegacySettlementResponseV1 } from "./v1";
7
- import type { PaymentPayloadV2, PaymentRequirementsV2, SettlementResponseV2, PaymentRequiredV2 } from "./v2";
7
+ import type { PaymentPayloadV2, PaymentRequirementsV2, SettlementResponseV2, PaymentRequiredV2, Address } from "./v2";
8
8
  /**
9
9
  * All x402 compatible payment payload types
10
10
  */
@@ -17,16 +17,57 @@ export type PaymentRequirements = X402PaymentRequirementsV1 | PaymentRequirement
17
17
  * All x402 compatible settlement response types
18
18
  */
19
19
  export type SettlementResponse = X402SettlementResponseV1 | SettlementResponseV2 | LegacySettlementResponseV1;
20
+ /**
21
+ * Configuration for connecting to a facilitator service
22
+ */
23
+ export interface FacilitatorConfig {
24
+ url: string;
25
+ createHeaders?: () => Record<string, string>;
26
+ }
27
+ /**
28
+ * Result from facilitator verification
29
+ */
30
+ export interface FacilitatorVerifyResult {
31
+ success: boolean;
32
+ payerAddress?: string;
33
+ balance?: string;
34
+ requiredAmount?: string;
35
+ error?: string;
36
+ }
37
+ /**
38
+ * Result from facilitator settlement
39
+ */
40
+ export interface FacilitatorSettleResult {
41
+ success: boolean;
42
+ txHash?: string;
43
+ error?: string;
44
+ }
45
+ /**
46
+ * Settlement mode - verify only, settle only, or both
47
+ */
48
+ export type SettlementMode = "verify" | "settle" | "async";
49
+ /**
50
+ * Payment destination - address, CAIP-2 chain ID, or CAIP asset ID
51
+ */
52
+ export type PayToAddress = Address | CAIP2ChainId | CAIPAssetId;
20
53
  /**
21
54
  * All x402 compatible payment required response types
22
55
  */
23
56
  export type PaymentRequired = X402PaymentRequiredV1 | PaymentRequiredV2;
57
+ /**
58
+ * CAIP-2 chain ID type (e.g., eip155:8453)
59
+ */
60
+ export type CAIP2ChainId = `eip155:${string}`;
61
+ /**
62
+ * CAIP-2 asset ID type (e.g., eip155:8453/erc20:0xa0b8691...)
63
+ */
64
+ export type CAIPAssetId = `eip155:${string}/erc20:${string}`;
24
65
  /**
25
66
  * Check if payload is x402 V1 format
26
67
  */
27
68
  export declare function isX402V1Payload(obj: unknown): obj is X402PaymentPayloadV1;
28
69
  /**
29
- * Check if payload is x402 V2 format
70
+ * Check if payload is x402 V2 format (Coinbase format)
30
71
  */
31
72
  export declare function isX402V2Payload(obj: unknown): obj is PaymentPayloadV2;
32
73
  /**
@@ -31,6 +31,40 @@ export interface FacilitatorConfig {
31
31
  /** Tokens this facilitator supports (optional) */
32
32
  tokens?: TokenId[];
33
33
  }
34
+ /**
35
+ * Result from facilitator verification
36
+ */
37
+ export interface FacilitatorVerifyResult {
38
+ success: boolean;
39
+ payerAddress?: string;
40
+ balance?: string;
41
+ requiredAmount?: string;
42
+ error?: string;
43
+ }
44
+ /**
45
+ * Result from facilitator settlement
46
+ */
47
+ export interface FacilitatorSettleResult {
48
+ success: boolean;
49
+ txHash?: string;
50
+ error?: string;
51
+ }
52
+ /**
53
+ * Settlement mode - verify only, settle only, or both
54
+ */
55
+ export type SettlementMode = "verify" | "settle" | "async";
56
+ /**
57
+ * CAIP-2 chain ID type (e.g., eip155:8453)
58
+ */
59
+ export type CAIP2ChainId = `eip155:${string}`;
60
+ /**
61
+ * CAIP-2 asset ID type (e.g., eip155:8453/erc20:0xa0b8691...)
62
+ */
63
+ export type CAIP2AssetId = `eip155:${string}/erc20:${string}`;
64
+ /**
65
+ * Payment destination - address, CAIP-2 chain ID, or CAIP asset ID
66
+ */
67
+ export type PayToAddress = `0x${string}` | CAIP2ChainId | CAIP2AssetId;
34
68
  /**
35
69
  * Pricing configuration for a specific network/token/facilitator combination
36
70
  */
@@ -18,6 +18,7 @@ export type PayToV2 = Address | {
18
18
  role?: string;
19
19
  callback?: string;
20
20
  };
21
+ export type PayToAddress = Address | PayToV2;
21
22
  export interface Extensions {
22
23
  [key: string]: unknown;
23
24
  }
@@ -97,17 +98,19 @@ export interface SchemePayloadV2 {
97
98
  }
98
99
  /**
99
100
  * Payment payload sent by client
100
- * Matches x402 V2 PaymentPayload spec
101
+ * Matches x402 V2 PaymentPayload spec (Coinbase format)
101
102
  */
102
103
  export interface PaymentPayloadV2 {
103
104
  /** Protocol version identifier */
104
105
  x402Version: 2;
105
- /** Resource being accessed (echoed from server) */
106
- resource?: ResourceInfo;
107
- /** Selected payment requirement from server's accepts array */
108
- accepted: PaymentRequirementsV2;
106
+ /** Payment scheme (e.g., "exact") */
107
+ scheme: string;
108
+ /** Network identifier in CAIP-2 format */
109
+ network: string;
109
110
  /** Scheme-specific payment data */
110
111
  payload: SchemePayloadV2;
112
+ /** Resource being accessed (optional, echoed from server) */
113
+ resource?: ResourceInfo;
111
114
  /** Protocol extensions data */
112
115
  extensions?: Extensions;
113
116
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Test Utilities Index
3
+ *
4
+ * Re-exports commonly used test utilities
5
+ */
6
+ export * from "./mock-facilitator";
7
+ export * from "../../fixtures/config";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Mock Facilitator for Testing
3
+ */
4
+ import type { PaymentPayload } from "@armory-sh/base";
5
+ export interface FacilitatorVerifyRequest {
6
+ payload: PaymentPayload;
7
+ requirements: Record<string, unknown>;
8
+ options?: Record<string, unknown>;
9
+ }
10
+ export interface FacilitatorVerifyResponse {
11
+ success: boolean;
12
+ payerAddress?: string;
13
+ balance?: string;
14
+ requiredAmount?: string;
15
+ error?: string;
16
+ }
17
+ export interface FacilitatorSettleRequest {
18
+ payload: PaymentPayload;
19
+ requirements: Record<string, unknown>;
20
+ }
21
+ export interface FacilitatorSettleResponse {
22
+ success: boolean;
23
+ txHash?: string;
24
+ error?: string;
25
+ }
26
+ export type AnyFacilitatorRequest = FacilitatorVerifyRequest | FacilitatorSettleRequest;
27
+ export type AnyFacilitatorResponse = FacilitatorVerifyResponse | FacilitatorSettleResponse;
28
+ export interface MockFacilitatorOptions {
29
+ port?: number;
30
+ alwaysVerify?: boolean;
31
+ alwaysSettle?: boolean;
32
+ verifyDelay?: number;
33
+ settleDelay?: number;
34
+ }
35
+ export declare const createMockFacilitator: (options?: MockFacilitatorOptions) => Promise<{
36
+ url: string;
37
+ close: () => Promise<void>;
38
+ }>;
@@ -2,45 +2,26 @@
2
2
  * Comprehensive validation for Armory configurations
3
3
  * Ensures networks, tokens, and facilitators are compatible
4
4
  */
5
- import type { NetworkId, TokenId, FacilitatorConfig, ResolvedNetwork, ResolvedToken, ResolvedFacilitator, ResolvedPaymentConfig, ValidationError, PaymentErrorCode } from "./types/simple.js";
5
+ import type { NetworkId, TokenId, FacilitatorConfig, ResolvedNetwork, ResolvedToken, ResolvedFacilitator, ResolvedPaymentConfig, ValidationError, PaymentErrorCode } from "./types/simple";
6
+ export declare const CustomTokenSchema: import("arktype/internal/variants/object.ts").ObjectType<{
7
+ symbol: string;
8
+ name: string;
9
+ version: string;
10
+ contractAddress: `0x${string}`;
11
+ chainId: number;
12
+ decimals?: number | undefined;
13
+ }, {}>;
6
14
  export declare const createError: (code: PaymentErrorCode, message: string, details?: Partial<ValidationError>) => ValidationError;
7
- /**
8
- * Normalize network name to match registry keys
9
- */
10
15
  export declare const normalizeNetworkName: (name: string) => string;
11
- /**
12
- * Resolve a network identifier to a network config
13
- */
14
- export declare const resolveNetwork: (input: NetworkId) => ResolvedNetwork | ValidationError;
15
- /**
16
- * Get all available network names
17
- */
16
+ export declare const resolveNetwork: (input: unknown) => ResolvedNetwork | ValidationError;
18
17
  export declare const getAvailableNetworks: () => string[];
19
- /**
20
- * Resolve a token identifier to a token config
21
- */
22
- export declare const resolveToken: (input: TokenId, network?: ResolvedNetwork) => ResolvedToken | ValidationError;
23
- /**
24
- * Get all available token symbols
25
- */
18
+ export declare const resolveToken: (input: unknown, network?: ResolvedNetwork) => ResolvedToken | ValidationError;
26
19
  export declare const getAvailableTokens: () => string[];
27
- /**
28
- * Resolve a facilitator configuration
29
- */
30
20
  export declare const resolveFacilitator: (input: FacilitatorConfig, supportedNetworks?: ResolvedNetwork[], supportedTokens?: ResolvedToken[]) => ResolvedFacilitator | ValidationError;
31
- /**
32
- * Check if a facilitator supports a specific network/token combination
33
- */
34
21
  export declare const checkFacilitatorSupport: (facilitator: ResolvedFacilitator, network: ResolvedNetwork, token: ResolvedToken) => ValidationError | {
35
22
  supported: true;
36
23
  };
37
- /**
38
- * Validate a complete payment configuration
39
- */
40
24
  export declare const validatePaymentConfig: (network: NetworkId, token: TokenId, facilitators?: FacilitatorConfig | FacilitatorConfig[], payTo?: string, amount?: string) => ResolvedPaymentConfig | ValidationError;
41
- /**
42
- * Validate multi-accept configuration (for merchants)
43
- */
44
25
  export declare const validateAcceptConfig: (options: {
45
26
  networks?: NetworkId[];
46
27
  tokens?: TokenId[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armory-sh/base",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "license": "MIT",
5
5
  "author": "Sawyer Cutler <sawyer@dirtroad.dev>",
6
6
  "type": "module",
@@ -27,6 +27,7 @@
27
27
  "directory": "packages/core"
28
28
  },
29
29
  "dependencies": {
30
+ "arktype": "^2.1.29",
30
31
  "viem": "2.45.0"
31
32
  },
32
33
  "devDependencies": {
@@ -35,7 +36,8 @@
35
36
  "typescript": "5.9.3"
36
37
  },
37
38
  "scripts": {
38
- "build": "tsup && tsc --emitDeclarationOnly",
39
- "test": "bun test"
39
+ "build": "rm -rf dist && tsup && tsc --emitDeclarationOnly",
40
+ "test": "bun test",
41
+ "typecheck": "tsc --noEmit"
40
42
  }
41
- }
43
+ }
@@ -1,5 +0,0 @@
1
- /**
2
- * Comprehensive unit tests for validation layer
3
- * Tests all input formats, cross-checks, and error messages
4
- */
5
- export {};