@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.
@@ -9,16 +9,26 @@ type CoreContract = GetContractReturnType<typeof core4micaAbi, {
9
9
  export type TxReceiptWaitOptions = {
10
10
  timeout?: number;
11
11
  pollingInterval?: number;
12
+ gas?: bigint;
12
13
  };
13
14
  export declare class ContractGateway {
14
15
  readonly publicClient: TPublicClient;
15
16
  readonly walletClient: TWalletClient;
16
17
  readonly contract: CoreContract;
17
18
  private erc20Cache;
19
+ private txQueue;
18
20
  private constructor();
19
21
  static create(rpcUrl: string, signer: Account, contractAddress: Hex, chainId: number): Promise<ContractGateway>;
20
22
  private erc20;
23
+ private enqueueTx;
24
+ private defaultFeeParams;
25
+ private splitWaitOptions;
21
26
  getGuaranteeDomain(): Promise<string>;
27
+ getGuaranteeVersionConfig(version: number): Promise<{
28
+ domainSeparator: string;
29
+ decoder: string;
30
+ enabled: boolean;
31
+ }>;
22
32
  approveErc20(token: string, amount: number | bigint | string, waitOptions?: TxReceiptWaitOptions): Promise<import("viem").TransactionReceipt>;
23
33
  deposit(amount: number | bigint | string, erc20Token?: string, waitOptions?: TxReceiptWaitOptions): Promise<import("viem").TransactionReceipt>;
24
34
  getUserAssets(): Promise<{
package/dist/contract.js CHANGED
@@ -4,12 +4,17 @@ exports.ContractGateway = void 0;
4
4
  const viem_1 = require("viem");
5
5
  const core4mica_1 = require("./abi/core4mica");
6
6
  const chain_1 = require("./chain");
7
+ const errors_1 = require("./errors");
7
8
  const utils_1 = require("./utils");
9
+ const DEFAULT_REMUNERATE_GAS_LIMIT = 8000000n;
10
+ const DEFAULT_MAX_FEE_PER_GAS = (0, viem_1.parseGwei)('0.1');
11
+ const DEFAULT_MAX_PRIORITY_FEE_PER_GAS = (0, viem_1.parseGwei)('0.1');
8
12
  class ContractGateway {
9
13
  publicClient;
10
14
  walletClient;
11
15
  contract;
12
16
  erc20Cache = new Map();
17
+ txQueue = Promise.resolve();
13
18
  constructor(publicClient, walletClient, contract) {
14
19
  this.publicClient = publicClient;
15
20
  this.walletClient = walletClient;
@@ -22,7 +27,7 @@ class ContractGateway {
22
27
  });
23
28
  const rpcChainId = await publicClient.getChainId();
24
29
  if (rpcChainId !== Number(chainId)) {
25
- throw new Error(`Connected to chain ${rpcChainId}, expected ${chainId}`);
30
+ throw new errors_1.ContractError(`Connected to chain ${rpcChainId}, expected ${chainId}`);
26
31
  }
27
32
  const walletClient = (0, viem_1.createWalletClient)({
28
33
  transport: (0, viem_1.http)(rpcUrl),
@@ -49,28 +54,63 @@ class ContractGateway {
49
54
  }
50
55
  return this.erc20Cache.get(token);
51
56
  }
57
+ enqueueTx(fn) {
58
+ // Serialize transaction submissions to avoid nonce collisions.
59
+ const run = this.txQueue.then(fn, fn);
60
+ this.txQueue = run.then(() => undefined, () => undefined);
61
+ return run;
62
+ }
63
+ defaultFeeParams() {
64
+ return {
65
+ maxFeePerGas: DEFAULT_MAX_FEE_PER_GAS,
66
+ maxPriorityFeePerGas: DEFAULT_MAX_PRIORITY_FEE_PER_GAS,
67
+ };
68
+ }
69
+ splitWaitOptions(waitOptions) {
70
+ if (!waitOptions) {
71
+ return { receipt: {} };
72
+ }
73
+ const { gas, timeout, pollingInterval } = waitOptions;
74
+ return {
75
+ gas,
76
+ receipt: {
77
+ ...(timeout !== undefined ? { timeout } : {}),
78
+ ...(pollingInterval !== undefined ? { pollingInterval } : {}),
79
+ },
80
+ };
81
+ }
52
82
  async getGuaranteeDomain() {
53
83
  return this.contract.read.guaranteeDomainSeparator();
54
84
  }
85
+ async getGuaranteeVersionConfig(version) {
86
+ const [, domainSeparator, decoder, enabled] = await this.contract.read.getGuaranteeVersionConfig([BigInt(version)]);
87
+ return { domainSeparator: domainSeparator, decoder: decoder, enabled };
88
+ }
55
89
  async approveErc20(token, amount, waitOptions) {
90
+ const { receipt } = this.splitWaitOptions(waitOptions);
56
91
  const erc20 = this.erc20(token);
57
92
  // spender address logic
58
93
  const spender = this.contract.address;
59
- const hash = await erc20.write.approve([spender, (0, utils_1.parseU256)(amount)]);
60
- return this.publicClient.waitForTransactionReceipt({ hash, ...(waitOptions ?? {}) });
94
+ const hash = await this.enqueueTx(() => erc20.write.approve([spender, (0, utils_1.parseU256)(amount)], this.defaultFeeParams()));
95
+ return this.publicClient.waitForTransactionReceipt({ hash, ...receipt });
61
96
  }
62
97
  async deposit(amount, erc20Token, waitOptions) {
98
+ const { receipt } = this.splitWaitOptions(waitOptions);
63
99
  let hash;
64
100
  if (erc20Token) {
65
- hash = await this.contract.write.depositStablecoin([erc20Token, (0, utils_1.parseU256)(amount)]);
101
+ hash = await this.enqueueTx(() => this.contract.write.depositStablecoin([erc20Token, (0, utils_1.parseU256)(amount)], this.defaultFeeParams()));
66
102
  }
67
103
  else {
68
- hash = await this.contract.write.deposit({ value: (0, utils_1.parseU256)(amount) });
104
+ hash = await this.enqueueTx(() => this.contract.write.deposit({ value: (0, utils_1.parseU256)(amount), ...this.defaultFeeParams() }));
69
105
  }
70
- return this.publicClient.waitForTransactionReceipt({ hash, ...(waitOptions ?? {}) });
106
+ return this.publicClient.waitForTransactionReceipt({ hash, ...receipt });
71
107
  }
72
108
  async getUserAssets() {
73
- const addr = this.walletClient.account.address;
109
+ const account = this.walletClient.account;
110
+ if (!account) {
111
+ throw new errors_1.ContractError('wallet client has no account configured');
112
+ }
113
+ const addr = account.address;
74
114
  const result = await this.contract.read.getUserAllAssets([addr]);
75
115
  return result.map((a) => ({
76
116
  asset: a.asset,
@@ -90,55 +130,62 @@ class ContractGateway {
90
130
  };
91
131
  }
92
132
  async payTabEth(tabId, reqId, amount, recipient, waitOptions) {
133
+ const { receipt } = this.splitWaitOptions(waitOptions);
93
134
  const data = new TextEncoder().encode(`tab_id:${tabId.toString(16)};req_id:${reqId.toString(16)}`);
94
- const hash = await this.walletClient.sendTransaction({
135
+ const hash = await this.enqueueTx(() => this.walletClient.sendTransaction({
95
136
  to: recipient,
96
137
  value: (0, utils_1.parseU256)(amount),
97
138
  data: (0, utils_1.hexFromBytes)(data),
98
- });
99
- return this.publicClient.waitForTransactionReceipt({ hash, ...(waitOptions ?? {}) });
139
+ ...this.defaultFeeParams(),
140
+ }));
141
+ return this.publicClient.waitForTransactionReceipt({ hash, ...receipt });
100
142
  }
101
143
  async payTabErc20(tabId, amount, erc20Token, recipient, waitOptions) {
102
- const hash = await this.contract.write.payTabInERC20Token([
144
+ const { receipt } = this.splitWaitOptions(waitOptions);
145
+ const hash = await this.enqueueTx(() => this.contract.write.payTabInERC20Token([
103
146
  (0, utils_1.parseU256)(tabId),
104
147
  erc20Token,
105
148
  (0, utils_1.parseU256)(amount),
106
149
  recipient,
107
- ]);
108
- return this.publicClient.waitForTransactionReceipt({ hash, ...(waitOptions ?? {}) });
150
+ ], this.defaultFeeParams()));
151
+ return this.publicClient.waitForTransactionReceipt({ hash, ...receipt });
109
152
  }
110
153
  async requestWithdrawal(amount, erc20Token, waitOptions) {
154
+ const { receipt } = this.splitWaitOptions(waitOptions);
111
155
  const value = (0, utils_1.parseU256)(amount);
112
156
  let hash;
113
157
  if (erc20Token) {
114
- hash = await this.contract.write.requestWithdrawal([erc20Token, value]);
158
+ hash = await this.enqueueTx(() => this.contract.write.requestWithdrawal([erc20Token, value], this.defaultFeeParams()));
115
159
  }
116
160
  else {
117
- hash = await this.contract.write.requestWithdrawal([value]);
161
+ hash = await this.enqueueTx(() => this.contract.write.requestWithdrawal([value], this.defaultFeeParams()));
118
162
  }
119
- return this.publicClient.waitForTransactionReceipt({ hash, ...(waitOptions ?? {}) });
163
+ return this.publicClient.waitForTransactionReceipt({ hash, ...receipt });
120
164
  }
121
165
  async cancelWithdrawal(erc20Token, waitOptions) {
166
+ const { receipt } = this.splitWaitOptions(waitOptions);
122
167
  let hash;
123
168
  if (erc20Token) {
124
- hash = await this.contract.write.cancelWithdrawal([erc20Token]);
169
+ hash = await this.enqueueTx(() => this.contract.write.cancelWithdrawal([erc20Token], this.defaultFeeParams()));
125
170
  }
126
171
  else {
127
- hash = await this.contract.write.cancelWithdrawal();
172
+ hash = await this.enqueueTx(() => this.contract.write.cancelWithdrawal(this.defaultFeeParams()));
128
173
  }
129
- return this.publicClient.waitForTransactionReceipt({ hash, ...(waitOptions ?? {}) });
174
+ return this.publicClient.waitForTransactionReceipt({ hash, ...receipt });
130
175
  }
131
176
  async finalizeWithdrawal(erc20Token, waitOptions) {
177
+ const { receipt } = this.splitWaitOptions(waitOptions);
132
178
  let hash;
133
179
  if (erc20Token) {
134
- hash = await this.contract.write.finalizeWithdrawal([erc20Token]);
180
+ hash = await this.enqueueTx(() => this.contract.write.finalizeWithdrawal([erc20Token], this.defaultFeeParams()));
135
181
  }
136
182
  else {
137
- hash = await this.contract.write.finalizeWithdrawal();
183
+ hash = await this.enqueueTx(() => this.contract.write.finalizeWithdrawal(this.defaultFeeParams()));
138
184
  }
139
- return this.publicClient.waitForTransactionReceipt({ hash, ...(waitOptions ?? {}) });
185
+ return this.publicClient.waitForTransactionReceipt({ hash, ...receipt });
140
186
  }
141
187
  async remunerate(claimsBlob, signatureWords, waitOptions) {
188
+ const { gas, receipt } = this.splitWaitOptions(waitOptions);
142
189
  const sigStruct = {
143
190
  x_c0_a: (0, utils_1.hexFromBytes)(signatureWords[0]),
144
191
  x_c0_b: (0, utils_1.hexFromBytes)(signatureWords[1]),
@@ -149,8 +196,11 @@ class ContractGateway {
149
196
  y_c1_a: (0, utils_1.hexFromBytes)(signatureWords[6]),
150
197
  y_c1_b: (0, utils_1.hexFromBytes)(signatureWords[7]),
151
198
  };
152
- const hash = await this.contract.write.remunerate([(0, utils_1.hexFromBytes)(claimsBlob), sigStruct]);
153
- return this.publicClient.waitForTransactionReceipt({ hash, ...(waitOptions ?? {}) });
199
+ const hash = await this.enqueueTx(() => this.contract.write.remunerate([(0, utils_1.hexFromBytes)(claimsBlob), sigStruct], {
200
+ gas: gas ?? DEFAULT_REMUNERATE_GAS_LIMIT,
201
+ ...this.defaultFeeParams(),
202
+ }));
203
+ return this.publicClient.waitForTransactionReceipt({ hash, ...receipt });
154
204
  }
155
205
  }
156
206
  exports.ContractGateway = ContractGateway;
package/dist/errors.d.ts CHANGED
@@ -1,8 +1,11 @@
1
+ /** Base class for all 4Mica SDK errors. Sets `error.name` to the subclass name. */
1
2
  export declare class FourMicaError extends Error {
2
3
  constructor(message: string);
3
4
  }
5
+ /** Thrown when the SDK configuration is invalid (e.g. missing required fields or bad URL). */
4
6
  export declare class ConfigError extends FourMicaError {
5
7
  }
8
+ /** Thrown when a 4Mica core RPC call fails. Includes the HTTP status and raw response body. */
6
9
  export declare class RpcError extends FourMicaError {
7
10
  readonly status?: number;
8
11
  readonly body?: unknown;
@@ -11,24 +14,34 @@ export declare class RpcError extends FourMicaError {
11
14
  body?: unknown;
12
15
  });
13
16
  }
17
+ /** Thrown when the SDK client fails to initialise (e.g. chain ID mismatch). */
14
18
  export declare class ClientInitializationError extends FourMicaError {
15
19
  }
20
+ /** Thrown when signing a payment claim fails (e.g. unsupported scheme, address mismatch). */
16
21
  export declare class SigningError extends FourMicaError {
17
22
  }
23
+ /** Thrown when an on-chain contract call fails or returns an unexpected result. */
18
24
  export declare class ContractError extends FourMicaError {
19
25
  }
26
+ /** Thrown when BLS certificate verification fails (e.g. domain mismatch, invalid encoding). */
20
27
  export declare class VerificationError extends FourMicaError {
21
28
  }
29
+ /** Thrown when an x402 HTTP payment flow encounters an error. */
22
30
  export declare class X402Error extends FourMicaError {
23
31
  }
32
+ /** Base class for authentication-related errors. */
24
33
  export declare class AuthError extends FourMicaError {
25
34
  }
35
+ /** Thrown when the auth URL is invalid or unreachable. */
26
36
  export declare class AuthUrlError extends AuthError {
27
37
  }
38
+ /** Thrown when a network-level error occurs during authentication. */
28
39
  export declare class AuthTransportError extends AuthError {
29
40
  }
41
+ /** Thrown when the auth server response cannot be decoded. */
30
42
  export declare class AuthDecodeError extends AuthError {
31
43
  }
44
+ /** Thrown when the auth server returns an error response. Includes HTTP status and body. */
32
45
  export declare class AuthApiError extends AuthError {
33
46
  readonly status?: number;
34
47
  readonly body?: unknown;
@@ -37,7 +50,9 @@ export declare class AuthApiError extends AuthError {
37
50
  body?: unknown;
38
51
  });
39
52
  }
53
+ /** Thrown when auth configuration is invalid. */
40
54
  export declare class AuthConfigError extends AuthError {
41
55
  }
56
+ /** Thrown when an authenticated operation is attempted without auth being configured. */
42
57
  export declare class AuthMissingConfigError extends AuthConfigError {
43
58
  }
package/dist/errors.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuthMissingConfigError = exports.AuthConfigError = exports.AuthApiError = exports.AuthDecodeError = exports.AuthTransportError = exports.AuthUrlError = exports.AuthError = exports.X402Error = exports.VerificationError = exports.ContractError = exports.SigningError = exports.ClientInitializationError = exports.RpcError = exports.ConfigError = exports.FourMicaError = void 0;
4
+ /** Base class for all 4Mica SDK errors. Sets `error.name` to the subclass name. */
4
5
  class FourMicaError extends Error {
5
6
  constructor(message) {
6
7
  super(message);
@@ -8,9 +9,11 @@ class FourMicaError extends Error {
8
9
  }
9
10
  }
10
11
  exports.FourMicaError = FourMicaError;
12
+ /** Thrown when the SDK configuration is invalid (e.g. missing required fields or bad URL). */
11
13
  class ConfigError extends FourMicaError {
12
14
  }
13
15
  exports.ConfigError = ConfigError;
16
+ /** Thrown when a 4Mica core RPC call fails. Includes the HTTP status and raw response body. */
14
17
  class RpcError extends FourMicaError {
15
18
  status;
16
19
  body;
@@ -21,33 +24,43 @@ class RpcError extends FourMicaError {
21
24
  }
22
25
  }
23
26
  exports.RpcError = RpcError;
27
+ /** Thrown when the SDK client fails to initialise (e.g. chain ID mismatch). */
24
28
  class ClientInitializationError extends FourMicaError {
25
29
  }
26
30
  exports.ClientInitializationError = ClientInitializationError;
31
+ /** Thrown when signing a payment claim fails (e.g. unsupported scheme, address mismatch). */
27
32
  class SigningError extends FourMicaError {
28
33
  }
29
34
  exports.SigningError = SigningError;
35
+ /** Thrown when an on-chain contract call fails or returns an unexpected result. */
30
36
  class ContractError extends FourMicaError {
31
37
  }
32
38
  exports.ContractError = ContractError;
39
+ /** Thrown when BLS certificate verification fails (e.g. domain mismatch, invalid encoding). */
33
40
  class VerificationError extends FourMicaError {
34
41
  }
35
42
  exports.VerificationError = VerificationError;
43
+ /** Thrown when an x402 HTTP payment flow encounters an error. */
36
44
  class X402Error extends FourMicaError {
37
45
  }
38
46
  exports.X402Error = X402Error;
47
+ /** Base class for authentication-related errors. */
39
48
  class AuthError extends FourMicaError {
40
49
  }
41
50
  exports.AuthError = AuthError;
51
+ /** Thrown when the auth URL is invalid or unreachable. */
42
52
  class AuthUrlError extends AuthError {
43
53
  }
44
54
  exports.AuthUrlError = AuthUrlError;
55
+ /** Thrown when a network-level error occurs during authentication. */
45
56
  class AuthTransportError extends AuthError {
46
57
  }
47
58
  exports.AuthTransportError = AuthTransportError;
59
+ /** Thrown when the auth server response cannot be decoded. */
48
60
  class AuthDecodeError extends AuthError {
49
61
  }
50
62
  exports.AuthDecodeError = AuthDecodeError;
63
+ /** Thrown when the auth server returns an error response. Includes HTTP status and body. */
51
64
  class AuthApiError extends AuthError {
52
65
  status;
53
66
  body;
@@ -58,9 +71,11 @@ class AuthApiError extends AuthError {
58
71
  }
59
72
  }
60
73
  exports.AuthApiError = AuthApiError;
74
+ /** Thrown when auth configuration is invalid. */
61
75
  class AuthConfigError extends AuthError {
62
76
  }
63
77
  exports.AuthConfigError = AuthConfigError;
78
+ /** Thrown when an authenticated operation is attempted without auth being configured. */
64
79
  class AuthMissingConfigError extends AuthConfigError {
65
80
  }
66
81
  exports.AuthMissingConfigError = AuthMissingConfigError;
@@ -1,3 +1,25 @@
1
1
  import { PaymentGuaranteeClaims } from './models';
2
+ /**
3
+ * ABI-encode a {@link PaymentGuaranteeClaims} object into a hex string.
4
+ *
5
+ * Produces the outer `(uint64 version, bytes innerClaims)` envelope format expected
6
+ * by the Core4Mica contract. Supports V1 (10 fields) and V2 (18 fields with validation policy).
7
+ *
8
+ * @param claims - Decoded claims to encode. Must have `version` set to `1` or `2`.
9
+ * @returns `0x`-prefixed hex string of the ABI-encoded envelope.
10
+ * @throws {@link VerificationError} if `version` is not `1` or `2`, the domain is not 32 bytes,
11
+ * or V2 claims are missing `validationPolicy`.
12
+ */
2
13
  export declare function encodeGuaranteeClaims(claims: PaymentGuaranteeClaims): string;
14
+ /**
15
+ * Decode ABI-encoded guarantee claims into a {@link PaymentGuaranteeClaims} object.
16
+ *
17
+ * Accepts either the modern `(uint64 version, bytes innerClaims)` envelope or the legacy
18
+ * unwrapped V1 format (raw 320-byte ABI encoding without an outer envelope).
19
+ *
20
+ * @param data - Hex string or raw bytes of the ABI-encoded claims.
21
+ * @returns Decoded {@link PaymentGuaranteeClaims} with `version` set to `1` or `2`.
22
+ * @throws {@link VerificationError} if the data is too short, the inner length is wrong,
23
+ * or the encoded version number is unsupported.
24
+ */
3
25
  export declare function decodeGuaranteeClaims(data: string | Uint8Array): PaymentGuaranteeClaims;
package/dist/guarantee.js CHANGED
@@ -6,7 +6,8 @@ const viem_1 = require("viem");
6
6
  const errors_1 = require("./errors");
7
7
  const utils_1 = require("./utils");
8
8
  const CLAIMS_ENCODED_BYTES = 32 * 10;
9
- const WRAPPED_CLAIMS_BYTES = 32 * 2 + 32 + CLAIMS_ENCODED_BYTES;
9
+ // Minimum bytes for a valid outer envelope: uint64 + bytes offset + bytes length
10
+ const MIN_ENVELOPE_BYTES = 32 * 2 + 32;
10
11
  const CLAIM_TYPES = [
11
12
  { type: 'bytes32' },
12
13
  { type: 'uint256' },
@@ -19,6 +20,54 @@ const CLAIM_TYPES = [
19
20
  { type: 'uint64' },
20
21
  { type: 'uint64' },
21
22
  ];
23
+ // V2 adds: validation_registry_address, validation_request_hash, validation_chain_id,
24
+ // validator_address, validator_agent_id, min_validation_score,
25
+ // validation_subject_hash, required_validation_tag (dynamic string)
26
+ const CLAIM_TYPES_V2 = [
27
+ { type: 'bytes32' }, // domain
28
+ { type: 'uint256' }, // tab_id
29
+ { type: 'uint256' }, // req_id
30
+ { type: 'address' }, // client (user)
31
+ { type: 'address' }, // recipient
32
+ { type: 'uint256' }, // amount
33
+ { type: 'uint256' }, // total_amount
34
+ { type: 'address' }, // asset
35
+ { type: 'uint64' }, // timestamp
36
+ { type: 'uint64' }, // version
37
+ { type: 'address' }, // validation_registry_address
38
+ { type: 'bytes32' }, // validation_request_hash
39
+ { type: 'uint64' }, // validation_chain_id
40
+ { type: 'address' }, // validator_address
41
+ { type: 'uint256' }, // validator_agent_id
42
+ { type: 'uint8' }, // min_validation_score
43
+ { type: 'bytes32' }, // validation_subject_hash
44
+ { type: 'string' }, // required_validation_tag (dynamic)
45
+ ];
46
+ const CLAIM_TYPES_V2_TUPLE = [
47
+ {
48
+ type: 'tuple',
49
+ components: [
50
+ { name: 'domain', type: 'bytes32' },
51
+ { name: 'tabId', type: 'uint256' },
52
+ { name: 'reqId', type: 'uint256' },
53
+ { name: 'user', type: 'address' },
54
+ { name: 'recipient', type: 'address' },
55
+ { name: 'amount', type: 'uint256' },
56
+ { name: 'totalAmount', type: 'uint256' },
57
+ { name: 'asset', type: 'address' },
58
+ { name: 'timestamp', type: 'uint64' },
59
+ { name: 'claimsVersion', type: 'uint64' },
60
+ { name: 'validationRegistryAddress', type: 'address' },
61
+ { name: 'validationRequestHash', type: 'bytes32' },
62
+ { name: 'validationChainId', type: 'uint64' },
63
+ { name: 'validatorAddress', type: 'address' },
64
+ { name: 'validatorAgentId', type: 'uint256' },
65
+ { name: 'minValidationScore', type: 'uint8' },
66
+ { name: 'validationSubjectHash', type: 'bytes32' },
67
+ { name: 'requiredValidationTag', type: 'string' },
68
+ ],
69
+ },
70
+ ];
22
71
  function ensureDomainBytes(domain) {
23
72
  const bytes = typeof domain === 'string' ? (0, viem_1.toBytes)(domain) : domain;
24
73
  if (bytes.length !== 32) {
@@ -28,47 +77,115 @@ function ensureDomainBytes(domain) {
28
77
  }
29
78
  function normalizeHexBytes(data) {
30
79
  if (typeof data === 'string') {
31
- return (data.startsWith('0x') ? data : `0x${data}`);
80
+ return (0, utils_1.ensureHexPrefix)(data);
32
81
  }
33
82
  return (0, utils_1.hexFromBytes)(data);
34
83
  }
84
+ /**
85
+ * ABI-encode a {@link PaymentGuaranteeClaims} object into a hex string.
86
+ *
87
+ * Produces the outer `(uint64 version, bytes innerClaims)` envelope format expected
88
+ * by the Core4Mica contract. Supports V1 (10 fields) and V2 (18 fields with validation policy).
89
+ *
90
+ * @param claims - Decoded claims to encode. Must have `version` set to `1` or `2`.
91
+ * @returns `0x`-prefixed hex string of the ABI-encoded envelope.
92
+ * @throws {@link VerificationError} if `version` is not `1` or `2`, the domain is not 32 bytes,
93
+ * or V2 claims are missing `validationPolicy`.
94
+ */
35
95
  function encodeGuaranteeClaims(claims) {
36
- if (claims.version !== 1) {
37
- throw new errors_1.VerificationError(`unsupported guarantee claims version: ${claims.version}`);
96
+ if (claims.version === 1) {
97
+ const domain = ensureDomainBytes(claims.domain);
98
+ const encoded = (0, viem_1.encodeAbiParameters)(CLAIM_TYPES, [
99
+ (0, utils_1.hexFromBytes)(domain),
100
+ (0, utils_1.parseU256)(claims.tabId),
101
+ (0, utils_1.parseU256)(claims.reqId),
102
+ claims.userAddress,
103
+ claims.recipientAddress,
104
+ (0, utils_1.parseU256)(claims.amount),
105
+ (0, utils_1.parseU256)(claims.totalAmount),
106
+ claims.assetAddress,
107
+ BigInt(claims.timestamp),
108
+ BigInt(claims.version),
109
+ ]);
110
+ return (0, viem_1.encodeAbiParameters)([{ type: 'uint64' }, { type: 'bytes' }], [BigInt(claims.version), encoded]);
111
+ }
112
+ if (claims.version === 2) {
113
+ if (!claims.validationPolicy) {
114
+ throw new errors_1.VerificationError('V2 guarantee claims missing validationPolicy');
115
+ }
116
+ const p = claims.validationPolicy;
117
+ const domain = ensureDomainBytes(claims.domain);
118
+ const encoded = (0, viem_1.encodeAbiParameters)(CLAIM_TYPES_V2_TUPLE, [
119
+ {
120
+ domain: (0, utils_1.hexFromBytes)(domain),
121
+ tabId: (0, utils_1.parseU256)(claims.tabId),
122
+ reqId: (0, utils_1.parseU256)(claims.reqId),
123
+ user: claims.userAddress,
124
+ recipient: claims.recipientAddress,
125
+ amount: (0, utils_1.parseU256)(claims.amount),
126
+ totalAmount: (0, utils_1.parseU256)(claims.totalAmount),
127
+ asset: claims.assetAddress,
128
+ timestamp: BigInt(claims.timestamp),
129
+ claimsVersion: BigInt(claims.version),
130
+ validationRegistryAddress: p.validationRegistryAddress,
131
+ validationRequestHash: (0, utils_1.ensureHexPrefix)(p.validationRequestHash),
132
+ validationChainId: BigInt(p.validationChainId),
133
+ validatorAddress: p.validatorAddress,
134
+ validatorAgentId: p.validatorAgentId,
135
+ minValidationScore: p.minValidationScore,
136
+ validationSubjectHash: (0, utils_1.ensureHexPrefix)(p.validationSubjectHash),
137
+ requiredValidationTag: p.requiredValidationTag,
138
+ },
139
+ ]);
140
+ return (0, viem_1.encodeAbiParameters)([{ type: 'uint64' }, { type: 'bytes' }], [BigInt(claims.version), encoded]);
38
141
  }
39
- const domain = ensureDomainBytes(claims.domain);
40
- const encoded = (0, viem_1.encodeAbiParameters)(CLAIM_TYPES, [
41
- (0, utils_1.hexFromBytes)(domain),
42
- (0, utils_1.parseU256)(claims.tabId),
43
- (0, utils_1.parseU256)(claims.reqId),
44
- claims.userAddress,
45
- claims.recipientAddress,
46
- (0, utils_1.parseU256)(claims.amount),
47
- (0, utils_1.parseU256)(claims.totalAmount),
48
- claims.assetAddress,
49
- BigInt(claims.timestamp),
50
- BigInt(claims.version),
51
- ]);
52
- return (0, viem_1.encodeAbiParameters)([{ type: 'uint64' }, { type: 'bytes' }], [BigInt(claims.version), encoded]);
142
+ throw new errors_1.VerificationError(`unsupported guarantee claims version: ${claims.version}`);
53
143
  }
144
+ /**
145
+ * Decode ABI-encoded guarantee claims into a {@link PaymentGuaranteeClaims} object.
146
+ *
147
+ * Accepts either the modern `(uint64 version, bytes innerClaims)` envelope or the legacy
148
+ * unwrapped V1 format (raw 320-byte ABI encoding without an outer envelope).
149
+ *
150
+ * @param data - Hex string or raw bytes of the ABI-encoded claims.
151
+ * @returns Decoded {@link PaymentGuaranteeClaims} with `version` set to `1` or `2`.
152
+ * @throws {@link VerificationError} if the data is too short, the inner length is wrong,
153
+ * or the encoded version number is unsupported.
154
+ */
54
155
  function decodeGuaranteeClaims(data) {
55
156
  const hex = normalizeHexBytes(data);
56
157
  const byteLen = (hex.length - 2) / 2;
57
- let encoded;
158
+ // Try to decode as unwrapped V1 (legacy)
58
159
  if (byteLen === CLAIMS_ENCODED_BYTES) {
59
- encoded = hex;
160
+ return decodeV1Claims(hex);
60
161
  }
61
- else {
62
- if (byteLen !== WRAPPED_CLAIMS_BYTES) {
63
- throw new errors_1.VerificationError(`unexpected guarantee claims length: ${byteLen} bytes`);
64
- }
65
- const [version, wrapped] = (0, viem_1.decodeAbiParameters)([{ type: 'uint64' }, { type: 'bytes' }], hex);
66
- if (version !== 1n) {
67
- throw new errors_1.VerificationError(`unsupported guarantee claims version: ${version}`);
162
+ // Decode outer envelope to get version
163
+ if (byteLen < MIN_ENVELOPE_BYTES) {
164
+ throw new errors_1.VerificationError(`unexpected guarantee claims length: ${byteLen} bytes`);
165
+ }
166
+ const [version, wrapped] = (0, viem_1.decodeAbiParameters)([{ type: 'uint64' }, { type: 'bytes' }], hex);
167
+ if (version === 1n) {
168
+ const innerHex = wrapped;
169
+ const innerByteLen = (innerHex.length - 2) / 2;
170
+ if (innerByteLen !== CLAIMS_ENCODED_BYTES) {
171
+ throw new errors_1.VerificationError(`unexpected V1 claims inner length: ${innerByteLen} bytes`);
68
172
  }
69
- encoded = wrapped;
173
+ return decodeV1Claims(innerHex);
70
174
  }
71
- const [domain, tabId, reqId, user, recipient, amount, totalAmount, asset, timestamp, claimsVersion,] = (0, viem_1.decodeAbiParameters)(CLAIM_TYPES, encoded);
175
+ if (version === 2n) {
176
+ return decodeV2Claims(wrapped);
177
+ }
178
+ throw new errors_1.VerificationError(`unsupported guarantee claims version: ${version}`);
179
+ }
180
+ function decodeV1Claims(encoded) {
181
+ let decoded;
182
+ try {
183
+ decoded = (0, viem_1.decodeAbiParameters)(CLAIM_TYPES, encoded);
184
+ }
185
+ catch (err) {
186
+ throw new errors_1.VerificationError(`failed to decode V1 guarantee claims: ${String(err)}`);
187
+ }
188
+ const [domain, tabId, reqId, user, recipient, amount, totalAmount, asset, timestamp, claimsVersion,] = decoded;
72
189
  if (claimsVersion !== 1n) {
73
190
  throw new errors_1.VerificationError(`unsupported guarantee claims version: ${claimsVersion}`);
74
191
  }
@@ -85,3 +202,66 @@ function decodeGuaranteeClaims(data) {
85
202
  version: Number(claimsVersion),
86
203
  };
87
204
  }
205
+ function decodeV2Claims(encoded) {
206
+ try {
207
+ const [decoded] = (0, viem_1.decodeAbiParameters)(CLAIM_TYPES_V2_TUPLE, encoded);
208
+ return buildDecodedV2Claims(decoded);
209
+ }
210
+ catch (tupleErr) {
211
+ try {
212
+ const decoded = (0, viem_1.decodeAbiParameters)(CLAIM_TYPES_V2, encoded);
213
+ return buildDecodedV2Claims({
214
+ domain: decoded[0],
215
+ tabId: decoded[1],
216
+ reqId: decoded[2],
217
+ user: decoded[3],
218
+ recipient: decoded[4],
219
+ amount: decoded[5],
220
+ totalAmount: decoded[6],
221
+ asset: decoded[7],
222
+ timestamp: decoded[8],
223
+ claimsVersion: decoded[9],
224
+ validationRegistryAddress: decoded[10],
225
+ validationRequestHash: decoded[11],
226
+ validationChainId: decoded[12],
227
+ validatorAddress: decoded[13],
228
+ validatorAgentId: decoded[14],
229
+ minValidationScore: decoded[15],
230
+ validationSubjectHash: decoded[16],
231
+ requiredValidationTag: decoded[17],
232
+ });
233
+ }
234
+ catch (flatErr) {
235
+ throw new errors_1.VerificationError(`failed to decode V2 guarantee claims: ${String(tupleErr)}; fallback decode failed: ${String(flatErr)}`);
236
+ }
237
+ }
238
+ }
239
+ function buildDecodedV2Claims(decoded) {
240
+ const { domain, tabId, reqId, user, recipient, amount, totalAmount, asset, timestamp, claimsVersion, validationRegistryAddress, validationRequestHash, validationChainId, validatorAddress, validatorAgentId, minValidationScore, validationSubjectHash, requiredValidationTag, } = decoded;
241
+ if (claimsVersion !== 2n) {
242
+ throw new errors_1.VerificationError(`expected V2 claims version, got: ${claimsVersion}`);
243
+ }
244
+ const validationPolicy = {
245
+ validationRegistryAddress: validationRegistryAddress,
246
+ validationRequestHash: validationRequestHash,
247
+ validationChainId: Number(validationChainId),
248
+ validatorAddress: validatorAddress,
249
+ validatorAgentId: (0, utils_1.parseU256)(validatorAgentId),
250
+ minValidationScore: Number(minValidationScore),
251
+ validationSubjectHash: validationSubjectHash,
252
+ requiredValidationTag: requiredValidationTag,
253
+ };
254
+ return {
255
+ domain: (0, viem_1.toBytes)(domain),
256
+ userAddress: user,
257
+ recipientAddress: recipient,
258
+ tabId: (0, utils_1.parseU256)(tabId),
259
+ reqId: (0, utils_1.parseU256)(reqId),
260
+ amount: (0, utils_1.parseU256)(amount),
261
+ totalAmount: (0, utils_1.parseU256)(totalAmount),
262
+ assetAddress: asset,
263
+ timestamp: Number(timestamp),
264
+ version: Number(claimsVersion),
265
+ validationPolicy,
266
+ };
267
+ }
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from './config';
4
4
  export * from './utils';
5
5
  export * from './models';
6
6
  export * from './payment';
7
+ export * from './validation';
7
8
  export * from './signing';
8
9
  export * from './rpc';
9
10
  export * from './auth';
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ __exportStar(require("./config"), exports);
20
20
  __exportStar(require("./utils"), exports);
21
21
  __exportStar(require("./models"), exports);
22
22
  __exportStar(require("./payment"), exports);
23
+ __exportStar(require("./validation"), exports);
23
24
  __exportStar(require("./signing"), exports);
24
25
  __exportStar(require("./rpc"), exports);
25
26
  __exportStar(require("./auth"), exports);