@cartridge/controller 0.5.7 → 0.5.9

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 (49) hide show
  1. package/.turbo/turbo-build$colon$deps.log +72 -67
  2. package/.turbo/turbo-build.log +73 -68
  3. package/dist/__tests__/parseChainId.test.d.ts +2 -0
  4. package/dist/__tests__/parseChainId.test.js +89 -0
  5. package/dist/__tests__/parseChainId.test.js.map +1 -0
  6. package/dist/account.d.ts +2 -2
  7. package/dist/account.js +4 -2
  8. package/dist/account.js.map +1 -1
  9. package/dist/controller.d.ts +8 -3
  10. package/dist/controller.js +111 -36
  11. package/dist/controller.js.map +1 -1
  12. package/dist/iframe/base.d.ts +1 -1
  13. package/dist/iframe/index.d.ts +1 -1
  14. package/dist/iframe/keychain.d.ts +1 -1
  15. package/dist/iframe/profile.d.ts +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.js +128 -37
  18. package/dist/index.js.map +1 -1
  19. package/dist/provider.d.ts +5 -6
  20. package/dist/provider.js +28 -19
  21. package/dist/provider.js.map +1 -1
  22. package/dist/session/account.d.ts +1 -1
  23. package/dist/session/account.js +2 -0
  24. package/dist/session/account.js.map +1 -1
  25. package/dist/session/index.d.ts +1 -1
  26. package/dist/session/index.js +40 -22
  27. package/dist/session/index.js.map +1 -1
  28. package/dist/session/provider.d.ts +4 -3
  29. package/dist/session/provider.js +40 -22
  30. package/dist/session/provider.js.map +1 -1
  31. package/dist/telegram/provider.d.ts +4 -3
  32. package/dist/telegram/provider.js +40 -24
  33. package/dist/telegram/provider.js.map +1 -1
  34. package/dist/{types-BReKRAuh.d.ts → types-CVnDQVqD.d.ts} +10 -11
  35. package/dist/types.d.ts +1 -1
  36. package/dist/types.js.map +1 -1
  37. package/dist/utils.d.ts +3 -2
  38. package/dist/utils.js +25 -0
  39. package/dist/utils.js.map +1 -1
  40. package/jest.config.ts +13 -0
  41. package/package.json +7 -3
  42. package/src/__tests__/parseChainId.test.ts +60 -0
  43. package/src/account.ts +2 -1
  44. package/src/controller.ts +85 -15
  45. package/src/provider.ts +37 -23
  46. package/src/session/provider.ts +14 -4
  47. package/src/telegram/provider.ts +14 -5
  48. package/src/types.ts +11 -12
  49. package/src/utils.ts +28 -0
@@ -0,0 +1,60 @@
1
+ import { constants, shortString } from "starknet";
2
+ import { parseChainId } from "../utils";
3
+
4
+ describe("parseChainId", () => {
5
+ describe("Starknet chains", () => {
6
+ test("identifies mainnet", () => {
7
+ expect(
8
+ parseChainId(new URL("https://api.cartridge.gg/x/starknet/mainnet")),
9
+ ).toBe(constants.StarknetChainId.SN_MAIN);
10
+ });
11
+
12
+ test("identifies sepolia", () => {
13
+ expect(
14
+ parseChainId(new URL("https://api.cartridge.gg/x/starknet/sepolia")),
15
+ ).toBe(constants.StarknetChainId.SN_SEPOLIA);
16
+ });
17
+ });
18
+
19
+ describe("Project-specific chains", () => {
20
+ test("identifies slot chain", () => {
21
+ expect(
22
+ parseChainId(new URL("https://api.cartridge.gg/x/slot/katana")),
23
+ ).toBe(shortString.encodeShortString("WP_SLOT"));
24
+ });
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
+ test("identifies slot chain with hyphenated name", () => {
33
+ expect(
34
+ parseChainId(
35
+ new URL("https://api.cartridge.gg/x/my-slot-chain/katana"),
36
+ ),
37
+ ).toBe(shortString.encodeShortString("WP_MY_SLOT_CHAIN"));
38
+ });
39
+
40
+ test("identifies slot mainnet chain", () => {
41
+ expect(
42
+ parseChainId(new URL("https://api.cartridge.gg/x/slot/mainnet")),
43
+ ).toBe(shortString.encodeShortString("GG_SLOT"));
44
+ });
45
+ });
46
+
47
+ describe("Error cases", () => {
48
+ test("throws error for unsupported URL format", () => {
49
+ expect(() =>
50
+ parseChainId(new URL("https://api.example.com/unsupported")),
51
+ ).toThrow("Chain https://api.example.com/unsupported not supported");
52
+ });
53
+
54
+ test("throws error for URLs without proper chain identifiers", () => {
55
+ expect(() =>
56
+ parseChainId(new URL("https://api.example.com/v1/starknet")),
57
+ ).toThrow("Chain https://api.example.com/v1/starknet not supported");
58
+ });
59
+ });
60
+ });
package/src/account.ts CHANGED
@@ -27,12 +27,13 @@ class ControllerAccount extends WalletAccount {
27
27
 
28
28
  constructor(
29
29
  provider: BaseProvider,
30
+ rpcUrl: string,
30
31
  address: string,
31
32
  keychain: AsyncMethodReturns<Keychain>,
32
33
  options: KeychainOptions,
33
34
  modal: Modal,
34
35
  ) {
35
- super({ nodeUrl: provider.rpc.toString() }, provider);
36
+ super({ nodeUrl: rpcUrl }, provider);
36
37
 
37
38
  this.address = address;
38
39
  this.keychain = keychain;
package/src/controller.ts CHANGED
@@ -13,20 +13,52 @@ import {
13
13
  Profile,
14
14
  IFrames,
15
15
  ProfileContextTypeVariant,
16
+ Chain,
16
17
  } from "./types";
17
18
  import BaseProvider from "./provider";
18
19
  import { WalletAccount } from "starknet";
19
20
  import { Policy } from "@cartridge/presets";
21
+ import { AddStarknetChainParameters, ChainId } from "@starknet-io/types-js";
22
+ import { parseChainId } from "./utils";
20
23
 
21
24
  export default class ControllerProvider extends BaseProvider {
22
25
  private keychain?: AsyncMethodReturns<Keychain>;
23
26
  private profile?: AsyncMethodReturns<Profile>;
24
27
  private options: ControllerOptions;
25
28
  private iframes: IFrames;
29
+ private selectedChain: ChainId;
30
+ private chains: Map<ChainId, Chain>;
26
31
 
27
32
  constructor(options: ControllerOptions) {
28
- const { rpc } = options;
29
- super({ rpc });
33
+ super();
34
+
35
+ const chains = new Map<ChainId, Chain>();
36
+
37
+ for (const chain of options.chains) {
38
+ const url = new URL(chain.rpcUrl);
39
+ const chainId = parseChainId(url);
40
+ chains.set(chainId, chain);
41
+ }
42
+
43
+ if (
44
+ options.policies?.messages?.length &&
45
+ options.policies.messages.length !== chains.size
46
+ ) {
47
+ console.warn(
48
+ "Each message policy is associated with a specific chain. " +
49
+ "The number of message policies does not match the number of chains specified - " +
50
+ "session message signing may not work on some chains.",
51
+ );
52
+ }
53
+
54
+ this.chains = chains;
55
+ this.selectedChain = options.defaultChainId;
56
+
57
+ if (!this.chains.has(this.selectedChain)) {
58
+ throw new Error(
59
+ `Chain ${this.selectedChain} not found in configured chains`,
60
+ );
61
+ }
30
62
 
31
63
  this.iframes = {
32
64
  keychain: new KeychainIFrame({
@@ -54,12 +86,11 @@ export default class ControllerProvider extends BaseProvider {
54
86
  return;
55
87
  }
56
88
 
57
- const response = (await this.keychain.probe(
58
- this.rpc.toString(),
59
- )) as ProbeReply;
89
+ const response = (await this.keychain.probe(this.rpcUrl())) as ProbeReply;
60
90
 
61
91
  this.account = new ControllerAccount(
62
92
  this,
93
+ this.rpcUrl(),
63
94
  response.address,
64
95
  this.keychain,
65
96
  this.options,
@@ -83,7 +114,7 @@ export default class ControllerProvider extends BaseProvider {
83
114
  openPurchaseCredits: () => this.openPurchaseCredits.bind(this),
84
115
  openExecute: () => this.openExecute.bind(this),
85
116
  },
86
- rpcUrl: this.rpc.toString(),
117
+ rpcUrl: this.rpcUrl(),
87
118
  username,
88
119
  version: this.version,
89
120
  });
@@ -113,8 +144,8 @@ export default class ControllerProvider extends BaseProvider {
113
144
 
114
145
  try {
115
146
  let response = await this.keychain.connect(
116
- this.options.policies || [],
117
- this.rpc.toString(),
147
+ this.options.policies || {},
148
+ this.rpcUrl(),
118
149
  );
119
150
  if (response.code !== ResponseCodes.SUCCESS) {
120
151
  throw new Error(response.message);
@@ -123,6 +154,7 @@ export default class ControllerProvider extends BaseProvider {
123
154
  response = response as ConnectReply;
124
155
  this.account = new ControllerAccount(
125
156
  this,
157
+ this.rpcUrl(),
126
158
  response.address,
127
159
  this.keychain,
128
160
  this.options,
@@ -137,6 +169,26 @@ export default class ControllerProvider extends BaseProvider {
137
169
  }
138
170
  }
139
171
 
172
+ async switchStarknetChain(chainId: string): Promise<boolean> {
173
+ try {
174
+ this.selectedChain = chainId;
175
+ this.account = await this.probe();
176
+ if (!this.account) {
177
+ this.account = await this.connect();
178
+ }
179
+ } catch (e) {
180
+ console.error(e);
181
+ return false;
182
+ }
183
+
184
+ this.emitNetworkChanged(chainId);
185
+ return true;
186
+ }
187
+
188
+ addStarknetChain(_chain: AddStarknetChainParameters): Promise<boolean> {
189
+ return Promise.resolve(true);
190
+ }
191
+
140
192
  async disconnect() {
141
193
  if (!this.keychain) {
142
194
  console.error(new NotReadyToConnect().message);
@@ -197,6 +249,10 @@ export default class ControllerProvider extends BaseProvider {
197
249
  return this.keychain.revoke(origin);
198
250
  }
199
251
 
252
+ rpcUrl(): string {
253
+ return this.chains.get(this.selectedChain)!.rpcUrl;
254
+ }
255
+
200
256
  username() {
201
257
  if (!this.keychain) {
202
258
  console.error(new NotReadyToConnect().message);
@@ -230,7 +286,7 @@ export default class ControllerProvider extends BaseProvider {
230
286
  this.keychain.openPurchaseCredits();
231
287
  }
232
288
 
233
- async openExecute(calls: any) {
289
+ async openExecute(calls: any, chainId?: string) {
234
290
  if (!this.keychain || !this.iframes.keychain) {
235
291
  console.error(new NotReadyToConnect().message);
236
292
  return;
@@ -239,16 +295,30 @@ export default class ControllerProvider extends BaseProvider {
239
295
  console.error("Profile is not ready");
240
296
  return;
241
297
  }
242
- if (this.iframes.profile?.sendBackward) {
243
- this.iframes.profile?.sendBackward();
244
- } else {
245
- this.iframes.profile?.close();
298
+ // Switch to the chain if provided
299
+ let currentChainId = this.selectedChain;
300
+ if (chainId) {
301
+ this.switchStarknetChain(chainId);
246
302
  }
303
+ // Switch iframes
304
+ this.iframes.profile?.sendBackward();
247
305
  this.iframes.keychain.open();
306
+ this.iframes.profile?.close();
307
+ // Invoke execute
248
308
  const res = await this.keychain.execute(calls, undefined, undefined, true);
309
+ // Switch back iframes
310
+ this.iframes.profile?.open();
249
311
  this.iframes.keychain.close();
250
- this.iframes.profile?.sendForward?.();
251
- return !(res && (res as ConnectError).code === ResponseCodes.NOT_CONNECTED);
312
+ this.iframes.profile?.sendForward();
313
+ // Switch back to the original chain
314
+ if (chainId) {
315
+ this.switchStarknetChain(currentChainId);
316
+ }
317
+ return !(
318
+ res &&
319
+ ((res as ConnectError).code === ResponseCodes.NOT_CONNECTED ||
320
+ (res as ConnectError).code === ResponseCodes.CANCELED)
321
+ );
252
322
  }
253
323
 
254
324
  async delegateAccount() {
package/src/provider.ts CHANGED
@@ -1,10 +1,13 @@
1
1
  import { WalletAccount } from "starknet";
2
2
  import {
3
3
  AddInvokeTransactionParameters,
4
+ AddStarknetChainParameters,
4
5
  Errors,
5
6
  Permission,
7
+ RequestAccountsParameters,
6
8
  RequestFn,
7
9
  StarknetWindowObject,
10
+ SwitchStarknetChainParameters,
8
11
  TypedData,
9
12
  WalletEventHandlers,
10
13
  WalletEventListener,
@@ -13,7 +16,6 @@ import {
13
16
  import manifest from "../package.json";
14
17
 
15
18
  import { icon } from "./icon";
16
- import { ProviderOptions } from "./types";
17
19
 
18
20
  export default abstract class BaseProvider implements StarknetWindowObject {
19
21
  public id = "controller";
@@ -21,16 +23,9 @@ export default abstract class BaseProvider implements StarknetWindowObject {
21
23
  public version = manifest.version;
22
24
  public icon = icon;
23
25
 
24
- public rpc: URL;
25
26
  public account?: WalletAccount;
26
27
  public subscriptions: WalletEvents[] = [];
27
28
 
28
- constructor(options: ProviderOptions) {
29
- const { rpc } = options;
30
-
31
- this.rpc = new URL(rpc);
32
- }
33
-
34
29
  request: RequestFn = async (call) => {
35
30
  switch (call.type) {
36
31
  case "wallet_getPermissions":
@@ -47,8 +42,11 @@ export default abstract class BaseProvider implements StarknetWindowObject {
47
42
  return [this.account.address];
48
43
  }
49
44
 
45
+ const silentMode =
46
+ call.params && (call.params as RequestAccountsParameters).silent_mode;
47
+
50
48
  this.account = await this.probe();
51
- if (!this.account) {
49
+ if (!this.account && !silentMode) {
52
50
  this.account = await this.connect();
53
51
  }
54
52
 
@@ -66,26 +64,22 @@ export default abstract class BaseProvider implements StarknetWindowObject {
66
64
  data: "wallet_watchAsset not implemented",
67
65
  } as Errors.UNEXPECTED_ERROR;
68
66
 
69
- case "wallet_addStarknetChain":
70
- throw {
71
- code: 63,
72
- message: "An unexpected error occurred",
73
- data: "wallet_addStarknetChain not implemented",
74
- } as Errors.UNEXPECTED_ERROR;
67
+ case "wallet_addStarknetChain": {
68
+ let params = call.params as AddStarknetChainParameters;
69
+ return this.addStarknetChain(params);
70
+ }
75
71
 
76
- case "wallet_switchStarknetChain":
77
- throw {
78
- code: 63,
79
- message: "An unexpected error occurred",
80
- data: "wallet_switchStarknetChain not implemented",
81
- } as Errors.UNEXPECTED_ERROR;
72
+ case "wallet_switchStarknetChain": {
73
+ let params = call.params as SwitchStarknetChainParameters;
74
+ return this.switchStarknetChain(params.chainId);
75
+ }
82
76
 
83
77
  case "wallet_requestChainId":
84
78
  if (!this.account) {
85
79
  throw {
86
80
  code: 63,
87
81
  message: "An unexpected error occurred",
88
- data: "wallet_deploymentData not implemented",
82
+ data: "Account not initialized",
89
83
  } as Errors.UNEXPECTED_ERROR;
90
84
  }
91
85
 
@@ -103,7 +97,7 @@ export default abstract class BaseProvider implements StarknetWindowObject {
103
97
  throw {
104
98
  code: 63,
105
99
  message: "An unexpected error occurred",
106
- data: "wallet_deploymentData not implemented",
100
+ data: "Account not initialized",
107
101
  } as Errors.UNEXPECTED_ERROR;
108
102
  }
109
103
 
@@ -173,6 +167,26 @@ export default abstract class BaseProvider implements StarknetWindowObject {
173
167
  }
174
168
  };
175
169
 
170
+ protected emitNetworkChanged(chainId: string) {
171
+ this.subscriptions
172
+ .filter((sub) => sub.type === "networkChanged")
173
+ .forEach((sub) => {
174
+ (sub.handler as WalletEventHandlers["networkChanged"])(chainId);
175
+ });
176
+ }
177
+
178
+ protected emitAccountsChanged(accounts: string[]) {
179
+ this.subscriptions
180
+ .filter((sub) => sub.type === "accountsChanged")
181
+ .forEach((sub) => {
182
+ (sub.handler as WalletEventHandlers["accountsChanged"])(accounts);
183
+ });
184
+ }
185
+
176
186
  abstract probe(): Promise<WalletAccount | undefined>;
177
187
  abstract connect(): Promise<WalletAccount | undefined>;
188
+ abstract switchStarknetChain(chainId: string): Promise<boolean>;
189
+ abstract addStarknetChain(
190
+ chain: AddStarknetChainParameters,
191
+ ): Promise<boolean>;
178
192
  }
@@ -5,6 +5,7 @@ import { KEYCHAIN_URL } from "../constants";
5
5
  import BaseProvider from "../provider";
6
6
  import { toWasmPolicies } from "../utils";
7
7
  import { SessionPolicies } from "@cartridge/presets";
8
+ import { AddStarknetChainParameters } from "@starknet-io/types-js";
8
9
 
9
10
  interface SessionRegistration {
10
11
  username: string;
@@ -26,14 +27,15 @@ export default class SessionProvider extends BaseProvider {
26
27
  public name = "Controller Session";
27
28
 
28
29
  protected _chainId: string;
29
-
30
+ protected _rpcUrl: string;
30
31
  protected _username?: string;
31
32
  protected _redirectUrl: string;
32
33
  protected _policies: SessionPolicies;
33
34
 
34
35
  constructor({ rpc, chainId, policies, redirectUrl }: SessionOptions) {
35
- super({ rpc });
36
+ super();
36
37
 
38
+ this._rpcUrl = rpc;
37
39
  this._chainId = chainId;
38
40
  this._redirectUrl = redirectUrl;
39
41
  this._policies = policies;
@@ -76,7 +78,7 @@ export default class SessionProvider extends BaseProvider {
76
78
  this._redirectUrl
77
79
  }&redirect_query_name=startapp&policies=${JSON.stringify(
78
80
  this._policies,
79
- )}&rpc_url=${this.rpc}`;
81
+ )}&rpc_url=${this._rpcUrl}`;
80
82
 
81
83
  localStorage.setItem("lastUsedConnector", this.id);
82
84
  window.open(url, "_blank");
@@ -84,6 +86,14 @@ export default class SessionProvider extends BaseProvider {
84
86
  return;
85
87
  }
86
88
 
89
+ switchStarknetChain(_chainId: string): Promise<boolean> {
90
+ throw new Error("switchStarknetChain not implemented");
91
+ }
92
+
93
+ addStarknetChain(_chain: AddStarknetChainParameters): Promise<boolean> {
94
+ throw new Error("addStarknetChain not implemented");
95
+ }
96
+
87
97
  disconnect(): Promise<void> {
88
98
  localStorage.removeItem("sessionSigner");
89
99
  localStorage.removeItem("session");
@@ -127,7 +137,7 @@ export default class SessionProvider extends BaseProvider {
127
137
 
128
138
  this._username = sessionRegistration.username;
129
139
  this.account = new SessionAccount(this, {
130
- rpcUrl: this.rpc.toString(),
140
+ rpcUrl: this._rpcUrl,
131
141
  privateKey: signer.privKey,
132
142
  address: sessionRegistration.address,
133
143
  ownerGuid: sessionRegistration.ownerGuid,
@@ -11,6 +11,7 @@ import SessionAccount from "../session/account";
11
11
  import BaseProvider from "../provider";
12
12
  import { toWasmPolicies } from "../utils";
13
13
  import { SessionPolicies } from "@cartridge/presets";
14
+ import { AddStarknetChainParameters } from "@starknet-io/types-js";
14
15
 
15
16
  interface SessionRegistration {
16
17
  username: string;
@@ -25,6 +26,7 @@ export default class TelegramProvider extends BaseProvider {
25
26
  protected _chainId: string;
26
27
  protected _username?: string;
27
28
  protected _policies: SessionPolicies;
29
+ private _rpcUrl: string;
28
30
 
29
31
  constructor({
30
32
  rpc,
@@ -37,10 +39,9 @@ export default class TelegramProvider extends BaseProvider {
37
39
  policies: SessionPolicies;
38
40
  tmaUrl: string;
39
41
  }) {
40
- super({
41
- rpc,
42
- });
42
+ super();
43
43
 
44
+ this._rpcUrl = rpc;
44
45
  this._tmaUrl = tmaUrl;
45
46
  this._chainId = chainId;
46
47
  this._policies = policies;
@@ -77,7 +78,7 @@ export default class TelegramProvider extends BaseProvider {
77
78
  this._tmaUrl
78
79
  }&redirect_query_name=startapp&policies=${JSON.stringify(
79
80
  this._policies,
80
- )}&rpc_url=${this.rpc}`;
81
+ )}&rpc_url=${this._rpcUrl}`;
81
82
 
82
83
  localStorage.setItem("lastUsedConnector", this.id);
83
84
  openLink(url);
@@ -86,6 +87,14 @@ export default class TelegramProvider extends BaseProvider {
86
87
  return;
87
88
  }
88
89
 
90
+ switchStarknetChain(_chainId: string): Promise<boolean> {
91
+ throw new Error("switchStarknetChain not implemented");
92
+ }
93
+
94
+ addStarknetChain(_chain: AddStarknetChainParameters): Promise<boolean> {
95
+ throw new Error("addStarknetChain not implemented");
96
+ }
97
+
89
98
  disconnect(): Promise<void> {
90
99
  cloudStorage.deleteItem("sessionSigner");
91
100
  cloudStorage.deleteItem("session");
@@ -118,7 +127,7 @@ export default class TelegramProvider extends BaseProvider {
118
127
 
119
128
  this._username = sessionRegistration.username;
120
129
  this.account = new SessionAccount(this, {
121
- rpcUrl: this.rpc.toString(),
130
+ rpcUrl: this._rpcUrl,
122
131
  privateKey: signer.privKey,
123
132
  address: sessionRegistration.address,
124
133
  ownerGuid: sessionRegistration.ownerGuid,
package/src/types.ts CHANGED
@@ -7,16 +7,12 @@ import {
7
7
  } from "starknet";
8
8
  import {
9
9
  AddInvokeTransactionResult,
10
+ ChainId,
10
11
  Signature,
11
12
  TypedData,
12
13
  } from "@starknet-io/types-js";
13
14
  import { KeychainIFrame, ProfileIFrame } from "./iframe";
14
- import {
15
- ColorMode,
16
- Policies,
17
- Policy,
18
- SessionPolicies,
19
- } from "@cartridge/presets";
15
+ import { ColorMode, Policy, SessionPolicies } from "@cartridge/presets";
20
16
 
21
17
  export type Session = {
22
18
  chainId: constants.StarknetChainId;
@@ -100,7 +96,7 @@ export type ControllerAccounts = Record<ContractAddress, CartridgeID>;
100
96
  export interface Keychain {
101
97
  probe(rpcUrl: string): Promise<ProbeReply | ConnectError>;
102
98
  connect(
103
- policies: Policies,
99
+ policies: SessionPolicies,
104
100
  rpcUrl: string,
105
101
  ): Promise<ConnectReply | ConnectError>;
106
102
  disconnect(): void;
@@ -134,6 +130,7 @@ export interface Keychain {
134
130
  openPurchaseCredits(): void;
135
131
  openExecute(calls: Call[]): Promise<void>;
136
132
  }
133
+
137
134
  export interface Profile {
138
135
  navigate(path: string): void;
139
136
  }
@@ -161,13 +158,17 @@ export type IFrameOptions = {
161
158
  colorMode?: ColorMode;
162
159
  };
163
160
 
161
+ export type Chain = {
162
+ rpcUrl: string;
163
+ };
164
+
164
165
  export type ProviderOptions = {
165
- /** The URL of the RPC */
166
- rpc: string;
166
+ defaultChainId: ChainId;
167
+ chains: Chain[];
167
168
  };
168
169
 
169
170
  export type KeychainOptions = IFrameOptions & {
170
- policies?: Policies;
171
+ policies?: SessionPolicies;
171
172
  /** The URL of keychain */
172
173
  url?: string;
173
174
  /** The origin of keychain */
@@ -193,8 +194,6 @@ export type ProfileContextTypeVariant =
193
194
  | "achievements"
194
195
  | "activity";
195
196
 
196
- export type Prefund = { address: string; min: string };
197
-
198
197
  export type Tokens = {
199
198
  erc20?: string[];
200
199
  };
package/src/utils.ts CHANGED
@@ -2,13 +2,16 @@ import {
2
2
  addAddressPadding,
3
3
  Call,
4
4
  CallData,
5
+ constants,
5
6
  getChecksumAddress,
6
7
  hash,
8
+ shortString,
7
9
  typedData,
8
10
  TypedDataRevision,
9
11
  } from "starknet";
10
12
  import wasm from "@cartridge/account-wasm/controller";
11
13
  import { Policies, SessionPolicies } from "@cartridge/presets";
14
+ import { ChainId } from "@starknet-io/types-js";
12
15
 
13
16
  // Whitelist of allowed property names to prevent prototype pollution
14
17
  const ALLOWED_PROPERTIES = new Set([
@@ -129,3 +132,28 @@ export function humanizeString(str: string): string {
129
132
  .replace(/^\w/, (c) => c.toUpperCase())
130
133
  );
131
134
  }
135
+
136
+ export function parseChainId(url: URL): ChainId {
137
+ const parts = url.pathname.split("/");
138
+
139
+ if (parts.includes("starknet")) {
140
+ if (parts.includes("mainnet")) {
141
+ return constants.StarknetChainId.SN_MAIN;
142
+ } else if (parts.includes("sepolia")) {
143
+ return constants.StarknetChainId.SN_SEPOLIA;
144
+ }
145
+ } else if (parts.length >= 3) {
146
+ const projectName = parts[2];
147
+ if (parts.includes("katana")) {
148
+ return shortString.encodeShortString(
149
+ `WP_${projectName.toUpperCase().replace(/-/g, "_")}`,
150
+ ) as ChainId;
151
+ } else if (parts.includes("mainnet")) {
152
+ return shortString.encodeShortString(
153
+ `GG_${projectName.toUpperCase().replace(/-/g, "_")}`,
154
+ ) as ChainId;
155
+ }
156
+ }
157
+
158
+ throw new Error(`Chain ${url.toString()} not supported`);
159
+ }