@cartridge/controller 0.5.0 → 0.5.2

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 (67) hide show
  1. package/.turbo/turbo-build$colon$deps.log +116 -115
  2. package/.turbo/turbo-build.log +120 -0
  3. package/.turbo/turbo-format.log +25 -0
  4. package/dist/account.d.ts +2 -2
  5. package/dist/account.js +19 -4
  6. package/dist/account.js.map +1 -1
  7. package/dist/constants.d.ts +2 -1
  8. package/dist/constants.js +2 -0
  9. package/dist/constants.js.map +1 -1
  10. package/dist/controller.d.ts +2 -2
  11. package/dist/controller.js +28 -188
  12. package/dist/controller.js.map +1 -1
  13. package/dist/iframe/base.d.ts +2 -2
  14. package/dist/iframe/base.js +3 -177
  15. package/dist/iframe/base.js.map +1 -1
  16. package/dist/iframe/index.d.ts +2 -2
  17. package/dist/iframe/index.js +8 -180
  18. package/dist/iframe/index.js.map +1 -1
  19. package/dist/iframe/keychain.d.ts +2 -2
  20. package/dist/iframe/keychain.js +3 -177
  21. package/dist/iframe/keychain.js.map +1 -1
  22. package/dist/iframe/profile.d.ts +2 -2
  23. package/dist/iframe/profile.js +8 -180
  24. package/dist/iframe/profile.js.map +1 -1
  25. package/dist/index.d.ts +4 -2
  26. package/dist/index.js +167 -189
  27. package/dist/index.js.map +1 -1
  28. package/dist/lookup.d.ts +4 -0
  29. package/dist/lookup.js +56 -0
  30. package/dist/lookup.js.map +1 -0
  31. package/dist/provider.d.ts +2 -2
  32. package/dist/session/account.d.ts +2 -3
  33. package/dist/session/account.js +13 -171
  34. package/dist/session/account.js.map +1 -1
  35. package/dist/session/index.d.ts +2 -3
  36. package/dist/session/index.js +40 -172
  37. package/dist/session/index.js.map +1 -1
  38. package/dist/session/provider.d.ts +4 -4
  39. package/dist/session/provider.js +39 -3
  40. package/dist/session/provider.js.map +1 -1
  41. package/dist/telegram/provider.d.ts +7 -4
  42. package/dist/telegram/provider.js +39 -3
  43. package/dist/telegram/provider.js.map +1 -1
  44. package/dist/{types-BkXOAuzX.d.ts → types-1WsOoNO2.d.ts} +17 -30
  45. package/dist/types.d.ts +2 -2
  46. package/dist/types.js.map +1 -1
  47. package/dist/utils.d.ts +6 -1
  48. package/dist/utils.js +101 -3
  49. package/dist/utils.js.map +1 -1
  50. package/package.json +5 -4
  51. package/src/account.ts +2 -1
  52. package/src/constants.ts +1 -0
  53. package/src/controller.ts +2 -3
  54. package/src/iframe/base.ts +3 -9
  55. package/src/iframe/profile.ts +5 -3
  56. package/src/index.ts +3 -1
  57. package/src/lookup.ts +68 -0
  58. package/src/session/account.ts +0 -1
  59. package/src/session/index.ts +0 -2
  60. package/src/session/provider.ts +5 -4
  61. package/src/telegram/provider.ts +8 -7
  62. package/src/types.ts +25 -34
  63. package/src/utils.ts +122 -2
  64. package/dist/presets.d.ts +0 -9
  65. package/dist/presets.js +0 -170
  66. package/dist/presets.js.map +0 -1
  67. package/src/presets.ts +0 -167
package/dist/utils.js CHANGED
@@ -1,7 +1,34 @@
1
1
  // src/utils.ts
2
- import { addAddressPadding, CallData } from "starknet";
2
+ import {
3
+ addAddressPadding,
4
+ CallData,
5
+ getChecksumAddress,
6
+ hash,
7
+ typedData,
8
+ TypedDataRevision
9
+ } from "starknet";
10
+ var ALLOWED_PROPERTIES = /* @__PURE__ */ new Set([
11
+ "contracts",
12
+ "messages",
13
+ "target",
14
+ "method",
15
+ "name",
16
+ "description",
17
+ "types",
18
+ "domain",
19
+ "primaryType"
20
+ ]);
21
+ function validatePropertyName(prop) {
22
+ if (!ALLOWED_PROPERTIES.has(prop)) {
23
+ throw new Error(`Invalid property name: ${prop}`);
24
+ }
25
+ }
26
+ function safeObjectAccess(obj, prop) {
27
+ validatePropertyName(prop);
28
+ return obj[prop];
29
+ }
3
30
  function normalizeCalls(calls) {
4
- return (Array.isArray(calls) ? calls : [calls]).map((call) => {
31
+ return toArray(calls).map((call) => {
5
32
  return {
6
33
  entrypoint: call.entrypoint,
7
34
  contractAddress: addAddressPadding(call.contractAddress),
@@ -9,7 +36,78 @@ function normalizeCalls(calls) {
9
36
  };
10
37
  });
11
38
  }
39
+ function toSessionPolicies(policies) {
40
+ return Array.isArray(policies) ? policies.reduce(
41
+ (prev, p) => {
42
+ if (safeObjectAccess(p, "target")) {
43
+ const target = getChecksumAddress(
44
+ safeObjectAccess(p, "target")
45
+ );
46
+ const entrypoint = safeObjectAccess(p, "method");
47
+ const contracts = safeObjectAccess(
48
+ prev,
49
+ "contracts"
50
+ );
51
+ const item = {
52
+ name: humanizeString(entrypoint),
53
+ entrypoint,
54
+ description: safeObjectAccess(p, "description")
55
+ };
56
+ if (target in contracts) {
57
+ const methods = toArray(contracts[target].methods);
58
+ contracts[target] = {
59
+ methods: [...methods, item]
60
+ };
61
+ } else {
62
+ contracts[target] = {
63
+ methods: [item]
64
+ };
65
+ }
66
+ } else {
67
+ const messages = safeObjectAccess(prev, "messages");
68
+ messages.push(p);
69
+ }
70
+ return prev;
71
+ },
72
+ { contracts: {}, messages: [] }
73
+ ) : policies;
74
+ }
75
+ function toWasmPolicies(policies) {
76
+ return [
77
+ ...Object.entries(policies.contracts ?? {}).flatMap(
78
+ ([target, { methods }]) => toArray(methods).map((m) => ({
79
+ target,
80
+ method: m.entrypoint
81
+ }))
82
+ ),
83
+ ...(policies.messages ?? []).map((p) => {
84
+ const domainHash = typedData.getStructHash(
85
+ p.types,
86
+ "StarknetDomain",
87
+ p.domain,
88
+ TypedDataRevision.ACTIVE
89
+ );
90
+ const typeHash = typedData.getTypeHash(
91
+ p.types,
92
+ p.primaryType,
93
+ TypedDataRevision.ACTIVE
94
+ );
95
+ return {
96
+ scope_hash: hash.computePoseidonHash(domainHash, typeHash)
97
+ };
98
+ })
99
+ ];
100
+ }
101
+ function toArray(val) {
102
+ return Array.isArray(val) ? val : [val];
103
+ }
104
+ function humanizeString(str) {
105
+ return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/_/g, " ").toLowerCase().replace(/^\w/, (c) => c.toUpperCase());
106
+ }
12
107
  export {
13
- normalizeCalls
108
+ normalizeCalls,
109
+ toArray,
110
+ toSessionPolicies,
111
+ toWasmPolicies
14
112
  };
15
113
  //# sourceMappingURL=utils.js.map
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts"],"sourcesContent":["import { addAddressPadding, Call, CallData } from \"starknet\";\n\nexport function normalizeCalls(calls: Call | Call[]) {\n return (Array.isArray(calls) ? calls : [calls]).map((call) => {\n return {\n entrypoint: call.entrypoint,\n contractAddress: addAddressPadding(call.contractAddress),\n calldata: CallData.toHex(call.calldata),\n };\n });\n}\n"],"mappings":";AAAA,SAAS,mBAAyB,gBAAgB;AAE3C,SAAS,eAAe,OAAsB;AACnD,UAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS;AAC5D,WAAO;AAAA,MACL,YAAY,KAAK;AAAA,MACjB,iBAAiB,kBAAkB,KAAK,eAAe;AAAA,MACvD,UAAU,SAAS,MAAM,KAAK,QAAQ;AAAA,IACxC;AAAA,EACF,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/utils.ts"],"sourcesContent":["import {\n addAddressPadding,\n Call,\n CallData,\n getChecksumAddress,\n hash,\n typedData,\n TypedDataRevision,\n} from \"starknet\";\nimport wasm from \"@cartridge/account-wasm/controller\";\nimport { Policies, SessionPolicies } from \"@cartridge/presets\";\n\n// Whitelist of allowed property names to prevent prototype pollution\nconst ALLOWED_PROPERTIES = new Set([\n \"contracts\",\n \"messages\",\n \"target\",\n \"method\",\n \"name\",\n \"description\",\n \"types\",\n \"domain\",\n \"primaryType\",\n]);\n\nfunction validatePropertyName(prop: string): void {\n if (!ALLOWED_PROPERTIES.has(prop)) {\n throw new Error(`Invalid property name: ${prop}`);\n }\n}\n\nfunction safeObjectAccess<T>(obj: any, prop: string): T {\n validatePropertyName(prop);\n return obj[prop];\n}\n\nexport function normalizeCalls(calls: Call | Call[]) {\n return toArray(calls).map((call) => {\n return {\n entrypoint: call.entrypoint,\n contractAddress: addAddressPadding(call.contractAddress),\n calldata: CallData.toHex(call.calldata),\n };\n });\n}\n\nexport function toSessionPolicies(policies: Policies): SessionPolicies {\n return Array.isArray(policies)\n ? policies.reduce<SessionPolicies>(\n (prev, p) => {\n if (safeObjectAccess<string>(p, \"target\")) {\n const target = getChecksumAddress(\n safeObjectAccess<string>(p, \"target\"),\n );\n const entrypoint = safeObjectAccess<string>(p, \"method\");\n const contracts = safeObjectAccess<Record<string, any>>(\n prev,\n \"contracts\",\n );\n const item = {\n name: humanizeString(entrypoint),\n entrypoint: entrypoint,\n description: safeObjectAccess<string>(p, \"description\"),\n };\n\n if (target in contracts) {\n const methods = toArray(contracts[target].methods);\n contracts[target] = {\n methods: [...methods, item],\n };\n } else {\n contracts[target] = {\n methods: [item],\n };\n }\n } else {\n const messages = safeObjectAccess<any[]>(prev, \"messages\");\n messages.push(p);\n }\n\n return prev;\n },\n { contracts: {}, messages: [] },\n )\n : policies;\n}\n\nexport function toWasmPolicies(policies: SessionPolicies): wasm.Policy[] {\n return [\n ...Object.entries(policies.contracts ?? {}).flatMap(\n ([target, { methods }]) =>\n toArray(methods).map((m) => ({\n target,\n method: m.entrypoint,\n })),\n ),\n ...(policies.messages ?? []).map((p) => {\n const domainHash = typedData.getStructHash(\n p.types,\n \"StarknetDomain\",\n p.domain,\n TypedDataRevision.ACTIVE,\n );\n const typeHash = typedData.getTypeHash(\n p.types,\n p.primaryType,\n TypedDataRevision.ACTIVE,\n );\n\n return {\n scope_hash: hash.computePoseidonHash(domainHash, typeHash),\n };\n }),\n ];\n}\n\nexport function toArray<T>(val: T | T[]): T[] {\n return Array.isArray(val) ? val : [val];\n}\n\nfunction humanizeString(str: string): string {\n return (\n str\n // Convert from camelCase or snake_case\n .replace(/([a-z])([A-Z])/g, \"$1 $2\") // camelCase to spaces\n .replace(/_/g, \" \") // snake_case to spaces\n .toLowerCase()\n // Capitalize first letter\n .replace(/^\\w/, (c) => c.toUpperCase())\n );\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartridge/controller",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Cartridge Controller",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -31,16 +31,17 @@
31
31
  "fast-deep-equal": "^3.1.3",
32
32
  "query-string": "^7.1.1",
33
33
  "starknet": "^6.11.0",
34
- "@cartridge/account-wasm": "^0.5.0",
35
- "@cartridge/utils": "^0.5.0"
34
+ "@cartridge/account-wasm": "0.5.2",
35
+ "@cartridge/presets": "0.5.2"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.6.0",
39
39
  "typescript": "^5.4.5",
40
- "@cartridge/tsconfig": "^0.5.0"
40
+ "@cartridge/tsconfig": "0.5.2"
41
41
  },
42
42
  "scripts": {
43
43
  "build:deps": "tsup --dts-resolve",
44
+ "build": "pnpm build:deps",
44
45
  "format": "prettier --write \"src/**/*.ts\"",
45
46
  "format:check": "prettier --check \"src/**/*.ts\"",
46
47
  "version": "pnpm pkg get version"
package/src/account.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  } from "./types";
18
18
  import { AsyncMethodReturns } from "@cartridge/penpal";
19
19
  import BaseProvider from "./provider";
20
+ import { toArray } from "./utils";
20
21
 
21
22
  class ControllerAccount extends WalletAccount {
22
23
  address: string;
@@ -52,7 +53,7 @@ class ControllerAccount extends WalletAccount {
52
53
  * @returns response from addTransaction
53
54
  */
54
55
  async execute(calls: AllowArray<Call>): Promise<InvokeFunctionResponse> {
55
- calls = Array.isArray(calls) ? calls : [calls];
56
+ calls = toArray(calls);
56
57
 
57
58
  return new Promise(async (resolve, reject) => {
58
59
  const sessionExecute = await this.keychain.execute(
package/src/constants.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export const KEYCHAIN_URL = "https://x.cartridge.gg";
2
2
  export const PROFILE_URL = "https://profile.cartridge.gg";
3
+ export const API_URL = "https://api.cartridge.gg";
package/src/controller.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  import { AsyncMethodReturns } from "@cartridge/penpal";
2
- import { normalize } from "@cartridge/utils";
3
2
 
4
3
  import ControllerAccount from "./account";
5
4
  import { KeychainIFrame, ProfileIFrame } from "./iframe";
6
5
  import { NotReadyToConnect } from "./errors";
7
6
  import {
8
7
  Keychain,
9
- Policy,
10
8
  ResponseCodes,
11
9
  ConnectReply,
12
10
  ProbeReply,
@@ -18,6 +16,7 @@ import {
18
16
  } from "./types";
19
17
  import BaseProvider from "./provider";
20
18
  import { WalletAccount } from "starknet";
19
+ import { Policy } from "@cartridge/presets";
21
20
 
22
21
  export default class ControllerProvider extends BaseProvider {
23
22
  private keychain?: AsyncMethodReturns<Keychain>;
@@ -82,7 +81,7 @@ export default class ControllerProvider extends BaseProvider {
82
81
  methods: {
83
82
  openSettings: this.openSettings.bind(this),
84
83
  openPurchaseCredits: this.openPurchaseCredits.bind(this),
85
- openExecute: normalize(() => this.openExecute.bind(this)),
84
+ openExecute: this.openExecute.bind(this),
86
85
  },
87
86
  rpcUrl: this.rpc.toString(),
88
87
  username,
@@ -1,5 +1,4 @@
1
1
  import { AsyncMethodReturns, connectToChild } from "@cartridge/penpal";
2
- import { defaultPresets } from "../presets";
3
2
  import { ControllerOptions, Modal } from "../types";
4
3
 
5
4
  export type IFrameOptions<CallSender> = Omit<
@@ -35,14 +34,9 @@ export class IFrame<CallSender extends {}> implements Modal {
35
34
  return;
36
35
  }
37
36
 
38
- url.searchParams.set(
39
- "theme",
40
- encodeURIComponent(
41
- JSON.stringify(
42
- defaultPresets[theme ?? "cartridge"] ?? defaultPresets.cartridge,
43
- ),
44
- ),
45
- );
37
+ if (theme) {
38
+ url.searchParams.set("theme", encodeURIComponent(theme));
39
+ }
46
40
 
47
41
  if (colorMode) {
48
42
  url.searchParams.set("colorMode", colorMode);
@@ -24,10 +24,12 @@ export class ProfileIFrame extends IFrame<Profile> {
24
24
  const _url = new URL(
25
25
  slot
26
26
  ? namespace
27
- ? `${_profileUrl}/account/${username}/slot/${slot}?ns=${encodeURIComponent(
28
- namespace,
27
+ ? `${_profileUrl}/account/${username}/slot/${slot}?ps=${encodeURIComponent(
28
+ slot,
29
+ )}&ns=${encodeURIComponent(namespace)}`
30
+ : `${_profileUrl}/account/${username}/slot/${slot}?ps=${encodeURIComponent(
31
+ slot,
29
32
  )}`
30
- : `${_profileUrl}/account/${username}/slot/${slot}`
31
33
  : `${_profileUrl}/account/${username}`,
32
34
  );
33
35
 
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { default } from "./controller";
2
2
  export * from "./errors";
3
3
  export * from "./types";
4
+ export * from "./lookup";
4
5
 
5
- export { defaultPresets } from "./presets";
6
+ export { toWasmPolicies, toSessionPolicies, toArray } from "./utils";
7
+ export * from "@cartridge/presets";
package/src/lookup.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { LookupRequest, LookupResponse } from "./types";
2
+ import { num } from "starknet";
3
+ import { API_URL } from "./constants";
4
+
5
+ const cache = new Map<string, string>();
6
+
7
+ async function lookup(request: LookupRequest): Promise<LookupResponse> {
8
+ if (!request.addresses?.length && !request.usernames?.length) {
9
+ return { results: [] };
10
+ }
11
+
12
+ const response = await fetch(`${API_URL}/lookup`, {
13
+ method: "POST",
14
+ headers: {
15
+ "Content-Type": "application/json",
16
+ },
17
+ body: JSON.stringify(request),
18
+ });
19
+
20
+ if (!response.ok) {
21
+ throw new Error(`HTTP error! status: ${response.status}`);
22
+ }
23
+
24
+ return response.json();
25
+ }
26
+
27
+ export async function lookupUsernames(
28
+ usernames: string[],
29
+ ): Promise<Map<string, string>> {
30
+ const uncachedUsernames = usernames.filter((name) => !cache.has(name));
31
+
32
+ if (uncachedUsernames.length > 0) {
33
+ const response = await lookup({ usernames: uncachedUsernames });
34
+
35
+ response.results.forEach((result) => {
36
+ cache.set(result.username, result.addresses[0]); // TODO: handle multiple controller addresses
37
+ });
38
+ }
39
+
40
+ return new Map(
41
+ usernames
42
+ .map((name) => [name, cache.get(name)] as [string, string])
43
+ .filter((entry): entry is [string, string] => entry[1] !== undefined),
44
+ );
45
+ }
46
+
47
+ export async function lookupAddresses(
48
+ addresses: string[],
49
+ ): Promise<Map<string, string>> {
50
+ addresses = addresses.map(num.toHex);
51
+ const uncachedAddresses = addresses.filter((addr) => !cache.has(addr));
52
+
53
+ if (uncachedAddresses.length > 0) {
54
+ const response = await lookup({
55
+ addresses: uncachedAddresses,
56
+ });
57
+
58
+ response.results.forEach((result) => {
59
+ cache.set(result.addresses[0], result.username); // TODO: handle multiple controller addresses
60
+ });
61
+ }
62
+
63
+ return new Map(
64
+ addresses
65
+ .map((addr) => [addr, cache.get(addr)] as [string, string])
66
+ .filter((entry): entry is [string, string] => entry[1] !== undefined),
67
+ );
68
+ }
@@ -7,7 +7,6 @@ import BaseProvider from "../provider";
7
7
 
8
8
  export * from "../errors";
9
9
  export * from "../types";
10
- export { defaultPresets } from "../presets";
11
10
 
12
11
  export default class SessionAccount extends WalletAccount {
13
12
  public controller: CartridgeSessionAccount;
@@ -2,5 +2,3 @@ export { default } from "./provider";
2
2
  export * from "./provider";
3
3
  export * from "../errors";
4
4
  export * from "../types";
5
-
6
- export { defaultPresets } from "../presets";
@@ -1,9 +1,10 @@
1
- import { Policy } from "../types";
2
1
  import { ec, stark, WalletAccount } from "starknet";
3
2
 
4
3
  import SessionAccount from "./account";
5
4
  import { KEYCHAIN_URL } from "../constants";
6
5
  import BaseProvider from "../provider";
6
+ import { toWasmPolicies } from "../utils";
7
+ import { SessionPolicies } from "@cartridge/presets";
7
8
 
8
9
  interface SessionRegistration {
9
10
  username: string;
@@ -16,7 +17,7 @@ interface SessionRegistration {
16
17
  export type SessionOptions = {
17
18
  rpc: string;
18
19
  chainId: string;
19
- policies: Policy[];
20
+ policies: SessionPolicies;
20
21
  redirectUrl: string;
21
22
  };
22
23
 
@@ -28,7 +29,7 @@ export default class SessionProvider extends BaseProvider {
28
29
 
29
30
  protected _username?: string;
30
31
  protected _redirectUrl: string;
31
- protected _policies: Policy[];
32
+ protected _policies: SessionPolicies;
32
33
 
33
34
  constructor({ rpc, chainId, policies, redirectUrl }: SessionOptions) {
34
35
  super({ rpc });
@@ -132,7 +133,7 @@ export default class SessionProvider extends BaseProvider {
132
133
  ownerGuid: sessionRegistration.ownerGuid,
133
134
  chainId: this._chainId,
134
135
  expiresAt: parseInt(sessionRegistration.expiresAt),
135
- policies: this._policies,
136
+ policies: toWasmPolicies(this._policies),
136
137
  });
137
138
 
138
139
  return this.account;
@@ -6,10 +6,11 @@ import {
6
6
  } from "@telegram-apps/sdk";
7
7
  import { ec, stark, WalletAccount } from "starknet";
8
8
 
9
- import { KEYCHAIN_URL } from "src/constants";
10
- import { Policy } from "src/types";
11
- import SessionAccount from "src/session/account";
12
- import BaseProvider from "src/provider";
9
+ import { KEYCHAIN_URL } from "../constants";
10
+ import SessionAccount from "../session/account";
11
+ import BaseProvider from "../provider";
12
+ import { toWasmPolicies } from "../utils";
13
+ import { SessionPolicies } from "@cartridge/presets";
13
14
 
14
15
  interface SessionRegistration {
15
16
  username: string;
@@ -23,7 +24,7 @@ export default class TelegramProvider extends BaseProvider {
23
24
  private _tmaUrl: string;
24
25
  protected _chainId: string;
25
26
  protected _username?: string;
26
- protected _policies: Policy[];
27
+ protected _policies: SessionPolicies;
27
28
 
28
29
  constructor({
29
30
  rpc,
@@ -33,7 +34,7 @@ export default class TelegramProvider extends BaseProvider {
33
34
  }: {
34
35
  rpc: string;
35
36
  chainId: string;
36
- policies: Policy[];
37
+ policies: SessionPolicies;
37
38
  tmaUrl: string;
38
39
  }) {
39
40
  super({
@@ -123,7 +124,7 @@ export default class TelegramProvider extends BaseProvider {
123
124
  ownerGuid: sessionRegistration.ownerGuid,
124
125
  chainId: this._chainId,
125
126
  expiresAt: parseInt(sessionRegistration.expiresAt),
126
- policies: this._policies,
127
+ policies: toWasmPolicies(this._policies),
127
128
  });
128
129
 
129
130
  return this.account;
package/src/types.ts CHANGED
@@ -11,7 +11,12 @@ import {
11
11
  TypedData,
12
12
  } from "@starknet-io/types-js";
13
13
  import { KeychainIFrame, ProfileIFrame } from "./iframe";
14
- import wasm from "@cartridge/account-wasm/controller";
14
+ import {
15
+ ColorMode,
16
+ Policies,
17
+ Policy,
18
+ SessionPolicies,
19
+ } from "@cartridge/presets";
15
20
 
16
21
  export type Session = {
17
22
  chainId: constants.StarknetChainId;
@@ -24,10 +29,6 @@ export type Session = {
24
29
  };
25
30
  };
26
31
 
27
- export type Policy = wasm.Policy & {
28
- description?: string;
29
- };
30
-
31
32
  export enum ResponseCodes {
32
33
  SUCCESS = "SUCCESS",
33
34
  NOT_CONNECTED = "NOT_CONNECTED",
@@ -51,7 +52,7 @@ export type ControllerError = {
51
52
  export type ConnectReply = {
52
53
  code: ResponseCodes.SUCCESS;
53
54
  address: string;
54
- policies: Policy[];
55
+ policies: SessionPolicies;
55
56
  };
56
57
 
57
58
  export type ExecuteReply =
@@ -77,6 +78,20 @@ export type IFrames = {
77
78
  profile?: ProfileIFrame;
78
79
  };
79
80
 
81
+ export interface LookupRequest {
82
+ usernames?: string[];
83
+ addresses?: string[];
84
+ }
85
+
86
+ export interface LookupResult {
87
+ username: string;
88
+ addresses: string[];
89
+ }
90
+
91
+ export interface LookupResponse {
92
+ results: LookupResult[];
93
+ }
94
+
80
95
  type ContractAddress = string;
81
96
  type CartridgeID = string;
82
97
  export type ControllerAccounts = Record<ContractAddress, CartridgeID>;
@@ -84,7 +99,7 @@ export type ControllerAccounts = Record<ContractAddress, CartridgeID>;
84
99
  export interface Keychain {
85
100
  probe(rpcUrl: string): Promise<ProbeReply | ConnectError>;
86
101
  connect(
87
- policies: Policy[],
102
+ policies: Policies,
88
103
  rpcUrl: string,
89
104
  ): Promise<ConnectReply | ConnectError>;
90
105
  disconnect(): void;
@@ -149,7 +164,7 @@ export type ProviderOptions = {
149
164
  };
150
165
 
151
166
  export type KeychainOptions = IFrameOptions & {
152
- policies?: Policy[];
167
+ policies?: Policies;
153
168
  /** The URL of keychain */
154
169
  url?: string;
155
170
  /** The origin of keychain */
@@ -172,32 +187,8 @@ export type ProfileOptions = IFrameOptions & {
172
187
  export type ProfileContextTypeVariant =
173
188
  | "inventory"
174
189
  | "trophies"
175
- | "achievements";
176
-
177
- export type ColorMode = "light" | "dark";
178
-
179
- export type ControllerTheme = {
180
- id: string;
181
- name: string;
182
- icon: string;
183
- cover: ThemeValue<string>;
184
- colorMode: ColorMode;
185
- };
186
-
187
- export type ControllerThemePresets = Record<string, ControllerThemePreset>;
188
-
189
- export type ControllerThemePreset = Omit<ControllerTheme, "colorMode"> & {
190
- colors?: ControllerColors;
191
- };
192
-
193
- export type ControllerColors = {
194
- primary?: ControllerColor;
195
- primaryForeground?: ControllerColor;
196
- };
197
-
198
- export type ControllerColor = ThemeValue<string>;
199
-
200
- export type ThemeValue<T> = T | { dark: T; light: T };
190
+ | "achievements"
191
+ | "activity";
201
192
 
202
193
  export type Prefund = { address: string; min: string };
203
194
 
package/src/utils.ts CHANGED
@@ -1,7 +1,41 @@
1
- import { addAddressPadding, Call, CallData } from "starknet";
1
+ import {
2
+ addAddressPadding,
3
+ Call,
4
+ CallData,
5
+ getChecksumAddress,
6
+ hash,
7
+ typedData,
8
+ TypedDataRevision,
9
+ } from "starknet";
10
+ import wasm from "@cartridge/account-wasm/controller";
11
+ import { Policies, SessionPolicies } from "@cartridge/presets";
12
+
13
+ // Whitelist of allowed property names to prevent prototype pollution
14
+ const ALLOWED_PROPERTIES = new Set([
15
+ "contracts",
16
+ "messages",
17
+ "target",
18
+ "method",
19
+ "name",
20
+ "description",
21
+ "types",
22
+ "domain",
23
+ "primaryType",
24
+ ]);
25
+
26
+ function validatePropertyName(prop: string): void {
27
+ if (!ALLOWED_PROPERTIES.has(prop)) {
28
+ throw new Error(`Invalid property name: ${prop}`);
29
+ }
30
+ }
31
+
32
+ function safeObjectAccess<T>(obj: any, prop: string): T {
33
+ validatePropertyName(prop);
34
+ return obj[prop];
35
+ }
2
36
 
3
37
  export function normalizeCalls(calls: Call | Call[]) {
4
- return (Array.isArray(calls) ? calls : [calls]).map((call) => {
38
+ return toArray(calls).map((call) => {
5
39
  return {
6
40
  entrypoint: call.entrypoint,
7
41
  contractAddress: addAddressPadding(call.contractAddress),
@@ -9,3 +43,89 @@ export function normalizeCalls(calls: Call | Call[]) {
9
43
  };
10
44
  });
11
45
  }
46
+
47
+ export function toSessionPolicies(policies: Policies): SessionPolicies {
48
+ return Array.isArray(policies)
49
+ ? policies.reduce<SessionPolicies>(
50
+ (prev, p) => {
51
+ if (safeObjectAccess<string>(p, "target")) {
52
+ const target = getChecksumAddress(
53
+ safeObjectAccess<string>(p, "target"),
54
+ );
55
+ const entrypoint = safeObjectAccess<string>(p, "method");
56
+ const contracts = safeObjectAccess<Record<string, any>>(
57
+ prev,
58
+ "contracts",
59
+ );
60
+ const item = {
61
+ name: humanizeString(entrypoint),
62
+ entrypoint: entrypoint,
63
+ description: safeObjectAccess<string>(p, "description"),
64
+ };
65
+
66
+ if (target in contracts) {
67
+ const methods = toArray(contracts[target].methods);
68
+ contracts[target] = {
69
+ methods: [...methods, item],
70
+ };
71
+ } else {
72
+ contracts[target] = {
73
+ methods: [item],
74
+ };
75
+ }
76
+ } else {
77
+ const messages = safeObjectAccess<any[]>(prev, "messages");
78
+ messages.push(p);
79
+ }
80
+
81
+ return prev;
82
+ },
83
+ { contracts: {}, messages: [] },
84
+ )
85
+ : policies;
86
+ }
87
+
88
+ export function toWasmPolicies(policies: SessionPolicies): wasm.Policy[] {
89
+ return [
90
+ ...Object.entries(policies.contracts ?? {}).flatMap(
91
+ ([target, { methods }]) =>
92
+ toArray(methods).map((m) => ({
93
+ target,
94
+ method: m.entrypoint,
95
+ })),
96
+ ),
97
+ ...(policies.messages ?? []).map((p) => {
98
+ const domainHash = typedData.getStructHash(
99
+ p.types,
100
+ "StarknetDomain",
101
+ p.domain,
102
+ TypedDataRevision.ACTIVE,
103
+ );
104
+ const typeHash = typedData.getTypeHash(
105
+ p.types,
106
+ p.primaryType,
107
+ TypedDataRevision.ACTIVE,
108
+ );
109
+
110
+ return {
111
+ scope_hash: hash.computePoseidonHash(domainHash, typeHash),
112
+ };
113
+ }),
114
+ ];
115
+ }
116
+
117
+ export function toArray<T>(val: T | T[]): T[] {
118
+ return Array.isArray(val) ? val : [val];
119
+ }
120
+
121
+ function humanizeString(str: string): string {
122
+ return (
123
+ str
124
+ // Convert from camelCase or snake_case
125
+ .replace(/([a-z])([A-Z])/g, "$1 $2") // camelCase to spaces
126
+ .replace(/_/g, " ") // snake_case to spaces
127
+ .toLowerCase()
128
+ // Capitalize first letter
129
+ .replace(/^\w/, (c) => c.toUpperCase())
130
+ );
131
+ }
package/dist/presets.d.ts DELETED
@@ -1,9 +0,0 @@
1
- import { n as ControllerThemePresets } from './types-BkXOAuzX.js';
2
- import 'starknet';
3
- import '@starknet-io/types-js';
4
- import '@cartridge/penpal';
5
- import '@cartridge/account-wasm/controller';
6
-
7
- declare const defaultPresets: ControllerThemePresets;
8
-
9
- export { defaultPresets };