@chipi-stack/chipi-react 14.15.0 → 14.16.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/dist/index.mjs CHANGED
@@ -20394,6 +20394,41 @@ function hexEq(a, b) {
20394
20394
  return a === b;
20395
20395
  }
20396
20396
  }
20397
+ function parseCapAmount(value, field, token) {
20398
+ try {
20399
+ const v = typeof value === "bigint" ? value : BigInt(String(value).trim());
20400
+ if (v < 0n) throw new Error("negative");
20401
+ return v;
20402
+ } catch {
20403
+ throw new Error(
20404
+ `session_spend_cap_invalid: ${field} for token ${token} must be a non-negative integer in base units (got "${String(value)}")`
20405
+ );
20406
+ }
20407
+ }
20408
+ function toSpendingPolicies(caps, lifetimeSeconds) {
20409
+ return caps.map((cap) => {
20410
+ const policy = "maxPerWindow" in cap ? {
20411
+ token: cap.token,
20412
+ maxPerCall: parseCapAmount(cap.maxPerCall, "maxPerCall", cap.token),
20413
+ maxPerWindow: parseCapAmount(cap.maxPerWindow, "maxPerWindow", cap.token),
20414
+ windowSeconds: cap.windowSeconds
20415
+ } : (() => {
20416
+ const amount = parseCapAmount(cap.amount, "amount", cap.token);
20417
+ return {
20418
+ token: cap.token,
20419
+ maxPerCall: amount,
20420
+ maxPerWindow: amount,
20421
+ windowSeconds: Math.max(1, lifetimeSeconds)
20422
+ };
20423
+ })();
20424
+ if (policy.maxPerWindow === 0n) {
20425
+ throw new Error(
20426
+ `session_spend_cap_zero: the cap for token ${cap.token} resolves to maxPerWindow = 0, which means unlimited on-chain. Use a positive cap.`
20427
+ );
20428
+ }
20429
+ return policy;
20430
+ });
20431
+ }
20397
20432
  function sessionCovers(policy, calls) {
20398
20433
  if (!policy?.allowedCalls?.length) return false;
20399
20434
  return calls.every(
@@ -20408,6 +20443,10 @@ function useChipiWalletObject(config3) {
20408
20443
  const { signTypedDataAsync } = useSignTypedData();
20409
20444
  const { createSessionKeyAsync } = useCreateSessionKey();
20410
20445
  const { addSessionKeyToContractAsync } = useAddSessionKeyToContract();
20446
+ const { chipiSDK } = useChipiContext();
20447
+ const sdk = useRef(chipiSDK);
20448
+ sdk.current = chipiSDK;
20449
+ const warnedMissingApproval = useRef(false);
20411
20450
  const cfg = useRef(config3);
20412
20451
  cfg.current = config3;
20413
20452
  const requireWallet = () => {
@@ -20420,28 +20459,33 @@ function useChipiWalletObject(config3) {
20420
20459
  if (policy.validUntil <= nowSec()) throw new Error("session_policy_expired");
20421
20460
  const c = cfg.current;
20422
20461
  const wallet = requireWallet();
20462
+ const lifetimeSeconds = Math.max(1, policy.validUntil - nowSec());
20463
+ const spendingPolicies = policy.spendCaps?.length ? toSpendingPolicies(policy.spendCaps, lifetimeSeconds) : [];
20423
20464
  const encryptKey = await c.getEncryptKey();
20424
20465
  const session = await createSessionKeyAsync({
20425
20466
  encryptKey,
20426
- durationSeconds: Math.max(1, policy.validUntil - nowSec())
20467
+ durationSeconds: lifetimeSeconds
20427
20468
  });
20428
20469
  const bearerToken = await c.getBearerToken();
20429
- await addSessionKeyToContractAsync({
20430
- params: {
20431
- encryptKey,
20432
- wallet,
20433
- sessionConfig: {
20434
- sessionPublicKey: session.publicKey,
20435
- validUntil: policy.validUntil,
20436
- maxCalls: 1e3,
20437
- // selectors; empty = all entrypoints allowed
20438
- allowedEntrypoints: policy.allowedCalls.map(
20439
- (a) => hash_exports.getSelectorFromName(a.entrypoint)
20440
- )
20441
- }
20442
- },
20443
- bearerToken
20444
- });
20470
+ const params = {
20471
+ encryptKey,
20472
+ wallet,
20473
+ sessionConfig: {
20474
+ sessionPublicKey: session.publicKey,
20475
+ validUntil: policy.validUntil,
20476
+ maxCalls: 1e3,
20477
+ // Selectors; empty = all entrypoints allowed. On-chain this is a
20478
+ // per-selector whitelist, NOT scoped to allowedCalls' contracts.
20479
+ allowedEntrypoints: policy.allowedCalls.map(
20480
+ (a) => hash_exports.getSelectorFromName(a.entrypoint)
20481
+ )
20482
+ }
20483
+ };
20484
+ if (spendingPolicies.length > 0) {
20485
+ await sdk.current.sessions.setupSession({ ...params, spendingPolicies }, bearerToken);
20486
+ } else {
20487
+ await addSessionKeyToContractAsync({ params, bearerToken });
20488
+ }
20445
20489
  return session;
20446
20490
  },
20447
20491
  [createSessionKeyAsync, addSessionKeyToContractAsync]
@@ -20486,7 +20530,17 @@ function useChipiWalletObject(config3) {
20486
20530
  createSession: async (policy) => {
20487
20531
  await createSession(policy);
20488
20532
  },
20489
- requestApproval: (req) => cfg.current.requestApproval?.(req) ?? Promise.resolve(true)
20533
+ requestApproval: (req) => {
20534
+ const gate = cfg.current.requestApproval;
20535
+ if (gate) return gate(req);
20536
+ if (!warnedMissingApproval.current) {
20537
+ warnedMissingApproval.current = true;
20538
+ console.warn(
20539
+ "[ChipiSDK:Connect] No `requestApproval` was provided to useChipiWalletObject, so every approval (connect, execute, signTypedData, session) is denied. Pass `requestApproval` to show the user an approval UI and resolve true/false."
20540
+ );
20541
+ }
20542
+ return Promise.resolve(false);
20543
+ }
20490
20544
  }),
20491
20545
  [callAnyContractAsync, executeWithSessionAsync, signTypedDataAsync, createSession]
20492
20546
  );
@@ -21166,10 +21220,33 @@ var USER_REFUSED_OP = 113;
21166
21220
  var INVALID_REQUEST_PAYLOAD = 114;
21167
21221
  var ACCOUNT_ALREADY_DEPLOYED = 115;
21168
21222
  var UNKNOWN_ERROR = 163;
21223
+ var MISSING_APPROVAL_WARNING = "[ChipiSDK:Connect] No `requestApproval` was provided, so every approval (connect, execute, signTypedData, session) is denied. Pass `requestApproval` to show the user an approval UI and resolve true/false.";
21224
+ function normalizeChainId(value) {
21225
+ try {
21226
+ if (typeof value === "bigint") return value;
21227
+ if (typeof value === "number") {
21228
+ return Number.isSafeInteger(value) && value >= 0 ? BigInt(value) : null;
21229
+ }
21230
+ if (typeof value !== "string" || value.length === 0) return null;
21231
+ if (/^0x[0-9a-f]+$/i.test(value) || /^[0-9]+$/.test(value)) return BigInt(value);
21232
+ return BigInt(shortString_exports.encodeShortString(value));
21233
+ } catch {
21234
+ return null;
21235
+ }
21236
+ }
21169
21237
  function createChipiWindowObject(deps) {
21170
21238
  const accountListeners = /* @__PURE__ */ new Set();
21171
21239
  const networkListeners = /* @__PURE__ */ new Set();
21172
- const approve = deps.requestApproval ?? (async () => true);
21240
+ let approved = false;
21241
+ let pendingConnect = null;
21242
+ let warnedMissingApproval = false;
21243
+ const approve = deps.requestApproval ?? (async () => {
21244
+ if (!warnedMissingApproval) {
21245
+ warnedMissingApproval = true;
21246
+ console.warn(MISSING_APPROVAL_WARNING);
21247
+ }
21248
+ return false;
21249
+ });
21173
21250
  async function requireApproval(req) {
21174
21251
  const ok = await approve(req);
21175
21252
  if (!ok) throw new RpcError2(USER_REFUSED_OP, "User refused the operation");
@@ -21177,19 +21254,27 @@ function createChipiWindowObject(deps) {
21177
21254
  const request = (async (call) => {
21178
21255
  switch (call.type) {
21179
21256
  case "wallet_requestAccounts": {
21180
- const accounts = await deps.getAccounts();
21181
- if (accounts.length === 0) {
21182
- await requireApproval({ type: "connect" });
21183
- return deps.getAccounts();
21257
+ if (call.params?.silent_mode) {
21258
+ return approved ? deps.getAccounts() : [];
21259
+ }
21260
+ if (approved) {
21261
+ const accounts = await deps.getAccounts();
21262
+ if (accounts.length > 0) return accounts;
21184
21263
  }
21185
- return accounts;
21264
+ if (!pendingConnect) {
21265
+ pendingConnect = requireApproval({ type: "connect" }).then(() => {
21266
+ approved = true;
21267
+ }).finally(() => {
21268
+ pendingConnect = null;
21269
+ });
21270
+ }
21271
+ await pendingConnect;
21272
+ return deps.getAccounts();
21186
21273
  }
21187
21274
  case "wallet_requestChainId":
21188
21275
  return deps.chainId;
21189
- case "wallet_getPermissions": {
21190
- const accounts = await deps.getAccounts();
21191
- return accounts.length > 0 ? ["accounts"] : [];
21192
- }
21276
+ case "wallet_getPermissions":
21277
+ return approved ? ["accounts"] : [];
21193
21278
  case "wallet_addInvokeTransaction": {
21194
21279
  const wireCalls = call.params?.calls ?? [];
21195
21280
  if (wireCalls.length === 0) {
@@ -21202,14 +21287,31 @@ function createChipiWindowObject(deps) {
21202
21287
  }
21203
21288
  case "wallet_signTypedData": {
21204
21289
  const typedData = call.params;
21290
+ const domainChainId = typedData?.domain?.chainId;
21291
+ if (domainChainId !== void 0 && domainChainId !== null) {
21292
+ const expected = normalizeChainId(deps.chainId);
21293
+ const actual = normalizeChainId(domainChainId);
21294
+ if (actual === null || expected === null || actual !== expected) {
21295
+ throw new RpcError2(
21296
+ INVALID_REQUEST_PAYLOAD,
21297
+ `wallet_signTypedData: domain.chainId ${String(domainChainId)} does not match the wallet chain ${deps.chainId}`
21298
+ );
21299
+ }
21300
+ }
21205
21301
  await requireApproval({ type: "signTypedData", typedData });
21206
21302
  return deps.signTypedData(typedData);
21207
21303
  }
21208
21304
  case "wallet_deploymentData":
21209
21305
  try {
21210
21306
  return await deps.getDeploymentData();
21211
- } catch {
21212
- throw new RpcError2(ACCOUNT_ALREADY_DEPLOYED, "Account already deployed");
21307
+ } catch (err2) {
21308
+ if (err2 instanceof Error && err2.message === "account_already_deployed") {
21309
+ throw new RpcError2(ACCOUNT_ALREADY_DEPLOYED, "Account already deployed");
21310
+ }
21311
+ throw new RpcError2(
21312
+ UNKNOWN_ERROR,
21313
+ err2 instanceof Error ? err2.message : "Could not read deployment data"
21314
+ );
21213
21315
  }
21214
21316
  case "wallet_supportedSpecs":
21215
21317
  return SUPPORTED_SPECS;