@cartridge/controller 0.7.14-alpha.2 → 0.8.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.
Files changed (45) hide show
  1. package/.turbo/turbo-build$colon$deps.log +49 -51
  2. package/.turbo/turbo-build.log +45 -47
  3. package/.turbo/turbo-format$colon$check.log +7 -0
  4. package/.turbo/turbo-format.log +44 -0
  5. package/dist/__tests__/setup.d.ts +0 -0
  6. package/dist/controller.d.ts +6 -4
  7. package/dist/iframe/base.d.ts +1 -0
  8. package/dist/iframe/profile.d.ts +1 -1
  9. package/dist/index.js +846 -4906
  10. package/dist/index.js.map +1 -1
  11. package/dist/node/index.cjs +2 -2
  12. package/dist/node/index.cjs.map +1 -1
  13. package/dist/node/index.js +2 -2
  14. package/dist/node/index.js.map +1 -1
  15. package/dist/{provider-vgdJbfDg.js → provider-ClUbos7A.js} +98 -75
  16. package/dist/provider-ClUbos7A.js.map +1 -0
  17. package/dist/session/account.d.ts +2 -2
  18. package/dist/session.js +3 -3
  19. package/dist/session.js.map +1 -1
  20. package/dist/stats.html +1 -1
  21. package/dist/types.d.ts +7 -6
  22. package/dist/utils.d.ts +3 -3
  23. package/dist/wallets/index.d.ts +0 -2
  24. package/jest.config.ts +2 -0
  25. package/package.json +5 -5
  26. package/src/__tests__/controllerDefaults.test.ts +80 -0
  27. package/src/__tests__/parseChainId.test.ts +0 -6
  28. package/src/__tests__/setup.ts +29 -0
  29. package/src/controller.ts +97 -21
  30. package/src/iframe/base.ts +19 -1
  31. package/src/iframe/profile.ts +0 -11
  32. package/src/node/account.ts +2 -2
  33. package/src/session/account.ts +2 -2
  34. package/src/types.ts +8 -5
  35. package/src/utils.ts +49 -13
  36. package/src/wallets/bridge.ts +9 -5
  37. package/src/wallets/index.ts +0 -2
  38. package/src/wallets/metamask/index.ts +18 -9
  39. package/src/wallets/rabby/index.ts +5 -6
  40. package/tsconfig.json +3 -2
  41. package/dist/provider-vgdJbfDg.js.map +0 -1
  42. package/dist/wallets/turnkey/index.d.ts +0 -20
  43. package/dist/wallets/wallet-connect/index.d.ts +0 -19
  44. package/src/wallets/turnkey/index.ts +0 -195
  45. package/src/wallets/wallet-connect/index.ts +0 -206
package/dist/types.d.ts CHANGED
@@ -76,7 +76,7 @@ type CartridgeID = string;
76
76
  export type ControllerAccounts = Record<ContractAddress, CartridgeID>;
77
77
  export interface Keychain {
78
78
  probe(rpcUrl: string): Promise<ProbeReply | ConnectError>;
79
- connect(policies: SessionPolicies, rpcUrl: string, signupOptions?: AuthOptions): Promise<ConnectReply | ConnectError>;
79
+ connect(policies: SessionPolicies, rpcUrl: string, signupOptions?: AuthOptions, version?: string): Promise<ConnectReply | ConnectError>;
80
80
  disconnect(): void;
81
81
  reset(): void;
82
82
  revoke(origin: string): void;
@@ -122,8 +122,8 @@ export type Chain = {
122
122
  rpcUrl: string;
123
123
  };
124
124
  export type ProviderOptions = {
125
- defaultChainId: ChainId;
126
- chains: Chain[];
125
+ defaultChainId?: ChainId;
126
+ chains?: Chain[];
127
127
  };
128
128
  export type KeychainOptions = IFrameOptions & {
129
129
  policies?: SessionPolicies;
@@ -137,6 +137,8 @@ export type KeychainOptions = IFrameOptions & {
137
137
  feeSource?: FeeSource;
138
138
  /** Signup options (the order of the options is reflected in the UI. It's recommended to group socials and wallets together ) */
139
139
  signupOptions?: AuthOptions;
140
+ /** When true, manually provided policies will override preset policies. Default is false. */
141
+ shouldOverridePresetPolicies?: boolean;
140
142
  };
141
143
  export type ProfileOptions = IFrameOptions & {
142
144
  /** The URL of profile. Mainly for internal development purpose */
@@ -147,11 +149,10 @@ export type ProfileOptions = IFrameOptions & {
147
149
  namespace?: string;
148
150
  /** The tokens to be listed on Inventory modal */
149
151
  tokens?: Tokens;
150
- /** The policies to use for the profile */
151
- policies?: SessionPolicies;
152
152
  };
153
153
  export type ProfileContextTypeVariant = "inventory" | "trophies" | "achievements" | "leaderboard" | "activity";
154
+ export type Token = "eth" | "strk" | "lords" | "usdc" | "usdt";
154
155
  export type Tokens = {
155
- erc20?: string[];
156
+ erc20?: Token[];
156
157
  };
157
158
  export {};
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Call } from 'starknet';
2
- import { default as wasm } from '@cartridge/account-wasm/controller';
2
+ import { Policy } from '@cartridge/controller-wasm/controller';
3
3
  import { Policies, SessionPolicies } from '@cartridge/presets';
4
4
  import { ChainId } from '@starknet-io/types-js';
5
5
  import { ParsedSessionPolicies } from './policies';
@@ -9,7 +9,7 @@ export declare function normalizeCalls(calls: Call | Call[]): {
9
9
  calldata: import('starknet').HexCalldata;
10
10
  }[];
11
11
  export declare function toSessionPolicies(policies: Policies): SessionPolicies;
12
- export declare function toWasmPolicies(policies: ParsedSessionPolicies): wasm.Policy[];
12
+ export declare function toWasmPolicies(policies: ParsedSessionPolicies): Policy[];
13
13
  export declare function toArray<T>(val: T | T[]): T[];
14
14
  export declare function humanizeString(str: string): string;
15
- export declare function parseChainId(url: URL): Promise<ChainId>;
15
+ export declare function parseChainId(url: URL): ChainId;
@@ -3,6 +3,4 @@ export * from './bridge';
3
3
  export * from './metamask';
4
4
  export * from './phantom';
5
5
  export * from './rabby';
6
- export * from './turnkey';
7
6
  export * from './types';
8
- export * from './wallet-connect';
package/jest.config.ts CHANGED
@@ -8,6 +8,8 @@ const config: Config = {
8
8
  transform: {
9
9
  '^.+\\.tsx?$': 'ts-jest',
10
10
  },
11
+ setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
12
+ testTimeout: 10000,
11
13
  };
12
14
 
13
15
  export default config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartridge/controller",
3
- "version": "0.7.14-alpha.2",
3
+ "version": "0.8.0",
4
4
  "description": "Cartridge Controller",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -28,6 +28,7 @@
28
28
  "starknetkit": "^2.6.1"
29
29
  },
30
30
  "dependencies": {
31
+ "@cartridge/controller-wasm": "0.1.7",
31
32
  "@cartridge/penpal": "^6.2.4",
32
33
  "ethers": "^6.13.5",
33
34
  "@starknet-io/types-js": "^0.7.7",
@@ -35,12 +36,11 @@
35
36
  "@turnkey/sdk-browser": "^4.0.0",
36
37
  "cbor-x": "^1.5.0",
37
38
  "mipd": "^0.0.7",
38
- "@walletconnect/ethereum-provider": "^2.20.0",
39
- "@cartridge/account-wasm": "0.7.14-alpha.2",
40
- "@cartridge/utils": "0.7.14-alpha.2"
39
+ "@walletconnect/ethereum-provider": "^2.20.0"
41
40
  },
42
41
  "devDependencies": {
43
42
  "@types/jest": "^29.5.14",
43
+ "@types/mocha": "^10.0.10",
44
44
  "@types/node": "^18.0.6",
45
45
  "jest": "^29.7.0",
46
46
  "prettier": "^3.4.2",
@@ -53,7 +53,7 @@
53
53
  "vite-plugin-node-polyfills": "^0.22.0",
54
54
  "vite-plugin-top-level-await": "^1.4.4",
55
55
  "vite-plugin-wasm": "^3.4.1",
56
- "@cartridge/tsconfig": "0.7.14-alpha.2"
56
+ "@cartridge/tsconfig": "0.8.0"
57
57
  },
58
58
  "scripts": {
59
59
  "build:deps": "pnpm build",
@@ -0,0 +1,80 @@
1
+ import { constants } from "starknet";
2
+ import ControllerProvider from "../controller";
3
+
4
+ describe("ControllerProvider defaults", () => {
5
+ let originalConsoleError: any;
6
+ let originalConsoleWarn: any;
7
+
8
+ beforeEach(() => {
9
+ // Mock console methods to suppress expected errors/warnings
10
+ originalConsoleError = console.error;
11
+ originalConsoleWarn = console.warn;
12
+ console.error = jest.fn();
13
+ console.warn = jest.fn();
14
+ });
15
+
16
+ afterEach(() => {
17
+ console.error = originalConsoleError;
18
+ console.warn = originalConsoleWarn;
19
+ });
20
+
21
+ test("should use default chains and chainId when not provided", () => {
22
+ const controller = new ControllerProvider({});
23
+
24
+ expect(controller.rpcUrl()).toBe(
25
+ "https://api.cartridge.gg/x/starknet/mainnet",
26
+ );
27
+ });
28
+
29
+ test("should use custom chains when provided", () => {
30
+ const customChains = [
31
+ { rpcUrl: "https://api.cartridge.gg/x/starknet/sepolia" },
32
+ ];
33
+
34
+ const controller = new ControllerProvider({
35
+ chains: customChains,
36
+ defaultChainId: constants.StarknetChainId.SN_SEPOLIA,
37
+ });
38
+
39
+ expect(controller.rpcUrl()).toBe(
40
+ "https://api.cartridge.gg/x/starknet/sepolia",
41
+ );
42
+ });
43
+
44
+ test("should throw error when using non-Cartridge RPC for mainnet", async () => {
45
+ const invalidChains = [
46
+ { rpcUrl: "https://some-other-provider.com/starknet/mainnet" },
47
+ ];
48
+
49
+ expect(() => {
50
+ new ControllerProvider({
51
+ chains: invalidChains,
52
+ defaultChainId: constants.StarknetChainId.SN_MAIN,
53
+ });
54
+ }).toThrow("Only Cartridge RPC providers are allowed for mainnet");
55
+ });
56
+
57
+ test("should throw error when using non-Cartridge RPC for sepolia", async () => {
58
+ const invalidChains = [
59
+ { rpcUrl: "https://some-other-provider.com/starknet/sepolia" },
60
+ ];
61
+
62
+ expect(() => {
63
+ new ControllerProvider({
64
+ chains: invalidChains,
65
+ defaultChainId: constants.StarknetChainId.SN_SEPOLIA,
66
+ });
67
+ }).toThrow("Only Cartridge RPC providers are allowed for sepolia");
68
+ });
69
+
70
+ test("should allow non-Cartridge RPC for custom chains", () => {
71
+ const customChains = [{ rpcUrl: "http://localhost:5050" }];
72
+
73
+ // This should not throw
74
+ expect(() => {
75
+ new ControllerProvider({
76
+ chains: customChains,
77
+ });
78
+ }).not.toThrow();
79
+ });
80
+ });
@@ -23,12 +23,6 @@ describe("parseChainId", () => {
23
23
  ).toBe(shortString.encodeShortString("WP_SLOT"));
24
24
  });
25
25
 
26
- test("identifies slot chain on localhost", () => {
27
- expect(parseChainId(new URL("http://localhost:8001/x/slot/katana"))).toBe(
28
- shortString.encodeShortString("WP_SLOT"),
29
- );
30
- });
31
-
32
26
  test("identifies slot chain with hyphenated name", () => {
33
27
  expect(
34
28
  parseChainId(
@@ -0,0 +1,29 @@
1
+ // Jest setup file to handle cleanup of timers and open handles
2
+
3
+ // Mock starknetkit to prevent it from creating timers
4
+ jest.mock("starknetkit", () => ({
5
+ connect: jest.fn(),
6
+ StarknetWindowObject: {},
7
+ }));
8
+
9
+ jest.mock("starknetkit/injected", () => ({
10
+ InjectedConnector: jest.fn(),
11
+ }));
12
+
13
+ // Clean up any remaining timers after each test
14
+ afterEach(() => {
15
+ jest.clearAllTimers();
16
+ jest.useRealTimers();
17
+ });
18
+
19
+ // Force cleanup after all tests
20
+ afterAll(() => {
21
+ // Clear any remaining timers
22
+ jest.clearAllTimers();
23
+ jest.useRealTimers();
24
+
25
+ // Force garbage collection if available
26
+ if (global.gc) {
27
+ global.gc();
28
+ }
29
+ });
package/src/controller.ts CHANGED
@@ -1,28 +1,29 @@
1
1
  import { AsyncMethodReturns } from "@cartridge/penpal";
2
2
 
3
+ import { Policy } from "@cartridge/presets";
4
+ import {
5
+ AddInvokeTransactionResult,
6
+ AddStarknetChainParameters,
7
+ ChainId,
8
+ } from "@starknet-io/types-js";
9
+ import { constants, shortString, WalletAccount } from "starknet";
10
+ import { version } from "../package.json";
3
11
  import ControllerAccount from "./account";
4
- import { KeychainIFrame, ProfileIFrame } from "./iframe";
5
12
  import { NotReadyToConnect } from "./errors";
13
+ import { KeychainIFrame, ProfileIFrame } from "./iframe";
14
+ import BaseProvider from "./provider";
6
15
  import {
7
- Keychain,
8
- ResponseCodes,
16
+ Chain,
17
+ ConnectError,
9
18
  ConnectReply,
10
- ProbeReply,
11
19
  ControllerOptions,
12
- ConnectError,
13
- Profile,
14
20
  IFrames,
21
+ Keychain,
22
+ ProbeReply,
23
+ Profile,
15
24
  ProfileContextTypeVariant,
16
- Chain,
25
+ ResponseCodes,
17
26
  } from "./types";
18
- import BaseProvider from "./provider";
19
- import { shortString, WalletAccount } from "starknet";
20
- import { Policy } from "@cartridge/presets";
21
- import {
22
- AddInvokeTransactionResult,
23
- AddStarknetChainParameters,
24
- ChainId,
25
- } from "@starknet-io/types-js";
26
27
  import { parseChainId } from "./utils";
27
28
 
28
29
  export default class ControllerProvider extends BaseProvider {
@@ -33,10 +34,26 @@ export default class ControllerProvider extends BaseProvider {
33
34
  private selectedChain: ChainId;
34
35
  private chains: Map<ChainId, Chain>;
35
36
 
37
+ isReady(): boolean {
38
+ return !!this.keychain;
39
+ }
40
+
36
41
  constructor(options: ControllerOptions) {
37
42
  super();
38
43
 
39
- this.selectedChain = options.defaultChainId;
44
+ // Default Cartridge chains that are always available
45
+ const cartridgeChains: Chain[] = [
46
+ { rpcUrl: "https://api.cartridge.gg/x/starknet/sepolia" },
47
+ { rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet" },
48
+ ];
49
+
50
+ // Merge user chains with default chains
51
+ // User chains take precedence if they specify the same network
52
+ const chains = [...cartridgeChains, ...(options.chains || [])];
53
+ const defaultChainId =
54
+ options.defaultChainId || constants.StarknetChainId.SN_MAIN;
55
+
56
+ this.selectedChain = defaultChainId;
40
57
  this.chains = new Map<ChainId, Chain>();
41
58
 
42
59
  this.iframes = {
@@ -49,15 +66,48 @@ export default class ControllerProvider extends BaseProvider {
49
66
  }),
50
67
  };
51
68
 
52
- this.options = options;
69
+ this.options = { ...options, chains, defaultChainId };
53
70
 
54
- this.validateChains(options.chains);
71
+ this.initializeChains(chains);
55
72
 
56
73
  if (typeof window !== "undefined") {
57
74
  (window as any).starknet_controller = this;
58
75
  }
59
76
  }
60
77
 
78
+ async logout() {
79
+ if (!this.keychain) {
80
+ console.error(new NotReadyToConnect().message);
81
+ return;
82
+ }
83
+
84
+ try {
85
+ // Disconnect the controller/keychain first
86
+ await this.disconnect();
87
+
88
+ // Close all controller iframes
89
+ const iframes = document.querySelectorAll('iframe[id^="controller-"]');
90
+ iframes.forEach((iframe) => {
91
+ const container = iframe.parentElement;
92
+ if (container) {
93
+ container.style.visibility = "hidden";
94
+ container.style.opacity = "0";
95
+ }
96
+ });
97
+
98
+ // Reset body overflow
99
+ if (document.body) {
100
+ document.body.style.overflow = "auto";
101
+ }
102
+
103
+ // Reload the page to complete logout
104
+ window.location.reload();
105
+ } catch (err) {
106
+ console.error("Logout failed:", err);
107
+ throw err;
108
+ }
109
+ }
110
+
61
111
  async probe(): Promise<WalletAccount | undefined> {
62
112
  try {
63
113
  await this.waitForKeychain();
@@ -96,6 +146,7 @@ export default class ControllerProvider extends BaseProvider {
96
146
  openSettings: () => this.openSettings.bind(this),
97
147
  openPurchaseCredits: () => this.openPurchaseCredits.bind(this),
98
148
  openExecute: () => this.openExecute.bind(this),
149
+ logout: () => this.logout.bind(this),
99
150
  },
100
151
  rpcUrl: this.rpcUrl(),
101
152
  username,
@@ -127,9 +178,18 @@ export default class ControllerProvider extends BaseProvider {
127
178
 
128
179
  try {
129
180
  let response = await this.keychain.connect(
130
- this.options.policies || {},
181
+ // Policy precedence logic:
182
+ // 1. If shouldOverridePresetPolicies is true and policies are provided, use policies
183
+ // 2. Otherwise, if preset is defined, use empty object (let preset take precedence)
184
+ // 3. Otherwise, use provided policies or empty object
185
+ this.options.shouldOverridePresetPolicies && this.options.policies
186
+ ? this.options.policies
187
+ : this.options.preset
188
+ ? {}
189
+ : this.options.policies || {},
131
190
  this.rpcUrl(),
132
191
  this.options.signupOptions,
192
+ version,
133
193
  );
134
194
  if (response.code !== ResponseCodes.SUCCESS) {
135
195
  throw new Error(response.message);
@@ -368,14 +428,30 @@ export default class ControllerProvider extends BaseProvider {
368
428
  return await this.keychain.delegateAccount();
369
429
  }
370
430
 
371
- private async validateChains(chains: Chain[]) {
431
+ private initializeChains(chains: Chain[]) {
372
432
  for (const chain of chains) {
373
433
  try {
374
434
  const url = new URL(chain.rpcUrl);
375
- const chainId = await parseChainId(url);
435
+ const chainId = parseChainId(url);
436
+
437
+ // Validate that mainnet and sepolia must use Cartridge RPC
438
+ const isMainnet = chainId === constants.StarknetChainId.SN_MAIN;
439
+ const isSepolia = chainId === constants.StarknetChainId.SN_SEPOLIA;
440
+ const isCartridgeRpc = url.hostname === "api.cartridge.gg";
441
+ const isLocalhost =
442
+ url.hostname === "localhost" || url.hostname === "127.0.0.1";
443
+
444
+ if ((isMainnet || isSepolia) && !(isCartridgeRpc || isLocalhost)) {
445
+ throw new Error(
446
+ `Only Cartridge RPC providers are allowed for ${isMainnet ? "mainnet" : "sepolia"}. ` +
447
+ `Please use: https://api.cartridge.gg/x/starknet/${isMainnet ? "mainnet" : "sepolia"}`,
448
+ );
449
+ }
450
+
376
451
  this.chains.set(chainId, chain);
377
452
  } catch (error) {
378
453
  console.error(`Failed to parse chainId for ${chain.rpcUrl}:`, error);
454
+ throw error; // Re-throw to ensure invalid chains fail fast
379
455
  }
380
456
  }
381
457
 
@@ -14,6 +14,7 @@ export class IFrame<CallSender extends {}> implements Modal {
14
14
  private iframe?: HTMLIFrameElement;
15
15
  private container?: HTMLDivElement;
16
16
  private onClose?: () => void;
17
+ private child?: AsyncMethodReturns<CallSender>;
17
18
 
18
19
  constructor({
19
20
  id,
@@ -72,6 +73,20 @@ export class IFrame<CallSender extends {}> implements Modal {
72
73
  container.style.pointerEvents = "auto";
73
74
  container.appendChild(iframe);
74
75
 
76
+ // Add click event listener to close iframe when clicking outside
77
+ container.addEventListener("click", (e) => {
78
+ if (e.target === container) {
79
+ // Attempting to reset(clear context) for keychain iframe (identified by ID)
80
+ if (id === "controller-keychain" && this.child) {
81
+ // Type assertion for keychain child only
82
+ (this.child as any)
83
+ .reset?.()
84
+ .catch((e: any) => console.error("Error resetting context:", e));
85
+ }
86
+ this.close();
87
+ }
88
+ });
89
+
75
90
  this.iframe = iframe;
76
91
  this.container = container;
77
92
 
@@ -98,7 +113,10 @@ export class IFrame<CallSender extends {}> implements Modal {
98
113
  reload: (_origin: string) => () => window.location.reload(),
99
114
  ...methods,
100
115
  },
101
- }).promise.then(onConnect);
116
+ }).promise.then((child) => {
117
+ this.child = child;
118
+ onConnect(child);
119
+ });
102
120
 
103
121
  this.resize();
104
122
  window.addEventListener("resize", () => this.resize());
@@ -20,7 +20,6 @@ export class ProfileIFrame extends IFrame<Profile> {
20
20
  slot,
21
21
  namespace,
22
22
  tokens,
23
- policies,
24
23
  ...iframeOptions
25
24
  }: ProfileIFrameOptions) {
26
25
  const _profileUrl = (profileUrl || PROFILE_URL).replace(/\/$/, "");
@@ -51,16 +50,6 @@ export class ProfileIFrame extends IFrame<Profile> {
51
50
  );
52
51
  }
53
52
 
54
- if (policies?.contracts) {
55
- const methods = Object.values(policies.contracts).flatMap(
56
- (contract) => contract.methods,
57
- );
58
- _url.searchParams.set(
59
- "methods",
60
- encodeURIComponent(JSON.stringify(methods)),
61
- );
62
- }
63
-
64
53
  super({
65
54
  ...iframeOptions,
66
55
  id: "controller-profile",
@@ -1,5 +1,5 @@
1
- import { Policy } from "@cartridge/account-wasm";
2
- import { CartridgeSessionAccount } from "@cartridge/account-wasm/session";
1
+ import { Policy } from "@cartridge/controller-wasm";
2
+ import { CartridgeSessionAccount } from "@cartridge/controller-wasm/session";
3
3
  import { Call, InvokeFunctionResponse, WalletAccount } from "starknet";
4
4
 
5
5
  import { normalizeCalls } from "../utils";
@@ -1,5 +1,5 @@
1
- import { Policy } from "@cartridge/account-wasm";
2
- import { CartridgeSessionAccount } from "@cartridge/account-wasm/session";
1
+ import { Policy } from "@cartridge/controller-wasm";
2
+ import { CartridgeSessionAccount } from "@cartridge/controller-wasm/session";
3
3
  import { Call, InvokeFunctionResponse, WalletAccount } from "starknet";
4
4
 
5
5
  import { normalizeCalls } from "../utils";
package/src/types.ts CHANGED
@@ -118,6 +118,7 @@ export interface Keychain {
118
118
  policies: SessionPolicies,
119
119
  rpcUrl: string,
120
120
  signupOptions?: AuthOptions,
121
+ version?: string,
121
122
  ): Promise<ConnectReply | ConnectError>;
122
123
  disconnect(): void;
123
124
 
@@ -200,8 +201,8 @@ export type Chain = {
200
201
  };
201
202
 
202
203
  export type ProviderOptions = {
203
- defaultChainId: ChainId;
204
- chains: Chain[];
204
+ defaultChainId?: ChainId;
205
+ chains?: Chain[];
205
206
  };
206
207
 
207
208
  export type KeychainOptions = IFrameOptions & {
@@ -216,6 +217,8 @@ export type KeychainOptions = IFrameOptions & {
216
217
  feeSource?: FeeSource;
217
218
  /** Signup options (the order of the options is reflected in the UI. It's recommended to group socials and wallets together ) */
218
219
  signupOptions?: AuthOptions;
220
+ /** When true, manually provided policies will override preset policies. Default is false. */
221
+ shouldOverridePresetPolicies?: boolean;
219
222
  };
220
223
 
221
224
  export type ProfileOptions = IFrameOptions & {
@@ -227,8 +230,6 @@ export type ProfileOptions = IFrameOptions & {
227
230
  namespace?: string;
228
231
  /** The tokens to be listed on Inventory modal */
229
232
  tokens?: Tokens;
230
- /** The policies to use for the profile */
231
- policies?: SessionPolicies;
232
233
  };
233
234
 
234
235
  export type ProfileContextTypeVariant =
@@ -238,6 +239,8 @@ export type ProfileContextTypeVariant =
238
239
  | "leaderboard"
239
240
  | "activity";
240
241
 
242
+ export type Token = "eth" | "strk" | "lords" | "usdc" | "usdt";
243
+
241
244
  export type Tokens = {
242
- erc20?: string[];
245
+ erc20?: Token[];
243
246
  };
package/src/utils.ts CHANGED
@@ -5,12 +5,11 @@ import {
5
5
  constants,
6
6
  getChecksumAddress,
7
7
  hash,
8
- Provider,
9
8
  shortString,
10
9
  typedData,
11
10
  TypedDataRevision,
12
11
  } from "starknet";
13
- import wasm from "@cartridge/account-wasm/controller";
12
+ import { Policy } from "@cartridge/controller-wasm/controller";
14
13
  import { Policies, SessionPolicies } from "@cartridge/presets";
15
14
  import { ChainId } from "@starknet-io/types-js";
16
15
  import { ParsedSessionPolicies } from "./policies";
@@ -28,8 +27,6 @@ const ALLOWED_PROPERTIES = new Set([
28
27
  "primaryType",
29
28
  ]);
30
29
 
31
- const LOCAL_HOSTNAMES = ["localhost", "127.0.0.1", "0.0.0.0"];
32
-
33
30
  function validatePropertyName(prop: string): void {
34
31
  if (!ALLOWED_PROPERTIES.has(prop)) {
35
32
  throw new Error(`Invalid property name: ${prop}`);
@@ -92,7 +89,7 @@ export function toSessionPolicies(policies: Policies): SessionPolicies {
92
89
  : policies;
93
90
  }
94
91
 
95
- export function toWasmPolicies(policies: ParsedSessionPolicies): wasm.Policy[] {
92
+ export function toWasmPolicies(policies: ParsedSessionPolicies): Policy[] {
96
93
  return [
97
94
  ...Object.entries(policies.contracts ?? {}).flatMap(
98
95
  ([target, { methods }]) =>
@@ -139,9 +136,55 @@ export function humanizeString(str: string): string {
139
136
  );
140
137
  }
141
138
 
142
- export async function parseChainId(url: URL): Promise<ChainId> {
139
+ export function parseChainId(url: URL): ChainId {
143
140
  const parts = url.pathname.split("/");
144
141
 
142
+ // Handle localhost URLs by making a synchronous call to getChainId
143
+ if (
144
+ url.hostname === "localhost" ||
145
+ url.hostname === "127.0.0.1" ||
146
+ url.hostname === "0.0.0.0"
147
+ ) {
148
+ // Check if we're in a browser environment
149
+ if (typeof XMLHttpRequest === "undefined") {
150
+ // In Node.js environment (like tests), we can't make synchronous HTTP calls
151
+ // For now, we'll use a placeholder chainId for localhost in tests
152
+ console.warn(
153
+ `Cannot make synchronous HTTP call in Node.js environment for ${url.toString()}`,
154
+ );
155
+ return shortString.encodeShortString("LOCALHOST") as ChainId;
156
+ }
157
+
158
+ // Use a synchronous XMLHttpRequest to get the chain ID
159
+ const xhr = new XMLHttpRequest();
160
+ xhr.open("POST", url.toString(), false); // false makes it synchronous
161
+ xhr.setRequestHeader("Content-Type", "application/json");
162
+
163
+ const requestBody = JSON.stringify({
164
+ jsonrpc: "2.0",
165
+ method: "starknet_chainId",
166
+ params: [],
167
+ id: 1,
168
+ });
169
+
170
+ try {
171
+ xhr.send(requestBody);
172
+
173
+ if (xhr.status === 200) {
174
+ const response = JSON.parse(xhr.responseText);
175
+ if (response.result) {
176
+ return response.result as ChainId;
177
+ }
178
+ }
179
+
180
+ throw new Error(
181
+ `Failed to get chain ID from ${url.toString()}: ${xhr.status} ${xhr.statusText}`,
182
+ );
183
+ } catch (error) {
184
+ throw new Error(`Failed to connect to ${url.toString()}: ${error}`);
185
+ }
186
+ }
187
+
145
188
  if (parts.includes("starknet")) {
146
189
  if (parts.includes("mainnet")) {
147
190
  return constants.StarknetChainId.SN_MAIN;
@@ -161,12 +204,5 @@ export async function parseChainId(url: URL): Promise<ChainId> {
161
204
  }
162
205
  }
163
206
 
164
- if (LOCAL_HOSTNAMES.includes(url.hostname)) {
165
- const provider = new Provider({
166
- nodeUrl: url.toString(),
167
- });
168
- return await provider.getChainId();
169
- }
170
-
171
207
  throw new Error(`Chain ${url.toString()} not supported`);
172
208
  }