@4mica/sdk 1.2.7 → 1.2.8

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.
@@ -5,6 +5,7 @@ const auth_1 = require("../auth");
5
5
  const contract_1 = require("../contract");
6
6
  const errors_1 = require("../errors");
7
7
  const rpc_1 = require("../rpc");
8
+ const networks_1 = require("../networks");
8
9
  const signing_1 = require("../signing");
9
10
  const recipient_1 = require("./recipient");
10
11
  const user_1 = require("./user");
@@ -84,7 +85,9 @@ class Client {
84
85
  return new Client(rpc, params, gateway, guaranteeDomain, signer, authSession);
85
86
  }
86
87
  static async buildGateway(cfg, params) {
87
- const ethRpcUrl = cfg.ethereumHttpRpcUrl ?? params.ethereumHttpRpcUrl;
88
+ const ethRpcUrl = cfg.ethereumHttpRpcUrl ??
89
+ params.ethereumHttpRpcUrl ??
90
+ (0, networks_1.resolvePublicRpcUrl)(`eip155:${params.chainId}`);
88
91
  const contractAddress = cfg.contractAddress ?? params.contractAddress;
89
92
  return contract_1.ContractGateway.create(ethRpcUrl, cfg.signer, contractAddress, params.chainId);
90
93
  }
@@ -15,7 +15,7 @@ export declare class UserClient {
15
15
  * @param amount - Amount to approve (in token base units).
16
16
  * @param waitOptions - Optional timeout/polling overrides for receipt polling.
17
17
  */
18
- approveErc20(token: string, amount: number | bigint | string, waitOptions?: TxReceiptWaitOptions): Promise<import("viem").TransactionReceipt>;
18
+ approveErc20(token: string, amount: number | bigint | string, waitOptions?: TxReceiptWaitOptions): Promise<import("viem").TransactionReceipt | undefined>;
19
19
  /**
20
20
  * Deposit collateral into the Core4Mica contract.
21
21
  *
@@ -29,7 +29,8 @@ export declare class ContractGateway {
29
29
  decoder: string;
30
30
  enabled: boolean;
31
31
  }>;
32
- approveErc20(token: string, amount: number | bigint | string, waitOptions?: TxReceiptWaitOptions): Promise<import("viem").TransactionReceipt>;
32
+ approveErc20(token: string, amount: number | bigint | string, waitOptions?: TxReceiptWaitOptions): Promise<import("viem").TransactionReceipt | undefined>;
33
+ private waitForErc20Allowance;
33
34
  deposit(amount: number | bigint | string, erc20Token?: string, waitOptions?: TxReceiptWaitOptions): Promise<import("viem").TransactionReceipt>;
34
35
  getUserAssets(opts?: {
35
36
  blockNumber?: bigint;
package/dist/contract.js CHANGED
@@ -15,10 +15,11 @@ function wrapViemError(error, context) {
15
15
  return error;
16
16
  if (error instanceof Error) {
17
17
  const e = error;
18
- const reason = e['cause']?.['reason'] ??
19
- e['shortMessage'] ??
20
- error.message;
21
- return new errors_1.ContractError(`${context}: ${reason}`);
18
+ const cause = e['cause'];
19
+ const reason = cause?.['reason'] ?? cause?.['message'] ?? e['shortMessage'] ?? error.message;
20
+ const details = e['details'] ?? cause?.['details'];
21
+ const suffix = details ? ` (${details})` : '';
22
+ return new errors_1.ContractError(`${context}: ${reason}${suffix}`);
22
23
  }
23
24
  return new errors_1.ContractError(`${context}: ${String(error)}`);
24
25
  }
@@ -26,6 +27,11 @@ const DEFAULT_REMUNERATE_GAS_LIMIT = 8000000n;
26
27
  const DEFAULT_PAY_TAB_ERC20_GAS_LIMIT = 300000n;
27
28
  const DEFAULT_MAX_FEE_PER_GAS = (0, viem_1.parseGwei)('0.1');
28
29
  const DEFAULT_MAX_PRIORITY_FEE_PER_GAS = (0, viem_1.parseGwei)('0.1');
30
+ const DEFAULT_RECEIPT_TIMEOUT_MS = 60_000;
31
+ const DEFAULT_RECEIPT_POLLING_INTERVAL_MS = 2_000;
32
+ function sleep(ms) {
33
+ return new Promise((resolve) => setTimeout(resolve, ms));
34
+ }
29
35
  class ContractGateway {
30
36
  publicClient;
31
37
  walletClient;
@@ -41,6 +47,7 @@ class ContractGateway {
41
47
  const chain = (0, chain_1.getChain)(chainId, rpcUrl);
42
48
  const publicClient = (0, viem_1.createPublicClient)({
43
49
  transport: (0, viem_1.http)(rpcUrl),
50
+ pollingInterval: 2_000,
44
51
  });
45
52
  const rpcChainId = await publicClient.getChainId();
46
53
  if (rpcChainId !== Number(chainId)) {
@@ -85,7 +92,7 @@ class ContractGateway {
85
92
  }
86
93
  splitWaitOptions(waitOptions) {
87
94
  if (!waitOptions) {
88
- return { receipt: {} };
95
+ return { receipt: { timeout: DEFAULT_RECEIPT_TIMEOUT_MS } };
89
96
  }
90
97
  const { gas, timeout, pollingInterval } = waitOptions;
91
98
  return {
@@ -108,6 +115,16 @@ class ContractGateway {
108
115
  const erc20 = this.erc20(token);
109
116
  const spender = this.contract.address;
110
117
  const targetAllowance = (0, utils_1.parseU256)(amount);
118
+ const account = this.walletClient.account;
119
+ if (account) {
120
+ const currentAllowance = (await erc20.read.allowance([
121
+ account.address,
122
+ spender,
123
+ ]));
124
+ if (currentAllowance >= targetAllowance) {
125
+ return undefined;
126
+ }
127
+ }
111
128
  const sendApprove = async (value) => {
112
129
  const hash = await this.enqueueTx(() => erc20.write.approve([spender, value], this.defaultFeeParams()));
113
130
  const txReceipt = await this.publicClient.waitForTransactionReceipt({ hash, ...receipt });
@@ -134,16 +151,8 @@ class ContractGateway {
134
151
  throw wrapViemError(retryError, 'ERC20 approve failed after allowance reset');
135
152
  }
136
153
  }
137
- // Verify the allowance was actually set on-chain. The catch path above can
138
- // leave allowance at 0 if the re-approve transaction fails silently.
139
- // Read at "latest" — waitForTransactionReceipt already confirmed the block,
140
- // and many public RPCs reject eth_call at a specific recent blockNumber.
141
- const account = this.walletClient.account;
142
154
  if (account) {
143
- const actual = (await erc20.read.allowance([
144
- account.address,
145
- spender,
146
- ]));
155
+ const actual = await this.waitForErc20Allowance(erc20, account.address, spender, targetAllowance, receipt);
147
156
  if (actual < targetAllowance) {
148
157
  throw new errors_1.ContractError(`ERC20 allowance verification failed: on-chain allowance is ${actual} but expected ${targetAllowance}. ` +
149
158
  `Try calling approveErc20 again.`);
@@ -151,6 +160,20 @@ class ContractGateway {
151
160
  }
152
161
  return txReceipt;
153
162
  }
163
+ async waitForErc20Allowance(erc20, owner, spender, targetAllowance, receiptOptions) {
164
+ const timeout = receiptOptions.timeout ?? DEFAULT_RECEIPT_TIMEOUT_MS;
165
+ const pollingInterval = receiptOptions.pollingInterval ?? DEFAULT_RECEIPT_POLLING_INTERVAL_MS;
166
+ const deadline = Date.now() + timeout;
167
+ let actual = 0n;
168
+ while (Date.now() <= deadline) {
169
+ actual = (await erc20.read.allowance([owner, spender]));
170
+ if (actual >= targetAllowance) {
171
+ return actual;
172
+ }
173
+ await sleep(Math.min(pollingInterval, Math.max(0, deadline - Date.now())));
174
+ }
175
+ return actual;
176
+ }
154
177
  async deposit(amount, erc20Token, waitOptions) {
155
178
  const { receipt } = this.splitWaitOptions(waitOptions);
156
179
  const parsedAmount = (0, utils_1.parseU256)(amount);
@@ -4,6 +4,8 @@ export interface NetworkInfo {
4
4
  caip2: string;
5
5
  /** Hosted 4Mica core API URL for this network. */
6
6
  rpcUrl: string;
7
+ /** Reliable public Ethereum RPC for on-chain calls (fallback when server doesn't provide one). */
8
+ publicRpcUrl: string;
7
9
  }
8
10
  /**
9
11
  * Hosted 4Mica network deployments, keyed by human-readable shorthand.
@@ -29,12 +31,10 @@ export declare const NETWORKS: Record<string, NetworkInfo>;
29
31
  /**
30
32
  * Resolve a network shorthand or CAIP-2 identifier to a core API URL.
31
33
  * Returns `undefined` if the identifier is not a known hosted network.
32
- *
33
- * @example
34
- * ```ts
35
- * resolveNetworkRpcUrl("base"); // "https://base.api.4mica.xyz/"
36
- * resolveNetworkRpcUrl("eip155:8453"); // "https://base.api.4mica.xyz/"
37
- * resolveNetworkRpcUrl("eip155:1"); // undefined
38
- * ```
39
34
  */
40
35
  export declare function resolveNetworkRpcUrl(network: string): string | undefined;
36
+ /**
37
+ * Resolve a CAIP-2 identifier to a reliable public Ethereum RPC URL.
38
+ * Returns `undefined` for unknown networks.
39
+ */
40
+ export declare function resolvePublicRpcUrl(caip2: string): string | undefined;
package/dist/networks.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NETWORKS = void 0;
4
4
  exports.resolveNetworkRpcUrl = resolveNetworkRpcUrl;
5
+ exports.resolvePublicRpcUrl = resolvePublicRpcUrl;
5
6
  /**
6
7
  * Hosted 4Mica network deployments, keyed by human-readable shorthand.
7
8
  *
@@ -26,28 +27,31 @@ exports.NETWORKS = {
26
27
  base: {
27
28
  caip2: 'eip155:8453',
28
29
  rpcUrl: 'https://base.api.4mica.xyz/',
30
+ publicRpcUrl: 'https://base-rpc.publicnode.com',
29
31
  },
30
32
  'base-sepolia': {
31
33
  caip2: 'eip155:84532',
32
34
  rpcUrl: 'https://base.sepolia.api.4mica.xyz/',
35
+ publicRpcUrl: 'https://base-sepolia-rpc.publicnode.com',
33
36
  },
34
37
  'ethereum-sepolia': {
35
38
  caip2: 'eip155:11155111',
36
39
  rpcUrl: 'https://ethereum.sepolia.api.4mica.xyz/',
40
+ publicRpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com',
37
41
  },
38
42
  };
39
43
  const NETWORKS_BY_CAIP2 = Object.fromEntries(Object.values(exports.NETWORKS).map((n) => [n.caip2, n]));
40
44
  /**
41
45
  * Resolve a network shorthand or CAIP-2 identifier to a core API URL.
42
46
  * Returns `undefined` if the identifier is not a known hosted network.
43
- *
44
- * @example
45
- * ```ts
46
- * resolveNetworkRpcUrl("base"); // "https://base.api.4mica.xyz/"
47
- * resolveNetworkRpcUrl("eip155:8453"); // "https://base.api.4mica.xyz/"
48
- * resolveNetworkRpcUrl("eip155:1"); // undefined
49
- * ```
50
47
  */
51
48
  function resolveNetworkRpcUrl(network) {
52
49
  return exports.NETWORKS[network]?.rpcUrl ?? NETWORKS_BY_CAIP2[network]?.rpcUrl;
53
50
  }
51
+ /**
52
+ * Resolve a CAIP-2 identifier to a reliable public Ethereum RPC URL.
53
+ * Returns `undefined` for unknown networks.
54
+ */
55
+ function resolvePublicRpcUrl(caip2) {
56
+ return NETWORKS_BY_CAIP2[caip2]?.publicRpcUrl;
57
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4mica/sdk",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "TypeScript SDK for interacting with the 4Mica payment network",
5
5
  "license": "MIT",
6
6
  "repository": {