@4mica/sdk 0.5.5 → 1.0.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/README.md CHANGED
@@ -279,9 +279,9 @@ Notes:
279
279
 
280
280
  - `createTab(userAddress, recipientAddress, erc20Token?, ttl?)`
281
281
  - `getTabPaymentStatus(tabId)`
282
- - `issuePaymentGuarantee(claims, signature, scheme)`
282
+ - `issuePaymentGuarantee(claims, signature, scheme)` — accepts V1 or V2 claims
283
283
  - `verifyPaymentGuarantee(cert)`
284
- - `remunerate(cert)`
284
+ - `remunerate(cert)` — requires `@noble/curves` peer dependency
285
285
  - `listSettledTabs()`
286
286
  - `listPendingRemunerations()`
287
287
  - `getTab(tabId)`
@@ -302,10 +302,71 @@ Available under `client.rpc` (requires an admin API key):
302
302
  - `listAdminApiKeys()`
303
303
  - `revokeAdminApiKey(keyId)`
304
304
 
305
+ ### V2 Payment Guarantees (on-chain validation policy)
306
+
307
+ V2 guarantees attach an on-chain validation policy that lets a validator agent attest to the
308
+ quality/validity of a payment before it is remunerated. Use `PaymentGuaranteeRequestClaimsV2`
309
+ and the canonical hash helpers:
310
+
311
+ ```ts
312
+ import {
313
+ PaymentGuaranteeRequestClaimsV2,
314
+ computeValidationSubjectHash,
315
+ computeValidationRequestHash,
316
+ SigningScheme,
317
+ } from "@4mica/sdk";
318
+
319
+ // 1) Build base V1 claims first
320
+ const baseClaims = PaymentGuaranteeRequestClaims.new(
321
+ userAddress, recipientAddress, tabId, amount, timestamp, erc20Token, reqId
322
+ );
323
+
324
+ // 2) Compute canonical hashes
325
+ const validationSubjectHash = computeValidationSubjectHash(baseClaims);
326
+
327
+ const partialV2 = new PaymentGuaranteeRequestClaimsV2({
328
+ ...baseClaims,
329
+ validationRegistryAddress: "0x...",
330
+ validationRequestHash: "0x" + "00".repeat(32), // placeholder
331
+ validationChainId: 1,
332
+ validatorAddress: "0x...",
333
+ validatorAgentId: 1n,
334
+ minValidationScore: 80, // 1–100
335
+ validationSubjectHash,
336
+ requiredValidationTag: "my-tag",
337
+ });
338
+
339
+ const validationRequestHash = computeValidationRequestHash(partialV2);
340
+ const claimsV2 = new PaymentGuaranteeRequestClaimsV2({ ...partialV2, validationRequestHash });
341
+
342
+ // 3) Sign and issue
343
+ const { signature, scheme } = await client.user.signPayment(claimsV2, SigningScheme.EIP712);
344
+ const cert = await client.recipient.issuePaymentGuarantee(claimsV2, signature, scheme);
345
+ ```
346
+
305
347
  ## Error Handling
306
348
 
307
- All SDK errors extend `FourMicaError`. Common error types include `ConfigError`, `RpcError`,
308
- `SigningError`, `VerificationError`, `X402Error`, and `AuthError`.
349
+ All SDK errors extend `FourMicaError`. Import individual error classes to distinguish them:
350
+
351
+ ```ts
352
+ import {
353
+ ConfigError, // invalid ConfigBuilder input
354
+ RpcError, // 4Mica core service error (has .status and .body)
355
+ SigningError, // signing scheme unsupported or address mismatch
356
+ ContractError, // on-chain call failed or unexpected result
357
+ VerificationError, // BLS certificate decode/domain mismatch
358
+ X402Error, // x402 flow error (bad scheme, tab resolution, settlement)
359
+ AuthError, // base class for all auth errors
360
+ AuthMissingConfigError, // auth not configured when login() is called
361
+ } from "@4mica/sdk";
362
+
363
+ try {
364
+ await client.recipient.remunerate(cert);
365
+ } catch (err) {
366
+ if (err instanceof VerificationError) { /* bad cert */ }
367
+ if (err instanceof ContractError) { /* on-chain failure */ }
368
+ }
369
+ ```
309
370
 
310
371
  ## License
311
372
 
@@ -25,14 +25,6 @@ export declare const core4micaAbi: readonly [{
25
25
  readonly type: "bytes32";
26
26
  readonly internalType: "bytes32";
27
27
  }];
28
- }, {
29
- readonly name: "usdc_";
30
- readonly type: "address";
31
- readonly internalType: "address";
32
- }, {
33
- readonly name: "usdt_";
34
- readonly type: "address";
35
- readonly internalType: "address";
36
28
  }];
37
29
  readonly stateMutability: "nonpayable";
38
30
  }, {
@@ -73,26 +65,6 @@ export declare const core4micaAbi: readonly [{
73
65
  readonly internalType: "uint64";
74
66
  }];
75
67
  readonly stateMutability: "view";
76
- }, {
77
- readonly type: "function";
78
- readonly name: "USDC";
79
- readonly inputs: readonly [];
80
- readonly outputs: readonly [{
81
- readonly name: "";
82
- readonly type: "address";
83
- readonly internalType: "address";
84
- }];
85
- readonly stateMutability: "view";
86
- }, {
87
- readonly type: "function";
88
- readonly name: "USDT";
89
- readonly inputs: readonly [];
90
- readonly outputs: readonly [{
91
- readonly name: "";
92
- readonly type: "address";
93
- readonly internalType: "address";
94
- }];
95
- readonly stateMutability: "view";
96
68
  }, {
97
69
  readonly type: "function";
98
70
  readonly name: "authority";
@@ -37,16 +37,6 @@ exports.core4micaAbi = [
37
37
  },
38
38
  ],
39
39
  },
40
- {
41
- name: 'usdc_',
42
- type: 'address',
43
- internalType: 'address',
44
- },
45
- {
46
- name: 'usdt_',
47
- type: 'address',
48
- internalType: 'address',
49
- },
50
40
  ],
51
41
  stateMutability: 'nonpayable',
52
42
  },
@@ -99,32 +89,6 @@ exports.core4micaAbi = [
99
89
  ],
100
90
  stateMutability: 'view',
101
91
  },
102
- {
103
- type: 'function',
104
- name: 'USDC',
105
- inputs: [],
106
- outputs: [
107
- {
108
- name: '',
109
- type: 'address',
110
- internalType: 'address',
111
- },
112
- ],
113
- stateMutability: 'view',
114
- },
115
- {
116
- type: 'function',
117
- name: 'USDT',
118
- inputs: [],
119
- outputs: [
120
- {
121
- name: '',
122
- type: 'address',
123
- internalType: 'address',
124
- },
125
- ],
126
- stateMutability: 'view',
127
- },
128
92
  {
129
93
  type: 'function',
130
94
  name: 'authority',
@@ -48,6 +48,16 @@ export declare const erc20Abi: readonly [{
48
48
  readonly internalType: "uint256";
49
49
  }];
50
50
  readonly stateMutability: "view";
51
+ }, {
52
+ readonly type: "function";
53
+ readonly name: "decimals";
54
+ readonly inputs: readonly [];
55
+ readonly outputs: readonly [{
56
+ readonly name: "";
57
+ readonly type: "uint8";
58
+ readonly internalType: "uint8";
59
+ }];
60
+ readonly stateMutability: "view";
51
61
  }, {
52
62
  readonly type: "function";
53
63
  readonly name: "totalSupply";
package/dist/abi/erc20.js CHANGED
@@ -69,6 +69,19 @@ exports.erc20Abi = [
69
69
  ],
70
70
  stateMutability: 'view',
71
71
  },
72
+ {
73
+ type: 'function',
74
+ name: 'decimals',
75
+ inputs: [],
76
+ outputs: [
77
+ {
78
+ name: '',
79
+ type: 'uint8',
80
+ internalType: 'uint8',
81
+ },
82
+ ],
83
+ stateMutability: 'view',
84
+ },
72
85
  {
73
86
  type: 'function',
74
87
  name: 'totalSupply',
package/dist/bls.js CHANGED
@@ -78,6 +78,10 @@ const loadCurvesSync = () => {
78
78
  }
79
79
  }
80
80
  };
81
+ const isBlsModule = (mod) => mod !== null &&
82
+ typeof mod === 'object' &&
83
+ 'bls12_381' in mod &&
84
+ typeof mod.bls12_381 === 'object';
81
85
  const loadCurvesAsync = async () => {
82
86
  if (curvesCache)
83
87
  return curvesCache;
@@ -90,7 +94,10 @@ const loadCurvesAsync = async () => {
90
94
  return require('@noble/curves/bls12-381');
91
95
  }
92
96
  catch {
93
- const mod = (await Promise.resolve().then(() => __importStar(require('@noble/curves/bls12-381.js'))));
97
+ const mod = await Promise.resolve().then(() => __importStar(require('@noble/curves/bls12-381.js')));
98
+ if (!isBlsModule(mod)) {
99
+ throw new errors_1.VerificationError('BLS decoding: unexpected module shape from @noble/curves');
100
+ }
94
101
  return mod;
95
102
  }
96
103
  })();
@@ -137,7 +144,11 @@ const normalizeSignature = (input) => {
137
144
  if (input && typeof input === 'object') {
138
145
  const record = input;
139
146
  if (Array.isArray(record.data)) {
140
- const bytes = Uint8Array.from(record.data);
147
+ const arr = record.data;
148
+ if (arr.some((b) => typeof b !== 'number' || b < 0 || b > 255 || !Number.isInteger(b))) {
149
+ throw new errors_1.VerificationError('signature data array contains invalid byte values');
150
+ }
151
+ const bytes = Uint8Array.from(arr);
141
152
  const hex = Buffer.from(bytes).toString('hex');
142
153
  return { hex, bytes };
143
154
  }
@@ -2,22 +2,69 @@ import type { AuthTokens } from '../auth';
2
2
  import { Config } from '../config';
3
3
  import { ContractGateway } from '../contract';
4
4
  import { RpcProxy } from '../rpc';
5
- import { CorePublicParameters, PaymentSigner } from '../signing';
5
+ import { CorePublicParameters } from '../models';
6
+ import { PaymentSigner } from '../signing';
6
7
  import { RecipientClient } from './recipient';
7
8
  import { UserClient } from './user';
9
+ /**
10
+ * Top-level SDK client. Holds a live connection to the 4Mica core RPC and the
11
+ * on-chain Core4Mica contract. Obtain an instance via {@link Client.new}.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const cfg = new ConfigBuilder().walletPrivateKey("0x...").build();
16
+ * const client = await Client.new(cfg);
17
+ * try {
18
+ * // client.user – payer-side operations
19
+ * // client.recipient – recipient-side operations
20
+ * } finally {
21
+ * await client.aclose();
22
+ * }
23
+ * ```
24
+ */
8
25
  export declare class Client {
26
+ /** Low-level RPC proxy to the 4Mica core service. */
9
27
  readonly rpc: RpcProxy;
28
+ /** Chain and contract parameters fetched from the core service at startup. */
10
29
  readonly params: CorePublicParameters;
30
+ /** viem-backed gateway for on-chain calls (deposit, remunerate, …). */
11
31
  readonly gateway: ContractGateway;
32
+ /** 32-byte domain separator used to verify V1 BLS guarantee certificates. */
12
33
  readonly guaranteeDomain: string;
34
+ /** Payer-side operations: deposit, sign, withdraw. */
13
35
  readonly user: UserClient;
36
+ /** Recipient-side operations: tabs, guarantees, remuneration. */
14
37
  readonly recipient: RecipientClient;
38
+ /** Payment signing wrapper around the configured viem Account. */
15
39
  readonly signer: PaymentSigner;
16
40
  private authSession?;
17
41
  private constructor();
42
+ /**
43
+ * Create and fully initialise a Client.
44
+ *
45
+ * Fetches public parameters from the core service, validates that the
46
+ * Ethereum RPC is on the expected chain, and sets up SIWE auth if configured.
47
+ *
48
+ * @param cfg - Validated configuration produced by {@link ConfigBuilder.build}.
49
+ * @throws {@link ConfigError} if the configuration is invalid.
50
+ * @throws {@link RpcError} if the core service is unreachable.
51
+ * @throws {@link ContractError} if the Ethereum RPC returns the wrong chain ID.
52
+ */
18
53
  static new(cfg: Config): Promise<Client>;
19
54
  private static buildGateway;
55
+ /**
56
+ * Release client resources. Safe to call multiple times.
57
+ * Use in a `finally` block to ensure cleanup after use.
58
+ */
20
59
  aclose(): Promise<void>;
60
+ /**
61
+ * Perform an explicit SIWE login and return the resulting tokens.
62
+ *
63
+ * Not required for normal operation — the first authenticated RPC call
64
+ * triggers auth automatically. Call this to pre-warm the session.
65
+ *
66
+ * @throws {@link AuthMissingConfigError} if auth was not enabled in the config.
67
+ */
21
68
  login(): Promise<AuthTokens>;
22
69
  }
23
70
  export { UserClient } from './user';
@@ -8,13 +8,36 @@ const rpc_1 = require("../rpc");
8
8
  const signing_1 = require("../signing");
9
9
  const recipient_1 = require("./recipient");
10
10
  const user_1 = require("./user");
11
+ /**
12
+ * Top-level SDK client. Holds a live connection to the 4Mica core RPC and the
13
+ * on-chain Core4Mica contract. Obtain an instance via {@link Client.new}.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const cfg = new ConfigBuilder().walletPrivateKey("0x...").build();
18
+ * const client = await Client.new(cfg);
19
+ * try {
20
+ * // client.user – payer-side operations
21
+ * // client.recipient – recipient-side operations
22
+ * } finally {
23
+ * await client.aclose();
24
+ * }
25
+ * ```
26
+ */
11
27
  class Client {
28
+ /** Low-level RPC proxy to the 4Mica core service. */
12
29
  rpc;
30
+ /** Chain and contract parameters fetched from the core service at startup. */
13
31
  params;
32
+ /** viem-backed gateway for on-chain calls (deposit, remunerate, …). */
14
33
  gateway;
34
+ /** 32-byte domain separator used to verify V1 BLS guarantee certificates. */
15
35
  guaranteeDomain;
36
+ /** Payer-side operations: deposit, sign, withdraw. */
16
37
  user;
38
+ /** Recipient-side operations: tabs, guarantees, remuneration. */
17
39
  recipient;
40
+ /** Payment signing wrapper around the configured viem Account. */
18
41
  signer;
19
42
  authSession;
20
43
  constructor(rpc, params, gateway, guaranteeDomain, signer, authSession) {
@@ -27,6 +50,17 @@ class Client {
27
50
  this.user = new user_1.UserClient(this);
28
51
  this.recipient = new recipient_1.RecipientClient(this);
29
52
  }
53
+ /**
54
+ * Create and fully initialise a Client.
55
+ *
56
+ * Fetches public parameters from the core service, validates that the
57
+ * Ethereum RPC is on the expected chain, and sets up SIWE auth if configured.
58
+ *
59
+ * @param cfg - Validated configuration produced by {@link ConfigBuilder.build}.
60
+ * @throws {@link ConfigError} if the configuration is invalid.
61
+ * @throws {@link RpcError} if the core service is unreachable.
62
+ * @throws {@link ContractError} if the Ethereum RPC returns the wrong chain ID.
63
+ */
30
64
  static async new(cfg) {
31
65
  const rpc = new rpc_1.RpcProxy(cfg.rpcUrl, cfg.adminApiKey);
32
66
  const params = await rpc.getPublicParams();
@@ -54,9 +88,21 @@ class Client {
54
88
  const contractAddress = cfg.contractAddress ?? params.contractAddress;
55
89
  return contract_1.ContractGateway.create(ethRpcUrl, cfg.signer, contractAddress, params.chainId);
56
90
  }
91
+ /**
92
+ * Release client resources. Safe to call multiple times.
93
+ * Use in a `finally` block to ensure cleanup after use.
94
+ */
57
95
  async aclose() {
58
96
  await this.rpc.aclose();
59
97
  }
98
+ /**
99
+ * Perform an explicit SIWE login and return the resulting tokens.
100
+ *
101
+ * Not required for normal operation — the first authenticated RPC call
102
+ * triggers auth automatically. Call this to pre-warm the session.
103
+ *
104
+ * @throws {@link AuthMissingConfigError} if auth was not enabled in the config.
105
+ */
60
106
  async login() {
61
107
  if (!this.authSession) {
62
108
  throw new errors_1.AuthMissingConfigError('auth is not enabled');
@@ -1,24 +1,128 @@
1
- import { AssetBalanceInfo, BLSCert, CollateralEventInfo, GuaranteeInfo, PaymentGuaranteeClaims, PaymentGuaranteeRequestClaims, PendingRemunerationInfo, RecipientPaymentInfo, SigningScheme, TabInfo, TabPaymentStatus } from '../models';
1
+ import { AssetBalanceInfo, BLSCert, CollateralEventInfo, GuaranteeInfo, PaymentGuaranteeClaims, PaymentGuaranteeRequestClaims, PaymentGuaranteeRequestClaimsV2, PendingRemunerationInfo, RecipientPaymentInfo, SigningScheme, TabInfo, TabPaymentStatus } from '../models';
2
2
  import type { TxReceiptWaitOptions } from '../contract';
3
3
  import type { Client } from './index';
4
+ /** Recipient-side operations: tab management, guarantee issuance, remuneration. */
4
5
  export declare class RecipientClient {
5
6
  private client;
6
7
  constructor(client: Client);
7
8
  private get recipientAddress();
8
9
  get guaranteeDomain(): string;
9
- createTab(userAddress: string, recipientAddress: string, erc20Token: string | undefined | null, ttl?: number | null): Promise<bigint>;
10
+ /**
11
+ * Create a payment tab via the core RPC.
12
+ *
13
+ * @param userAddress - Address of the payer.
14
+ * @param recipientAddress - Address of the recipient.
15
+ * @param erc20Token - ERC20 token address for the tab, or `null`/`undefined` for ETH.
16
+ * @param ttl - Optional time-to-live in seconds.
17
+ * @returns `{ tabId, assetAddress, nextReqId }` — the tab ID, the asset address as stored
18
+ * by the core (use this for all subsequent claims), and the first request ID to use.
19
+ * @throws {@link RpcError} if the request fails.
20
+ */
21
+ createTab(userAddress: string, recipientAddress: string, erc20Token: string | undefined | null, ttl?: number | null): Promise<{
22
+ tabId: bigint;
23
+ assetAddress: string;
24
+ nextReqId: bigint;
25
+ }>;
26
+ /**
27
+ * Query the on-chain payment status of a tab.
28
+ *
29
+ * @param tabId - Tab identifier.
30
+ * @returns `{ paid, remunerated, asset }`.
31
+ */
10
32
  getTabPaymentStatus(tabId: number | bigint): Promise<TabPaymentStatus>;
11
- issuePaymentGuarantee(claims: PaymentGuaranteeRequestClaims, signature: string, scheme: SigningScheme): Promise<BLSCert>;
12
- verifyPaymentGuarantee(cert: BLSCert): PaymentGuaranteeClaims;
33
+ /**
34
+ * Issue a BLS-signed payment guarantee certificate via the core RPC.
35
+ *
36
+ * The returned {@link BLSCert} can be stored and later passed to
37
+ * {@link remunerate} to claim the payment on-chain.
38
+ *
39
+ * @param claims - Signed payment claims (V1 or V2).
40
+ * @param signature - ECDSA signature hex string from the payer.
41
+ * @param scheme - Signing scheme used to produce the signature.
42
+ * @returns BLS certificate with ABI-encoded claims and BLS signature.
43
+ * @throws {@link RpcError} if the core service rejects the request.
44
+ */
45
+ issuePaymentGuarantee(claims: PaymentGuaranteeRequestClaims | PaymentGuaranteeRequestClaimsV2, signature: string, scheme: SigningScheme): Promise<BLSCert>;
46
+ /**
47
+ * Verify and decode a BLS guarantee certificate.
48
+ *
49
+ * Decodes the ABI-encoded claims and validates the domain separator against
50
+ * the on-chain configuration. For V2 certificates, the active V2 domain is
51
+ * fetched from the contract and verified to be enabled.
52
+ *
53
+ * @param cert - BLS certificate (hex-encoded claims + hex-encoded signature).
54
+ * @returns Decoded {@link PaymentGuaranteeClaims}, including validation policy for V2.
55
+ * @throws {@link VerificationError} on domain mismatch, invalid length, or disabled version.
56
+ */
57
+ verifyPaymentGuarantee(cert: BLSCert): Promise<PaymentGuaranteeClaims>;
58
+ /**
59
+ * Claim payment on-chain by submitting a verified BLS certificate.
60
+ *
61
+ * Verifies the certificate first (see {@link verifyPaymentGuarantee}), then
62
+ * converts the BLS signature into the G2 coordinate words expected by the
63
+ * contract and submits the `remunerate` transaction.
64
+ *
65
+ * Requires the optional `@noble/curves` package for BLS point decompression.
66
+ *
67
+ * @param cert - BLS certificate to settle.
68
+ * @param waitOptions - Optional timeout/polling overrides for receipt polling.
69
+ * @throws {@link VerificationError} if the certificate is invalid or `claims`/`signature`
70
+ * are not hex strings.
71
+ * @throws {@link ContractError} if the contract call fails.
72
+ */
13
73
  remunerate(cert: BLSCert, waitOptions?: TxReceiptWaitOptions): Promise<import("viem").TransactionReceipt>;
74
+ /** List all tabs for this recipient that have been settled on-chain. */
14
75
  listSettledTabs(): Promise<TabInfo[]>;
76
+ /** List all tabs with outstanding (un-remunerated) guarantees for this recipient. */
15
77
  listPendingRemunerations(): Promise<PendingRemunerationInfo[]>;
78
+ /**
79
+ * Fetch a single tab by ID.
80
+ *
81
+ * @param tabId - Tab identifier.
82
+ * @returns The tab, or `null` if not found.
83
+ */
16
84
  getTab(tabId: number | bigint): Promise<TabInfo | null>;
85
+ /**
86
+ * List all tabs belonging to this recipient.
87
+ *
88
+ * @param settlementStatuses - Optional filter on settlement status (e.g. `['PENDING']`).
89
+ */
17
90
  listRecipientTabs(settlementStatuses?: string[]): Promise<TabInfo[]>;
91
+ /**
92
+ * List all guarantee requests associated with a tab.
93
+ *
94
+ * @param tabId - Tab identifier.
95
+ */
18
96
  getTabGuarantees(tabId: number | bigint): Promise<GuaranteeInfo[]>;
97
+ /**
98
+ * Fetch the most recent guarantee for a tab.
99
+ *
100
+ * @param tabId - Tab identifier.
101
+ * @returns The latest {@link GuaranteeInfo}, or `null` if none exists.
102
+ */
19
103
  getLatestGuarantee(tabId: number | bigint): Promise<GuaranteeInfo | null>;
104
+ /**
105
+ * Fetch a specific guarantee by tab ID and request ID.
106
+ *
107
+ * @param tabId - Tab identifier.
108
+ * @param reqId - Guarantee request identifier.
109
+ * @returns The {@link GuaranteeInfo}, or `null` if not found.
110
+ */
20
111
  getGuarantee(tabId: number | bigint, reqId: number | bigint): Promise<GuaranteeInfo | null>;
112
+ /** List all on-chain payments received by this recipient. */
21
113
  listRecipientPayments(): Promise<RecipientPaymentInfo[]>;
114
+ /**
115
+ * List collateral deposit/withdrawal events associated with a tab.
116
+ *
117
+ * @param tabId - Tab identifier.
118
+ */
22
119
  getCollateralEventsForTab(tabId: number | bigint): Promise<CollateralEventInfo[]>;
120
+ /**
121
+ * Fetch the collateral balance a user has locked for a specific asset.
122
+ *
123
+ * @param userAddress - Address of the payer.
124
+ * @param assetAddress - ERC20 token address, or zero address for ETH.
125
+ * @returns Balance info, or `null` if no record exists.
126
+ */
23
127
  getUserAssetBalance(userAddress: string, assetAddress: string): Promise<AssetBalanceInfo | null>;
24
128
  }