@4mica/sdk 1.2.2 → 1.2.5

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.
@@ -32,7 +32,9 @@ export declare class UserClient {
32
32
  * locked collateral and any pending withdrawal.
33
33
  * @throws {@link ContractError} if the contract call fails.
34
34
  */
35
- getUser(): Promise<UserInfo[]>;
35
+ getUser(opts?: {
36
+ blockNumber?: bigint;
37
+ }): Promise<UserInfo[]>;
36
38
  /**
37
39
  * Query the on-chain payment status of a tab.
38
40
  *
@@ -43,8 +43,8 @@ class UserClient {
43
43
  * locked collateral and any pending withdrawal.
44
44
  * @throws {@link ContractError} if the contract call fails.
45
45
  */
46
- async getUser() {
47
- const assets = await this.client.gateway.getUserAssets();
46
+ async getUser(opts) {
47
+ const assets = await this.client.gateway.getUserAssets(opts);
48
48
  return assets.map((a) => ({
49
49
  asset: a.asset,
50
50
  collateral: (0, utils_1.parseU256)(a.collateral),
package/dist/config.d.ts CHANGED
@@ -48,15 +48,15 @@ export declare class ConfigBuilder {
48
48
  * Resolves to the corresponding core API URL.
49
49
  * Mutually exclusive with {@link rpcUrl} — last call wins.
50
50
  *
51
- * Supported values: `"base-sepolia"` / `"eip155:84532"`,
51
+ * Supported values: `"base"` / `"eip155:8453"`, `"base-sepolia"` / `"eip155:84532"`,
52
52
  * `"ethereum-sepolia"` / `"eip155:11155111"`.
53
53
  *
54
54
  * @throws {@link ConfigError} if the network is not recognised.
55
55
  *
56
56
  * @example
57
57
  * ```ts
58
- * new ConfigBuilder().network("base-sepolia").walletPrivateKey("0x...").build();
59
- * new ConfigBuilder().network("eip155:84532").walletPrivateKey("0x...").build();
58
+ * new ConfigBuilder().network("base").walletPrivateKey("0x...").build();
59
+ * new ConfigBuilder().network("eip155:8453").walletPrivateKey("0x...").build();
60
60
  * ```
61
61
  */
62
62
  network(value: string): ConfigBuilder;
@@ -82,7 +82,7 @@ export declare class ConfigBuilder {
82
82
  * Load configuration from environment variables.
83
83
  *
84
84
  * Recognised variables:
85
- * - `4MICA_NETWORK` — shorthand or CAIP-2 id (e.g. `base-sepolia`); takes precedence over `4MICA_RPC_URL`
85
+ * - `4MICA_NETWORK` — shorthand or CAIP-2 id (e.g. `base`); takes precedence over `4MICA_RPC_URL`
86
86
  * - `4MICA_RPC_URL`
87
87
  * - `4MICA_WALLET_PRIVATE_KEY`
88
88
  * - `4MICA_ETHEREUM_HTTP_RPC_URL`
package/dist/config.js CHANGED
@@ -38,21 +38,21 @@ class ConfigBuilder {
38
38
  * Resolves to the corresponding core API URL.
39
39
  * Mutually exclusive with {@link rpcUrl} — last call wins.
40
40
  *
41
- * Supported values: `"base-sepolia"` / `"eip155:84532"`,
41
+ * Supported values: `"base"` / `"eip155:8453"`, `"base-sepolia"` / `"eip155:84532"`,
42
42
  * `"ethereum-sepolia"` / `"eip155:11155111"`.
43
43
  *
44
44
  * @throws {@link ConfigError} if the network is not recognised.
45
45
  *
46
46
  * @example
47
47
  * ```ts
48
- * new ConfigBuilder().network("base-sepolia").walletPrivateKey("0x...").build();
49
- * new ConfigBuilder().network("eip155:84532").walletPrivateKey("0x...").build();
48
+ * new ConfigBuilder().network("base").walletPrivateKey("0x...").build();
49
+ * new ConfigBuilder().network("eip155:8453").walletPrivateKey("0x...").build();
50
50
  * ```
51
51
  */
52
52
  network(value) {
53
53
  const url = (0, networks_1.resolveNetworkRpcUrl)(value);
54
54
  if (!url) {
55
- throw new errors_1.ConfigError(`unknown network "${value}". Use a known shorthand (e.g. "base-sepolia") or CAIP-2 id, or call rpcUrl() directly.`);
55
+ throw new errors_1.ConfigError(`unknown network "${value}". Use a known shorthand (e.g. "base") or CAIP-2 id, or call rpcUrl() directly.`);
56
56
  }
57
57
  this._rpcUrl = url;
58
58
  return this;
@@ -108,7 +108,7 @@ class ConfigBuilder {
108
108
  * Load configuration from environment variables.
109
109
  *
110
110
  * Recognised variables:
111
- * - `4MICA_NETWORK` — shorthand or CAIP-2 id (e.g. `base-sepolia`); takes precedence over `4MICA_RPC_URL`
111
+ * - `4MICA_NETWORK` — shorthand or CAIP-2 id (e.g. `base`); takes precedence over `4MICA_RPC_URL`
112
112
  * - `4MICA_RPC_URL`
113
113
  * - `4MICA_WALLET_PRIVATE_KEY`
114
114
  * - `4MICA_ETHEREUM_HTTP_RPC_URL`
@@ -122,7 +122,7 @@ class ConfigBuilder {
122
122
  const env = process.env;
123
123
  if (env['4MICA_NETWORK'])
124
124
  this.network(env['4MICA_NETWORK']);
125
- if (env['4MICA_RPC_URL'])
125
+ else if (env['4MICA_RPC_URL'])
126
126
  this._rpcUrl = env['4MICA_RPC_URL'];
127
127
  if (env['4MICA_WALLET_PRIVATE_KEY'])
128
128
  this._walletPrivateKey = env['4MICA_WALLET_PRIVATE_KEY'];
@@ -31,7 +31,9 @@ export declare class ContractGateway {
31
31
  }>;
32
32
  approveErc20(token: string, amount: number | bigint | string, waitOptions?: TxReceiptWaitOptions): Promise<import("viem").TransactionReceipt>;
33
33
  deposit(amount: number | bigint | string, erc20Token?: string, waitOptions?: TxReceiptWaitOptions): Promise<import("viem").TransactionReceipt>;
34
- getUserAssets(): Promise<{
34
+ getUserAssets(opts?: {
35
+ blockNumber?: bigint;
36
+ }): Promise<{
35
37
  asset: `0x${string}`;
36
38
  collateral: bigint;
37
39
  withdrawalRequestTimestamp: bigint;
package/dist/contract.js CHANGED
@@ -6,6 +6,22 @@ const core4mica_1 = require("./abi/core4mica");
6
6
  const chain_1 = require("./chain");
7
7
  const errors_1 = require("./errors");
8
8
  const utils_1 = require("./utils");
9
+ /**
10
+ * Extract a human-readable message from a viem contract error, falling back
11
+ * to the raw message if no structured reason is available.
12
+ */
13
+ function wrapViemError(error, context) {
14
+ if (error instanceof errors_1.ContractError)
15
+ return error;
16
+ if (error instanceof Error) {
17
+ const e = error;
18
+ const reason = e['cause']?.['reason'] ??
19
+ e['shortMessage'] ??
20
+ error.message;
21
+ return new errors_1.ContractError(`${context}: ${reason}`);
22
+ }
23
+ return new errors_1.ContractError(`${context}: ${String(error)}`);
24
+ }
9
25
  const DEFAULT_REMUNERATE_GAS_LIMIT = 8000000n;
10
26
  const DEFAULT_PAY_TAB_ERC20_GAS_LIMIT = 300000n;
11
27
  const DEFAULT_MAX_FEE_PER_GAS = (0, viem_1.parseGwei)('0.1');
@@ -100,36 +116,82 @@ class ContractGateway {
100
116
  }
101
117
  return txReceipt;
102
118
  };
119
+ let txReceipt;
103
120
  try {
104
- return await sendApprove(targetAllowance);
121
+ txReceipt = await sendApprove(targetAllowance);
105
122
  }
106
123
  catch (error) {
107
- // Some ERC20s require resetting allowance to zero before setting a new value.
124
+ // Some ERC20s (e.g. USDT) require resetting allowance to zero before
125
+ // setting a new non-zero value.
108
126
  if (targetAllowance === 0n) {
109
- throw error;
127
+ throw wrapViemError(error, 'ERC20 approve failed');
128
+ }
129
+ try {
130
+ await sendApprove(0n);
131
+ txReceipt = await sendApprove(targetAllowance);
132
+ }
133
+ catch (retryError) {
134
+ throw wrapViemError(retryError, 'ERC20 approve failed after allowance reset');
110
135
  }
111
- await sendApprove(0n);
112
- return sendApprove(targetAllowance);
113
136
  }
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 the confirmed block number so the result is consistent regardless
140
+ // of RPC node propagation lag.
141
+ const account = this.walletClient.account;
142
+ if (account) {
143
+ const actual = (await erc20.read.allowance([account.address, spender], {
144
+ blockNumber: txReceipt.blockNumber,
145
+ }));
146
+ if (actual < targetAllowance) {
147
+ throw new errors_1.ContractError(`ERC20 allowance verification failed: on-chain allowance is ${actual} but expected ${targetAllowance}. ` +
148
+ `Try calling approveErc20 again.`);
149
+ }
150
+ }
151
+ return txReceipt;
114
152
  }
115
153
  async deposit(amount, erc20Token, waitOptions) {
116
154
  const { receipt } = this.splitWaitOptions(waitOptions);
155
+ const parsedAmount = (0, utils_1.parseU256)(amount);
117
156
  let hash;
118
157
  if (erc20Token) {
119
- hash = await this.enqueueTx(() => this.contract.write.depositStablecoin([erc20Token, (0, utils_1.parseU256)(amount)], this.defaultFeeParams()));
158
+ // Pre-check allowance to surface a clear error before hitting the contract.
159
+ const account = this.walletClient.account;
160
+ if (account) {
161
+ const erc20 = this.erc20(erc20Token);
162
+ const allowance = await erc20.read.allowance([
163
+ account.address,
164
+ this.contract.address,
165
+ ]);
166
+ if (allowance < parsedAmount) {
167
+ throw new errors_1.ContractError(`Insufficient ERC20 allowance: ${allowance} approved but ${parsedAmount} required. ` +
168
+ `Call approveErc20("${erc20Token}", ${parsedAmount}) before depositing.`);
169
+ }
170
+ }
171
+ try {
172
+ hash = await this.enqueueTx(() => this.contract.write.depositStablecoin([erc20Token, parsedAmount], this.defaultFeeParams()));
173
+ }
174
+ catch (error) {
175
+ throw wrapViemError(error, 'depositStablecoin failed');
176
+ }
120
177
  }
121
178
  else {
122
- hash = await this.enqueueTx(() => this.contract.write.deposit({ value: (0, utils_1.parseU256)(amount), ...this.defaultFeeParams() }));
179
+ try {
180
+ hash = await this.enqueueTx(() => this.contract.write.deposit({ value: parsedAmount, ...this.defaultFeeParams() }));
181
+ }
182
+ catch (error) {
183
+ throw wrapViemError(error, 'deposit failed');
184
+ }
123
185
  }
124
186
  return this.publicClient.waitForTransactionReceipt({ hash, ...receipt });
125
187
  }
126
- async getUserAssets() {
188
+ async getUserAssets(opts) {
127
189
  const account = this.walletClient.account;
128
190
  if (!account) {
129
191
  throw new errors_1.ContractError('wallet client has no account configured');
130
192
  }
131
193
  const addr = account.address;
132
- const result = await this.contract.read.getUserAllAssets([addr]);
194
+ const result = await this.contract.read.getUserAllAssets([addr], opts);
133
195
  return result.map((a) => ({
134
196
  asset: a.asset,
135
197
  collateral: a.collateral,
@@ -17,12 +17,12 @@ export interface NetworkInfo {
17
17
  *
18
18
  * // By shorthand
19
19
  * const cfg = new ConfigBuilder()
20
- * .network("base-sepolia")
20
+ * .network("base")
21
21
  * .walletPrivateKey("0x...")
22
22
  * .build();
23
23
  *
24
24
  * // Inspect available networks
25
- * console.log(NETWORKS["base-sepolia"].caip2); // "eip155:84532"
25
+ * console.log(NETWORKS["base"].caip2); // "eip155:8453"
26
26
  * ```
27
27
  */
28
28
  export declare const NETWORKS: Record<string, NetworkInfo>;
@@ -32,8 +32,8 @@ export declare const NETWORKS: Record<string, NetworkInfo>;
32
32
  *
33
33
  * @example
34
34
  * ```ts
35
- * resolveNetworkRpcUrl("base-sepolia"); // "https://base.sepolia.api.4mica.xyz/"
36
- * resolveNetworkRpcUrl("eip155:84532"); // "https://base.sepolia.api.4mica.xyz/"
35
+ * resolveNetworkRpcUrl("base"); // "https://base.api.4mica.xyz/"
36
+ * resolveNetworkRpcUrl("eip155:8453"); // "https://base.api.4mica.xyz/"
37
37
  * resolveNetworkRpcUrl("eip155:1"); // undefined
38
38
  * ```
39
39
  */
package/dist/networks.js CHANGED
@@ -14,15 +14,19 @@ exports.resolveNetworkRpcUrl = resolveNetworkRpcUrl;
14
14
  *
15
15
  * // By shorthand
16
16
  * const cfg = new ConfigBuilder()
17
- * .network("base-sepolia")
17
+ * .network("base")
18
18
  * .walletPrivateKey("0x...")
19
19
  * .build();
20
20
  *
21
21
  * // Inspect available networks
22
- * console.log(NETWORKS["base-sepolia"].caip2); // "eip155:84532"
22
+ * console.log(NETWORKS["base"].caip2); // "eip155:8453"
23
23
  * ```
24
24
  */
25
25
  exports.NETWORKS = {
26
+ base: {
27
+ caip2: 'eip155:8453',
28
+ rpcUrl: 'https://base.api.4mica.xyz/',
29
+ },
26
30
  'base-sepolia': {
27
31
  caip2: 'eip155:84532',
28
32
  rpcUrl: 'https://base.sepolia.api.4mica.xyz/',
@@ -39,8 +43,8 @@ const NETWORKS_BY_CAIP2 = Object.fromEntries(Object.values(exports.NETWORKS).map
39
43
  *
40
44
  * @example
41
45
  * ```ts
42
- * resolveNetworkRpcUrl("base-sepolia"); // "https://base.sepolia.api.4mica.xyz/"
43
- * resolveNetworkRpcUrl("eip155:84532"); // "https://base.sepolia.api.4mica.xyz/"
46
+ * resolveNetworkRpcUrl("base"); // "https://base.api.4mica.xyz/"
47
+ * resolveNetworkRpcUrl("eip155:8453"); // "https://base.api.4mica.xyz/"
44
48
  * resolveNetworkRpcUrl("eip155:1"); // undefined
45
49
  * ```
46
50
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4mica/sdk",
3
- "version": "1.2.2",
3
+ "version": "1.2.5",
4
4
  "description": "TypeScript SDK for interacting with the 4Mica payment network",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,6 +45,9 @@
45
45
  "test:e2e": "vitest run --config vitest.e2e.config.ts",
46
46
  "example:base-sepolia:x402-v2": "node_modules/.bin/tsx examples/base-sepolia-x402-facilitator-e2e.ts",
47
47
  "example:manual:agents": "node_modules/.bin/tsx examples/manual-agents/demo.ts",
48
+ "example:wachai:v2": "node_modules/.bin/tsx examples/wachai-v2-demo/demo.ts",
49
+ "example:deposit": "node_modules/.bin/tsx examples/deposit.ts",
50
+ "example:yield": "node_modules/.bin/tsx examples/yield.ts",
48
51
  "lint": "eslint . --ext .ts",
49
52
  "fmt": "prettier --check \"{src,tests}/**/*.{ts,js,json}\""
50
53
  },