@4mica/sdk 0.5.6 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/signing.js CHANGED
@@ -1,43 +1,20 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PaymentSigner = exports.GUARANTEE_EIP712_TYPES = exports.CorePublicParameters = void 0;
3
+ exports.PaymentSigner = exports.GUARANTEE_EIP712_TYPES_V2 = exports.GUARANTEE_EIP712_TYPES = exports.computeValidationRequestHash = exports.computeValidationSubjectHash = void 0;
4
4
  exports.validateGuaranteeTypedData = validateGuaranteeTypedData;
5
5
  exports.validateGuaranteeSigningContext = validateGuaranteeSigningContext;
6
6
  exports.buildGuaranteeTypedData = buildGuaranteeTypedData;
7
+ exports.buildGuaranteeTypedDataV2 = buildGuaranteeTypedDataV2;
8
+ exports.encodeGuaranteeEip191V2 = encodeGuaranteeEip191V2;
7
9
  exports.encodeGuaranteeEip191 = encodeGuaranteeEip191;
8
10
  const viem_1 = require("viem");
11
+ var validation_1 = require("./validation");
12
+ Object.defineProperty(exports, "computeValidationSubjectHash", { enumerable: true, get: function () { return validation_1.computeValidationSubjectHash; } });
13
+ Object.defineProperty(exports, "computeValidationRequestHash", { enumerable: true, get: function () { return validation_1.computeValidationRequestHash; } });
9
14
  const errors_1 = require("./errors");
10
15
  const models_1 = require("./models");
11
16
  const utils_1 = require("./utils");
12
17
  const serde_1 = require("./serde");
13
- class CorePublicParameters {
14
- publicKey;
15
- contractAddress;
16
- ethereumHttpRpcUrl;
17
- eip712Name;
18
- eip712Version;
19
- chainId;
20
- constructor(publicKey, contractAddress, ethereumHttpRpcUrl, eip712Name, eip712Version, chainId) {
21
- this.publicKey = publicKey;
22
- this.contractAddress = contractAddress;
23
- this.ethereumHttpRpcUrl = ethereumHttpRpcUrl;
24
- this.eip712Name = eip712Name;
25
- this.eip712Version = eip712Version;
26
- this.chainId = chainId;
27
- }
28
- static fromRpc(payload) {
29
- const pkRaw = payload.public_key ?? payload.publicKey;
30
- const pk = typeof pkRaw === 'string'
31
- ? (0, viem_1.toBytes)(pkRaw)
32
- : pkRaw instanceof Uint8Array
33
- ? pkRaw
34
- : Array.isArray(pkRaw)
35
- ? Uint8Array.from(pkRaw)
36
- : new Uint8Array();
37
- return new CorePublicParameters(pk, String(payload.contract_address ?? payload.contractAddress ?? ''), String(payload.ethereum_http_rpc_url ?? payload.ethereumHttpRpcUrl ?? ''), (payload.eip712_name ?? payload.eip712Name ?? '4Mica'), (payload.eip712_version ?? payload.eip712Version ?? '1'), Number(payload.chain_id ?? payload.chainId));
38
- }
39
- }
40
- exports.CorePublicParameters = CorePublicParameters;
41
18
  exports.GUARANTEE_EIP712_TYPES = {
42
19
  EIP712Domain: [
43
20
  { name: 'name', type: 'string' },
@@ -54,6 +31,30 @@ exports.GUARANTEE_EIP712_TYPES = {
54
31
  { name: 'timestamp', type: 'uint64' },
55
32
  ],
56
33
  };
34
+ exports.GUARANTEE_EIP712_TYPES_V2 = {
35
+ EIP712Domain: [
36
+ { name: 'name', type: 'string' },
37
+ { name: 'version', type: 'string' },
38
+ { name: 'chainId', type: 'uint256' },
39
+ ],
40
+ SolGuaranteeRequestClaimsV2: [
41
+ { name: 'user', type: 'address' },
42
+ { name: 'recipient', type: 'address' },
43
+ { name: 'tabId', type: 'uint256' },
44
+ { name: 'reqId', type: 'uint256' },
45
+ { name: 'amount', type: 'uint256' },
46
+ { name: 'asset', type: 'address' },
47
+ { name: 'timestamp', type: 'uint64' },
48
+ { name: 'validationRegistryAddress', type: 'address' },
49
+ { name: 'validationRequestHash', type: 'bytes32' },
50
+ { name: 'validationChainId', type: 'uint256' },
51
+ { name: 'validatorAddress', type: 'address' },
52
+ { name: 'validatorAgentId', type: 'uint256' },
53
+ { name: 'minValidationScore', type: 'uint8' },
54
+ { name: 'validationSubjectHash', type: 'bytes32' },
55
+ { name: 'requiredValidationTag', type: 'string' },
56
+ ],
57
+ };
57
58
  const fieldsMatch = (actual, expected) => {
58
59
  if (!Array.isArray(actual) || actual.length !== expected.length)
59
60
  return false;
@@ -165,6 +166,73 @@ function buildGuaranteeTypedData(params, claims) {
165
166
  },
166
167
  };
167
168
  }
169
+ function buildGuaranteeTypedDataV2(params, claims) {
170
+ return {
171
+ types: exports.GUARANTEE_EIP712_TYPES_V2,
172
+ primaryType: 'SolGuaranteeRequestClaimsV2',
173
+ domain: {
174
+ name: params.eip712Name,
175
+ version: params.eip712Version,
176
+ chainId: params.chainId,
177
+ },
178
+ message: {
179
+ user: claims.userAddress,
180
+ recipient: claims.recipientAddress,
181
+ tabId: claims.tabId,
182
+ reqId: claims.reqId,
183
+ amount: claims.amount,
184
+ asset: claims.assetAddress,
185
+ timestamp: BigInt(claims.timestamp),
186
+ validationRegistryAddress: claims.validationRegistryAddress,
187
+ validationRequestHash: (0, utils_1.ensureHexPrefix)(claims.validationRequestHash),
188
+ validationChainId: BigInt(claims.validationChainId),
189
+ validatorAddress: claims.validatorAddress,
190
+ validatorAgentId: claims.validatorAgentId,
191
+ minValidationScore: claims.minValidationScore,
192
+ validationSubjectHash: (0, utils_1.ensureHexPrefix)(claims.validationSubjectHash),
193
+ requiredValidationTag: claims.requiredValidationTag,
194
+ },
195
+ };
196
+ }
197
+ function encodeGuaranteeEip191V2(claims) {
198
+ return (0, viem_1.encodeAbiParameters)([
199
+ { type: 'address' },
200
+ { type: 'address' },
201
+ { type: 'uint256' },
202
+ { type: 'uint256' },
203
+ { type: 'uint256' },
204
+ { type: 'address' },
205
+ { type: 'uint64' },
206
+ { type: 'address' },
207
+ { type: 'bytes32' },
208
+ { type: 'uint256' },
209
+ { type: 'address' },
210
+ { type: 'uint256' },
211
+ { type: 'uint8' },
212
+ { type: 'bytes32' },
213
+ { type: 'string' },
214
+ ], [
215
+ claims.userAddress,
216
+ claims.recipientAddress,
217
+ claims.tabId,
218
+ claims.reqId,
219
+ claims.amount,
220
+ claims.assetAddress,
221
+ BigInt(claims.timestamp),
222
+ claims.validationRegistryAddress,
223
+ (claims.validationRequestHash.startsWith('0x')
224
+ ? claims.validationRequestHash
225
+ : `0x${claims.validationRequestHash}`),
226
+ BigInt(claims.validationChainId),
227
+ claims.validatorAddress,
228
+ claims.validatorAgentId,
229
+ claims.minValidationScore,
230
+ (claims.validationSubjectHash.startsWith('0x')
231
+ ? claims.validationSubjectHash
232
+ : `0x${claims.validationSubjectHash}`),
233
+ claims.requiredValidationTag,
234
+ ]);
235
+ }
168
236
  function encodeGuaranteeEip191(claims) {
169
237
  const payload = (0, viem_1.encodeAbiParameters)([
170
238
  { type: 'address' },
@@ -193,10 +261,21 @@ class PaymentSigner {
193
261
  async signRequest(params, claims, scheme = models_1.SigningScheme.EIP712) {
194
262
  try {
195
263
  validateGuaranteeSigningContext(params, claims, { signerAddress: this.signer.address });
264
+ const isV2 = claims instanceof models_1.PaymentGuaranteeRequestClaimsV2;
196
265
  if (scheme === models_1.SigningScheme.EIP712) {
197
266
  if (!this.signer.signTypedData) {
198
267
  throw new errors_1.SigningError('signTypedData is not supported for this account');
199
268
  }
269
+ if (isV2) {
270
+ const typed = buildGuaranteeTypedDataV2(params, claims);
271
+ const signature = await this.signer.signTypedData({
272
+ domain: typed.domain,
273
+ types: { SolGuaranteeRequestClaimsV2: typed.types.SolGuaranteeRequestClaimsV2 },
274
+ primaryType: typed.primaryType,
275
+ message: typed.message,
276
+ });
277
+ return { signature, scheme };
278
+ }
200
279
  const typed = buildGuaranteeTypedData(params, claims);
201
280
  const signature = await this.signer.signTypedData({
202
281
  domain: typed.domain,
@@ -210,7 +289,7 @@ class PaymentSigner {
210
289
  if (!this.signer.signMessage) {
211
290
  throw new errors_1.SigningError('signMessage is not supported for this account');
212
291
  }
213
- const message = encodeGuaranteeEip191(claims);
292
+ const message = isV2 ? encodeGuaranteeEip191V2(claims) : encodeGuaranteeEip191(claims);
214
293
  const signature = await this.signer.signMessage({ message });
215
294
  return { signature, scheme };
216
295
  }
package/dist/utils.d.ts CHANGED
@@ -5,6 +5,7 @@ export declare class ValidationError extends Error {
5
5
  export declare function validateUrl(raw: string): string;
6
6
  export declare function normalizePrivateKey(raw: string): string;
7
7
  export declare function normalizeAddress(raw: string): string;
8
+ export declare function ensureHexPrefix(value: string): Hex;
8
9
  export declare function parseU256(value: number | bigint | string): bigint;
9
10
  export declare function serializeU256(value: number | bigint | string): string;
10
11
  export declare function hexFromBytes(bytes: Uint8Array): Hex;
package/dist/utils.js CHANGED
@@ -4,6 +4,7 @@ exports.ValidationError = void 0;
4
4
  exports.validateUrl = validateUrl;
5
5
  exports.normalizePrivateKey = normalizePrivateKey;
6
6
  exports.normalizeAddress = normalizeAddress;
7
+ exports.ensureHexPrefix = ensureHexPrefix;
7
8
  exports.parseU256 = parseU256;
8
9
  exports.serializeU256 = serializeU256;
9
10
  exports.hexFromBytes = hexFromBytes;
@@ -16,16 +17,17 @@ class ValidationError extends Error {
16
17
  }
17
18
  exports.ValidationError = ValidationError;
18
19
  function validateUrl(raw) {
20
+ let url;
19
21
  try {
20
- const url = new URL(raw);
21
- if (!url.protocol || !url.host) {
22
- throw new Error('missing parts');
23
- }
24
- return raw;
22
+ url = new URL(raw);
25
23
  }
26
24
  catch {
27
25
  throw new ValidationError(`invalid URL: ${raw}`);
28
26
  }
27
+ if (!url.protocol || !url.host) {
28
+ throw new ValidationError(`invalid URL: ${raw}`);
29
+ }
30
+ return raw;
29
31
  }
30
32
  function normalizePrivateKey(raw) {
31
33
  const key = raw.startsWith('0x') ? raw.slice(2) : raw;
@@ -45,6 +47,9 @@ function normalizeAddress(raw) {
45
47
  }
46
48
  throw new ValidationError(`invalid address: ${raw}`);
47
49
  }
50
+ function ensureHexPrefix(value) {
51
+ return (value.startsWith('0x') ? value : `0x${value}`);
52
+ }
48
53
  function parseNumericString(raw) {
49
54
  const text = raw.trim();
50
55
  const n = BigInt(text);
@@ -58,6 +63,9 @@ function parseU256(value) {
58
63
  if (!Number.isFinite(value)) {
59
64
  throw new ValidationError('invalid integer');
60
65
  }
66
+ if (!Number.isSafeInteger(value)) {
67
+ throw new ValidationError('integer value exceeds safe precision; use bigint or hex string for large values');
68
+ }
61
69
  if (value < 0) {
62
70
  throw new ValidationError('u256 cannot be negative');
63
71
  }
@@ -0,0 +1,32 @@
1
+ import { type Hex } from 'viem';
2
+ import { PaymentGuaranteeRequestClaims, PaymentGuaranteeRequestClaimsV2 } from './models';
3
+ /** Binding domain string used as a prefix when hashing validation subjects. */
4
+ export declare const VALIDATION_SUBJECT_BINDING_DOMAIN = "4MICA_VALIDATION_SUBJECT_V1";
5
+ /** Binding domain string used as a prefix when hashing validation requests. */
6
+ export declare const VALIDATION_REQUEST_BINDING_DOMAIN = "4MICA_VALIDATION_REQUEST_V1";
7
+ /**
8
+ * Compute the `validationSubjectHash` for a V2 payment guarantee request.
9
+ *
10
+ * Binds the payment subject (tab, user, recipient, amount, asset, timestamp) to
11
+ * a keccak256 hash prefixed by {@link VALIDATION_SUBJECT_BINDING_DOMAIN} so that
12
+ * on-chain validators can verify which payment the validation refers to.
13
+ *
14
+ * @param claims - V1 base claims (the subject fields are the same for V1 and V2).
15
+ * @returns 32-byte keccak256 hash as a `0x`-prefixed hex string.
16
+ */
17
+ export declare function computeValidationSubjectHash(claims: PaymentGuaranteeRequestClaims): Hex;
18
+ /**
19
+ * Compute the `validationRequestHash` for a V2 payment guarantee request.
20
+ *
21
+ * Binds the full validation policy (chain, registry, validator, score threshold,
22
+ * subject hash, required tag) to a keccak256 hash prefixed by
23
+ * {@link VALIDATION_REQUEST_BINDING_DOMAIN}. This hash is included in the V2 claims
24
+ * that are BLS-signed by the core service, allowing on-chain verification that the
25
+ * validation policy was not tampered with.
26
+ *
27
+ * @param claims - V2 claims with all validation policy fields populated.
28
+ * The `validationSubjectHash` field must already be computed via
29
+ * {@link computeValidationSubjectHash} before calling this function.
30
+ * @returns 32-byte keccak256 hash as a `0x`-prefixed hex string.
31
+ */
32
+ export declare function computeValidationRequestHash(claims: PaymentGuaranteeRequestClaimsV2): Hex;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VALIDATION_REQUEST_BINDING_DOMAIN = exports.VALIDATION_SUBJECT_BINDING_DOMAIN = void 0;
4
+ exports.computeValidationSubjectHash = computeValidationSubjectHash;
5
+ exports.computeValidationRequestHash = computeValidationRequestHash;
6
+ const viem_1 = require("viem");
7
+ const utils_1 = require("./utils");
8
+ /** Binding domain string used as a prefix when hashing validation subjects. */
9
+ exports.VALIDATION_SUBJECT_BINDING_DOMAIN = '4MICA_VALIDATION_SUBJECT_V1';
10
+ /** Binding domain string used as a prefix when hashing validation requests. */
11
+ exports.VALIDATION_REQUEST_BINDING_DOMAIN = '4MICA_VALIDATION_REQUEST_V1';
12
+ /**
13
+ * Compute the `validationSubjectHash` for a V2 payment guarantee request.
14
+ *
15
+ * Binds the payment subject (tab, user, recipient, amount, asset, timestamp) to
16
+ * a keccak256 hash prefixed by {@link VALIDATION_SUBJECT_BINDING_DOMAIN} so that
17
+ * on-chain validators can verify which payment the validation refers to.
18
+ *
19
+ * @param claims - V1 base claims (the subject fields are the same for V1 and V2).
20
+ * @returns 32-byte keccak256 hash as a `0x`-prefixed hex string.
21
+ */
22
+ function computeValidationSubjectHash(claims) {
23
+ const bindingDomain = (0, viem_1.keccak256)(new TextEncoder().encode(exports.VALIDATION_SUBJECT_BINDING_DOMAIN));
24
+ const encoded = (0, viem_1.encodeAbiParameters)([
25
+ { type: 'bytes32' },
26
+ { type: 'uint256' },
27
+ { type: 'uint256' },
28
+ { type: 'address' },
29
+ { type: 'address' },
30
+ { type: 'uint256' },
31
+ { type: 'address' },
32
+ { type: 'uint64' },
33
+ ], [
34
+ bindingDomain,
35
+ claims.tabId,
36
+ claims.reqId,
37
+ claims.userAddress,
38
+ claims.recipientAddress,
39
+ claims.amount,
40
+ claims.assetAddress,
41
+ BigInt(claims.timestamp),
42
+ ]);
43
+ return (0, viem_1.keccak256)(encoded);
44
+ }
45
+ /**
46
+ * Compute the `validationRequestHash` for a V2 payment guarantee request.
47
+ *
48
+ * Binds the full validation policy (chain, registry, validator, score threshold,
49
+ * subject hash, required tag) to a keccak256 hash prefixed by
50
+ * {@link VALIDATION_REQUEST_BINDING_DOMAIN}. This hash is included in the V2 claims
51
+ * that are BLS-signed by the core service, allowing on-chain verification that the
52
+ * validation policy was not tampered with.
53
+ *
54
+ * @param claims - V2 claims with all validation policy fields populated.
55
+ * The `validationSubjectHash` field must already be computed via
56
+ * {@link computeValidationSubjectHash} before calling this function.
57
+ * @returns 32-byte keccak256 hash as a `0x`-prefixed hex string.
58
+ */
59
+ function computeValidationRequestHash(claims) {
60
+ const bindingDomain = (0, viem_1.keccak256)(new TextEncoder().encode(exports.VALIDATION_REQUEST_BINDING_DOMAIN));
61
+ const tagHash = (0, viem_1.keccak256)(new TextEncoder().encode(claims.requiredValidationTag));
62
+ const encoded = (0, viem_1.encodeAbiParameters)([
63
+ { type: 'bytes32' },
64
+ { type: 'uint256' },
65
+ { type: 'address' },
66
+ { type: 'address' },
67
+ { type: 'uint256' },
68
+ { type: 'bytes32' },
69
+ { type: 'uint8' },
70
+ { type: 'bytes32' },
71
+ ], [
72
+ bindingDomain,
73
+ BigInt(claims.validationChainId),
74
+ claims.validationRegistryAddress,
75
+ claims.validatorAddress,
76
+ claims.validatorAgentId,
77
+ (0, utils_1.ensureHexPrefix)(claims.validationSubjectHash),
78
+ claims.minValidationScore,
79
+ tagHash,
80
+ ]);
81
+ return (0, viem_1.keccak256)(encoded);
82
+ }
@@ -1,22 +1,90 @@
1
- import { PaymentGuaranteeRequestClaims, PaymentSignature, SigningScheme } from '../models';
1
+ import { PaymentGuaranteeRequestClaims, PaymentGuaranteeRequestClaimsV2, PaymentSignature, SigningScheme } from '../models';
2
2
  import { PaymentRequirementsV1, TabResponse, X402SignedPayment, X402SettledPayment, PaymentRequirementsV2, X402PaymentRequired, X402ResourceInfo } from './models';
3
3
  import type { FetchFn } from '../rpc';
4
4
  export * from './models';
5
+ /**
6
+ * Minimal signing interface required by {@link X402Flow}.
7
+ * Implemented by {@link UserClient} from the main SDK client.
8
+ */
5
9
  export interface FlowSigner {
6
- signPayment(claims: PaymentGuaranteeRequestClaims, scheme: SigningScheme): Promise<PaymentSignature>;
10
+ signPayment(claims: PaymentGuaranteeRequestClaims | PaymentGuaranteeRequestClaimsV2, scheme: SigningScheme): Promise<PaymentSignature>;
7
11
  }
12
+ /**
13
+ * Handles the x402 HTTP 402 payment protocol for 4Mica.
14
+ *
15
+ * Orchestrates the full client-side x402 flow: resolving a tab from the recipient's
16
+ * tab endpoint, building and signing payment claims (V1 or V2), and optionally settling
17
+ * the payment against a facilitator service.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const flow = X402Flow.fromClient(client);
22
+ * const signed = await flow.signPayment(paymentRequirements, userAddress);
23
+ * // attach signed.header as the X-PAYMENT header on the protected request
24
+ * ```
25
+ */
8
26
  export declare class X402Flow {
9
27
  private signer;
10
28
  private fetchFn;
29
+ /**
30
+ * @param signer - Payment signer, typically `client.user`.
31
+ * @param fetchFn - HTTP fetch implementation. Defaults to global `fetch`.
32
+ */
11
33
  constructor(signer: FlowSigner, fetchFn?: FetchFn);
34
+ /**
35
+ * Convenience factory — creates an `X402Flow` from the user sub-client of a `Client`.
36
+ *
37
+ * @param client - Any object with a `.user` property implementing {@link FlowSigner}.
38
+ */
12
39
  static fromClient(client: {
13
40
  user: FlowSigner;
14
41
  }): X402Flow;
42
+ /**
43
+ * Sign an x402 V1 payment.
44
+ *
45
+ * Resolves a tab from `paymentRequirements.extra.tabEndpoint`, builds V1 claims,
46
+ * signs them with EIP-712, and returns the base64-encoded payment header together
47
+ * with the raw payload and signature.
48
+ *
49
+ * @param paymentRequirements - V1 payment requirements from the `402 Payment Required` response.
50
+ * @param userAddress - Address of the paying user.
51
+ * @returns Signed payment ready to attach as the `X-PAYMENT` request header.
52
+ * @throws {@link X402Error} if the scheme is not a 4Mica scheme or the tab endpoint fails.
53
+ */
15
54
  signPayment(paymentRequirements: PaymentRequirementsV1, userAddress: string): Promise<X402SignedPayment>;
55
+ /**
56
+ * Sign an x402 V2 payment, optionally including a V2 validation policy.
57
+ *
58
+ * If `accepted.extra` contains all required validation policy fields
59
+ * (`validationRegistryAddress`, `validatorAddress`, `validatorAgentId`,
60
+ * `minValidationScore`, `validationChainId`), the claims are built as V2 with
61
+ * the computed `validationSubjectHash` and `validationRequestHash`. Otherwise
62
+ * falls back to V1 claims.
63
+ *
64
+ * @param paymentRequired - The original `402 Payment Required` response object.
65
+ * @param accepted - The accepted V2 payment requirements.
66
+ * @param userAddress - Address of the paying user.
67
+ * @returns Signed payment ready to attach as the `X-PAYMENT` request header.
68
+ * @throws {@link X402Error} if the scheme is not a 4Mica scheme or the tab endpoint fails.
69
+ */
16
70
  signPaymentV2(paymentRequired: X402PaymentRequired, accepted: PaymentRequirementsV2, userAddress: string): Promise<X402SignedPayment>;
71
+ /**
72
+ * Submit a signed payment to a facilitator for on-chain settlement.
73
+ *
74
+ * Sends a POST request to `{facilitatorUrl}/settle` with the payment header,
75
+ * decoded payload, and payment requirements. Use this when the protected resource
76
+ * requires facilitator-confirmed settlement before granting access.
77
+ *
78
+ * @param payment - Signed payment returned by {@link signPayment} or {@link signPaymentV2}.
79
+ * @param paymentRequirements - V1 payment requirements included in the settlement request.
80
+ * @param facilitatorUrl - Base URL of the facilitator service.
81
+ * @returns The original payment plus the raw settlement response from the facilitator.
82
+ * @throws {@link X402Error} if the facilitator returns a non-2xx response.
83
+ */
17
84
  settlePayment(payment: X402SignedPayment, paymentRequirements: PaymentRequirementsV1, facilitatorUrl: string): Promise<X402SettledPayment>;
18
85
  protected requestTab(x402Version: number, paymentRequirements: PaymentRequirementsV1 | PaymentRequirementsV2, userAddress: string, resource?: X402ResourceInfo): Promise<TabResponse>;
19
86
  protected buildClaims(requirements: PaymentRequirementsV1 | PaymentRequirementsV2, tab: TabResponse, userAddress: string): PaymentGuaranteeRequestClaims;
87
+ protected buildClaimsV2(requirements: PaymentRequirementsV2, tab: TabResponse, userAddress: string): PaymentGuaranteeRequestClaimsV2;
20
88
  private static validateScheme;
21
89
  private static decodePaymentHeader;
22
90
  }
@@ -19,17 +19,60 @@ const models_1 = require("../models");
19
19
  const utils_1 = require("../utils");
20
20
  const errors_1 = require("../errors");
21
21
  const payment_1 = require("../payment");
22
+ const validation_1 = require("../validation");
22
23
  __exportStar(require("./models"), exports);
24
+ function hasValidationPolicy(extra) {
25
+ return !!(extra?.validationRegistryAddress &&
26
+ extra?.validatorAddress &&
27
+ extra?.validatorAgentId !== undefined &&
28
+ extra?.minValidationScore !== undefined &&
29
+ extra?.validationChainId !== undefined);
30
+ }
31
+ /**
32
+ * Handles the x402 HTTP 402 payment protocol for 4Mica.
33
+ *
34
+ * Orchestrates the full client-side x402 flow: resolving a tab from the recipient's
35
+ * tab endpoint, building and signing payment claims (V1 or V2), and optionally settling
36
+ * the payment against a facilitator service.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * const flow = X402Flow.fromClient(client);
41
+ * const signed = await flow.signPayment(paymentRequirements, userAddress);
42
+ * // attach signed.header as the X-PAYMENT header on the protected request
43
+ * ```
44
+ */
23
45
  class X402Flow {
24
46
  signer;
25
47
  fetchFn;
48
+ /**
49
+ * @param signer - Payment signer, typically `client.user`.
50
+ * @param fetchFn - HTTP fetch implementation. Defaults to global `fetch`.
51
+ */
26
52
  constructor(signer, fetchFn = fetch) {
27
53
  this.signer = signer;
28
54
  this.fetchFn = fetchFn;
29
55
  }
56
+ /**
57
+ * Convenience factory — creates an `X402Flow` from the user sub-client of a `Client`.
58
+ *
59
+ * @param client - Any object with a `.user` property implementing {@link FlowSigner}.
60
+ */
30
61
  static fromClient(client) {
31
62
  return new X402Flow(client.user);
32
63
  }
64
+ /**
65
+ * Sign an x402 V1 payment.
66
+ *
67
+ * Resolves a tab from `paymentRequirements.extra.tabEndpoint`, builds V1 claims,
68
+ * signs them with EIP-712, and returns the base64-encoded payment header together
69
+ * with the raw payload and signature.
70
+ *
71
+ * @param paymentRequirements - V1 payment requirements from the `402 Payment Required` response.
72
+ * @param userAddress - Address of the paying user.
73
+ * @returns Signed payment ready to attach as the `X-PAYMENT` request header.
74
+ * @throws {@link X402Error} if the scheme is not a 4Mica scheme or the tab endpoint fails.
75
+ */
33
76
  async signPayment(paymentRequirements, userAddress) {
34
77
  X402Flow.validateScheme(paymentRequirements.scheme);
35
78
  const tab = await this.requestTab(1, paymentRequirements, userAddress);
@@ -45,10 +88,27 @@ class X402Flow {
45
88
  const header = Buffer.from(JSON.stringify(envelope)).toString('base64');
46
89
  return { header, payload: paymentPayload, signature };
47
90
  }
91
+ /**
92
+ * Sign an x402 V2 payment, optionally including a V2 validation policy.
93
+ *
94
+ * If `accepted.extra` contains all required validation policy fields
95
+ * (`validationRegistryAddress`, `validatorAddress`, `validatorAgentId`,
96
+ * `minValidationScore`, `validationChainId`), the claims are built as V2 with
97
+ * the computed `validationSubjectHash` and `validationRequestHash`. Otherwise
98
+ * falls back to V1 claims.
99
+ *
100
+ * @param paymentRequired - The original `402 Payment Required` response object.
101
+ * @param accepted - The accepted V2 payment requirements.
102
+ * @param userAddress - Address of the paying user.
103
+ * @returns Signed payment ready to attach as the `X-PAYMENT` request header.
104
+ * @throws {@link X402Error} if the scheme is not a 4Mica scheme or the tab endpoint fails.
105
+ */
48
106
  async signPaymentV2(paymentRequired, accepted, userAddress) {
49
107
  X402Flow.validateScheme(accepted.scheme);
50
108
  const tab = await this.requestTab(2, accepted, userAddress, paymentRequired.resource);
51
- const claims = this.buildClaims(accepted, tab, userAddress);
109
+ const claims = hasValidationPolicy(accepted.extra)
110
+ ? this.buildClaimsV2(accepted, tab, userAddress)
111
+ : this.buildClaims(accepted, tab, userAddress);
52
112
  const signature = await this.signer.signPayment(claims, models_1.SigningScheme.EIP712);
53
113
  const paymentPayload = (0, payment_1.buildPaymentPayload)(claims, signature);
54
114
  const envelope = {
@@ -60,6 +120,19 @@ class X402Flow {
60
120
  const header = Buffer.from(JSON.stringify(envelope)).toString('base64');
61
121
  return { header, payload: paymentPayload, signature };
62
122
  }
123
+ /**
124
+ * Submit a signed payment to a facilitator for on-chain settlement.
125
+ *
126
+ * Sends a POST request to `{facilitatorUrl}/settle` with the payment header,
127
+ * decoded payload, and payment requirements. Use this when the protected resource
128
+ * requires facilitator-confirmed settlement before granting access.
129
+ *
130
+ * @param payment - Signed payment returned by {@link signPayment} or {@link signPaymentV2}.
131
+ * @param paymentRequirements - V1 payment requirements included in the settlement request.
132
+ * @param facilitatorUrl - Base URL of the facilitator service.
133
+ * @returns The original payment plus the raw settlement response from the facilitator.
134
+ * @throws {@link X402Error} if the facilitator returns a non-2xx response.
135
+ */
63
136
  async settlePayment(payment, paymentRequirements, facilitatorUrl) {
64
137
  const url = `${facilitatorUrl.replace(/\/$/, '')}/settle`;
65
138
  const paymentPayload = X402Flow.decodePaymentHeader(payment.header);
@@ -119,6 +192,33 @@ class X402Flow {
119
192
  const timestamp = Math.floor(Date.now() / 1000);
120
193
  return models_1.PaymentGuaranteeRequestClaims.new(userAddress, (0, utils_1.normalizeAddress)(requirements.payTo), tabId, amount, timestamp, requirements.asset, reqId);
121
194
  }
195
+ buildClaimsV2(requirements, tab, userAddress) {
196
+ const base = this.buildClaims(requirements, tab, userAddress);
197
+ const extra = requirements.extra;
198
+ const validationSubjectHash = (0, validation_1.computeValidationSubjectHash)(base);
199
+ const partialClaims = new models_1.PaymentGuaranteeRequestClaimsV2({
200
+ userAddress: base.userAddress,
201
+ recipientAddress: base.recipientAddress,
202
+ tabId: base.tabId,
203
+ reqId: base.reqId,
204
+ amount: base.amount,
205
+ timestamp: base.timestamp,
206
+ assetAddress: base.assetAddress,
207
+ validationRegistryAddress: extra.validationRegistryAddress,
208
+ validationRequestHash: '0x' + '00'.repeat(32),
209
+ validationChainId: extra.validationChainId,
210
+ validatorAddress: extra.validatorAddress,
211
+ validatorAgentId: (0, utils_1.parseU256)(extra.validatorAgentId),
212
+ minValidationScore: extra.minValidationScore,
213
+ validationSubjectHash,
214
+ requiredValidationTag: extra.requiredValidationTag ?? '',
215
+ });
216
+ const validationRequestHash = (0, validation_1.computeValidationRequestHash)(partialClaims);
217
+ return new models_1.PaymentGuaranteeRequestClaimsV2({
218
+ ...partialClaims,
219
+ validationRequestHash,
220
+ });
221
+ }
122
222
  static validateScheme(scheme) {
123
223
  if (!scheme.toLowerCase().includes('4mica')) {
124
224
  throw new errors_1.X402Error(`invalid scheme: ${scheme}`);
@@ -1,5 +1,5 @@
1
1
  import { PaymentSignature } from '../models';
2
- import type { PaymentPayload, PaymentPayloadClaims } from '../payment';
2
+ import type { PaymentPayload } from '../payment';
3
3
  export interface PaymentRequirementsV1 {
4
4
  scheme: string;
5
5
  network: string;
@@ -25,13 +25,19 @@ export interface PaymentRequirementsV2 {
25
25
  export type PaymentRequirements = PaymentRequirementsV2;
26
26
  export interface PaymentRequirementsExtra {
27
27
  tabEndpoint?: string;
28
+ validationRegistryAddress?: string;
29
+ validationChainId?: number;
30
+ validatorAddress?: string;
31
+ validatorAgentId?: string;
32
+ minValidationScore?: number;
33
+ requiredValidationTag?: string;
28
34
  }
29
35
  export interface TabResponse {
30
36
  tabId: string;
31
37
  userAddress: string;
32
38
  nextReqId?: string;
33
39
  }
34
- export type X402PaymentPayloadClaims = PaymentPayloadClaims;
40
+ export type X402PaymentPayloadClaims = PaymentPayload['claims'];
35
41
  export type X402PaymentPayload = PaymentPayload;
36
42
  export interface X402PaymentEnvelopeV1 {
37
43
  x402Version: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4mica/sdk",
3
- "version": "0.5.6",
3
+ "version": "1.0.1",
4
4
  "description": "TypeScript SDK for interacting with the 4Mica payment network",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,11 +41,15 @@
41
41
  "scripts": {
42
42
  "build": "tsc -p tsconfig.build.json",
43
43
  "test": "vitest run",
44
+ "test:e2e": "vitest run --config vitest.e2e.config.ts",
45
+ "example:base-sepolia:x402-v2": "node_modules/.bin/tsx examples/base-sepolia-x402-facilitator-e2e.ts",
44
46
  "lint": "eslint . --ext .ts",
45
47
  "fmt": "prettier --check \"{src,tests}/**/*.{ts,js,json}\""
46
48
  },
47
49
  "dependencies": {
50
+ "@4mica/x402": "file:../x402-4mica/packages/typescript/x402",
48
51
  "@noble/curves": "^2.0.1",
52
+ "@quillai-network/wachai-validation-sdk": "^0.1.0",
49
53
  "viem": "^2.45.1"
50
54
  },
51
55
  "devDependencies": {
@@ -53,6 +57,7 @@
53
57
  "@types/node": "^25.2.0",
54
58
  "@typescript-eslint/eslint-plugin": "^8.54.0",
55
59
  "@typescript-eslint/parser": "^8.54.0",
60
+ "dotenv-cli": "^11.0.0",
56
61
  "eslint": "^9.39.2",
57
62
  "eslint-config-prettier": "^10.1.8",
58
63
  "eslint-plugin-react": "^7.37.5",