@cartridge/controller 0.5.1 → 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 -1
  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 -1
  11. package/dist/controller.js +27 -184
  12. package/dist/controller.js.map +1 -1
  13. package/dist/iframe/base.d.ts +2 -1
  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 -1
  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 -1
  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 -1
  23. package/dist/iframe/profile.js +8 -180
  24. package/dist/iframe/profile.js.map +1 -1
  25. package/dist/index.d.ts +5 -2
  26. package/dist/index.js +166 -185
  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 -1
  32. package/dist/session/account.d.ts +2 -2
  33. package/dist/session/account.js +6 -170
  34. package/dist/session/account.js.map +1 -1
  35. package/dist/session/index.d.ts +2 -2
  36. package/dist/session/index.js +20 -183
  37. package/dist/session/index.js.map +1 -1
  38. package/dist/session/provider.d.ts +4 -3
  39. package/dist/session/provider.js +19 -14
  40. package/dist/session/provider.js.map +1 -1
  41. package/dist/telegram/provider.d.ts +7 -4
  42. package/dist/telegram/provider.js +19 -14
  43. package/dist/telegram/provider.js.map +1 -1
  44. package/dist/{types-ikHqoYmG.d.ts → types-1WsOoNO2.d.ts} +17 -37
  45. package/dist/types.d.ts +2 -1
  46. package/dist/types.js.map +1 -1
  47. package/dist/utils.d.ts +5 -5
  48. package/dist/utils.js +80 -14
  49. package/dist/utils.js.map +1 -1
  50. package/package.json +5 -3
  51. package/src/account.ts +2 -1
  52. package/src/constants.ts +1 -0
  53. package/src/controller.ts +1 -1
  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 +4 -4
  61. package/src/telegram/provider.ts +7 -7
  62. package/src/types.ts +23 -44
  63. package/src/utils.ts +100 -16
  64. package/dist/presets.d.ts +0 -8
  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
@@ -2,12 +2,33 @@
2
2
  import {
3
3
  addAddressPadding,
4
4
  CallData,
5
+ getChecksumAddress,
5
6
  hash,
6
7
  typedData,
7
8
  TypedDataRevision
8
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
+ }
9
30
  function normalizeCalls(calls) {
10
- return (Array.isArray(calls) ? calls : [calls]).map((call) => {
31
+ return toArray(calls).map((call) => {
11
32
  return {
12
33
  entrypoint: call.entrypoint,
13
34
  contractAddress: addAddressPadding(call.contractAddress),
@@ -15,33 +36,78 @@ function normalizeCalls(calls) {
15
36
  };
16
37
  });
17
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
+ }
18
75
  function toWasmPolicies(policies) {
19
- return policies.map((richPolicy) => {
20
- if ("target" in richPolicy) {
21
- return {
22
- target: richPolicy.target,
23
- method: richPolicy.method
24
- };
25
- } else {
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) => {
26
84
  const domainHash = typedData.getStructHash(
27
- richPolicy.types,
85
+ p.types,
28
86
  "StarknetDomain",
29
- richPolicy.domain,
87
+ p.domain,
30
88
  TypedDataRevision.ACTIVE
31
89
  );
32
90
  const typeHash = typedData.getTypeHash(
33
- richPolicy.types,
34
- richPolicy.primaryType,
91
+ p.types,
92
+ p.primaryType,
35
93
  TypedDataRevision.ACTIVE
36
94
  );
37
95
  return {
38
96
  scope_hash: hash.computePoseidonHash(domainHash, typeHash)
39
97
  };
40
- }
41
- });
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());
42
106
  }
43
107
  export {
44
108
  normalizeCalls,
109
+ toArray,
110
+ toSessionPolicies,
45
111
  toWasmPolicies
46
112
  };
47
113
  //# sourceMappingURL=utils.js.map
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts"],"sourcesContent":["import {\n addAddressPadding,\n Call,\n CallData,\n hash,\n typedData,\n TypedDataRevision,\n} from \"starknet\";\nimport { Policy } from \"./types\";\nimport wasm from \"@cartridge/account-wasm/controller\";\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\nexport function toWasmPolicies(policies: Policy[]): wasm.Policy[] {\n return policies.map((richPolicy) => {\n if (\"target\" in richPolicy) {\n return {\n target: richPolicy.target,\n method: richPolicy.method,\n };\n } else {\n const domainHash = typedData.getStructHash(\n richPolicy.types,\n \"StarknetDomain\",\n richPolicy.domain,\n TypedDataRevision.ACTIVE,\n );\n const typeHash = typedData.getTypeHash(\n richPolicy.types,\n richPolicy.primaryType,\n TypedDataRevision.ACTIVE,\n );\n\n return {\n scope_hash: hash.computePoseidonHash(domainHash, typeHash),\n };\n }\n });\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIA,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;AAEO,SAAS,eAAe,UAAmC;AAChE,SAAO,SAAS,IAAI,CAAC,eAAe;AAClC,QAAI,YAAY,YAAY;AAC1B,aAAO;AAAA,QACL,QAAQ,WAAW;AAAA,QACnB,QAAQ,WAAW;AAAA,MACrB;AAAA,IACF,OAAO;AACL,YAAM,aAAa,UAAU;AAAA,QAC3B,WAAW;AAAA,QACX;AAAA,QACA,WAAW;AAAA,QACX,kBAAkB;AAAA,MACpB;AACA,YAAM,WAAW,UAAU;AAAA,QACzB,WAAW;AAAA,QACX,WAAW;AAAA,QACX,kBAAkB;AAAA,MACpB;AAEA,aAAO;AAAA,QACL,YAAY,KAAK,oBAAoB,YAAY,QAAQ;AAAA,MAC3D;AAAA,IACF;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.1",
3
+ "version": "0.5.2",
4
4
  "description": "Cartridge Controller",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -31,15 +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.1"
34
+ "@cartridge/account-wasm": "0.5.2",
35
+ "@cartridge/presets": "0.5.2"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@types/node": "^20.6.0",
38
39
  "typescript": "^5.4.5",
39
- "@cartridge/tsconfig": "^0.5.1"
40
+ "@cartridge/tsconfig": "0.5.2"
40
41
  },
41
42
  "scripts": {
42
43
  "build:deps": "tsup --dts-resolve",
44
+ "build": "pnpm build:deps",
43
45
  "format": "prettier --write \"src/**/*.ts\"",
44
46
  "format:check": "prettier --check \"src/**/*.ts\"",
45
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
@@ -5,7 +5,6 @@ import { KeychainIFrame, ProfileIFrame } from "./iframe";
5
5
  import { NotReadyToConnect } from "./errors";
6
6
  import {
7
7
  Keychain,
8
- Policy,
9
8
  ResponseCodes,
10
9
  ConnectReply,
11
10
  ProbeReply,
@@ -17,6 +16,7 @@ import {
17
16
  } from "./types";
18
17
  import BaseProvider from "./provider";
19
18
  import { WalletAccount } from "starknet";
19
+ import { Policy } from "@cartridge/presets";
20
20
 
21
21
  export default class ControllerProvider extends BaseProvider {
22
22
  private keychain?: AsyncMethodReturns<Keychain>;
@@ -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,10 +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";
7
- import { toWasmPolicies } from "src/utils";
6
+ import { toWasmPolicies } from "../utils";
7
+ import { SessionPolicies } from "@cartridge/presets";
8
8
 
9
9
  interface SessionRegistration {
10
10
  username: string;
@@ -17,7 +17,7 @@ interface SessionRegistration {
17
17
  export type SessionOptions = {
18
18
  rpc: string;
19
19
  chainId: string;
20
- policies: Policy[];
20
+ policies: SessionPolicies;
21
21
  redirectUrl: string;
22
22
  };
23
23
 
@@ -29,7 +29,7 @@ export default class SessionProvider extends BaseProvider {
29
29
 
30
30
  protected _username?: string;
31
31
  protected _redirectUrl: string;
32
- protected _policies: Policy[];
32
+ protected _policies: SessionPolicies;
33
33
 
34
34
  constructor({ rpc, chainId, policies, redirectUrl }: SessionOptions) {
35
35
  super({ rpc });
@@ -6,11 +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";
13
- import { toWasmPolicies } from "src/utils";
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";
14
14
 
15
15
  interface SessionRegistration {
16
16
  username: string;
@@ -24,7 +24,7 @@ export default class TelegramProvider extends BaseProvider {
24
24
  private _tmaUrl: string;
25
25
  protected _chainId: string;
26
26
  protected _username?: string;
27
- protected _policies: Policy[];
27
+ protected _policies: SessionPolicies;
28
28
 
29
29
  constructor({
30
30
  rpc,
@@ -34,7 +34,7 @@ export default class TelegramProvider extends BaseProvider {
34
34
  }: {
35
35
  rpc: string;
36
36
  chainId: string;
37
- policies: Policy[];
37
+ policies: SessionPolicies;
38
38
  tmaUrl: string;
39
39
  }) {
40
40
  super({
package/src/types.ts CHANGED
@@ -8,11 +8,15 @@ import {
8
8
  import {
9
9
  AddInvokeTransactionResult,
10
10
  Signature,
11
- StarknetDomain,
12
- StarknetType,
13
11
  TypedData,
14
12
  } from "@starknet-io/types-js";
15
13
  import { KeychainIFrame, ProfileIFrame } from "./iframe";
14
+ import {
15
+ ColorMode,
16
+ Policies,
17
+ Policy,
18
+ SessionPolicies,
19
+ } from "@cartridge/presets";
16
20
 
17
21
  export type Session = {
18
22
  chainId: constants.StarknetChainId;
@@ -25,20 +29,6 @@ export type Session = {
25
29
  };
26
30
  };
27
31
 
28
- export type Policy = CallPolicy | TypedDataPolicy;
29
-
30
- export type CallPolicy = {
31
- target: string;
32
- method: string;
33
- description?: string;
34
- };
35
-
36
- export type TypedDataPolicy = {
37
- types: Record<string, StarknetType[]>;
38
- primaryType: string;
39
- domain: StarknetDomain;
40
- };
41
-
42
32
  export enum ResponseCodes {
43
33
  SUCCESS = "SUCCESS",
44
34
  NOT_CONNECTED = "NOT_CONNECTED",
@@ -62,7 +52,7 @@ export type ControllerError = {
62
52
  export type ConnectReply = {
63
53
  code: ResponseCodes.SUCCESS;
64
54
  address: string;
65
- policies: Policy[];
55
+ policies: SessionPolicies;
66
56
  };
67
57
 
68
58
  export type ExecuteReply =
@@ -88,6 +78,20 @@ export type IFrames = {
88
78
  profile?: ProfileIFrame;
89
79
  };
90
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
+
91
95
  type ContractAddress = string;
92
96
  type CartridgeID = string;
93
97
  export type ControllerAccounts = Record<ContractAddress, CartridgeID>;
@@ -95,7 +99,7 @@ export type ControllerAccounts = Record<ContractAddress, CartridgeID>;
95
99
  export interface Keychain {
96
100
  probe(rpcUrl: string): Promise<ProbeReply | ConnectError>;
97
101
  connect(
98
- policies: Policy[],
102
+ policies: Policies,
99
103
  rpcUrl: string,
100
104
  ): Promise<ConnectReply | ConnectError>;
101
105
  disconnect(): void;
@@ -160,7 +164,7 @@ export type ProviderOptions = {
160
164
  };
161
165
 
162
166
  export type KeychainOptions = IFrameOptions & {
163
- policies?: Policy[];
167
+ policies?: Policies;
164
168
  /** The URL of keychain */
165
169
  url?: string;
166
170
  /** The origin of keychain */
@@ -186,31 +190,6 @@ export type ProfileContextTypeVariant =
186
190
  | "achievements"
187
191
  | "activity";
188
192
 
189
- export type ColorMode = "light" | "dark";
190
-
191
- export type ControllerTheme = {
192
- id: string;
193
- name: string;
194
- icon: string;
195
- cover: ThemeValue<string>;
196
- colorMode: ColorMode;
197
- };
198
-
199
- export type ControllerThemePresets = Record<string, ControllerThemePreset>;
200
-
201
- export type ControllerThemePreset = Omit<ControllerTheme, "colorMode"> & {
202
- colors?: ControllerColors;
203
- };
204
-
205
- export type ControllerColors = {
206
- primary?: ControllerColor;
207
- primaryForeground?: ControllerColor;
208
- };
209
-
210
- export type ControllerColor = ThemeValue<string>;
211
-
212
- export type ThemeValue<T> = T | { dark: T; light: T };
213
-
214
193
  export type Prefund = { address: string; min: string };
215
194
 
216
195
  export type Tokens = {
package/src/utils.ts CHANGED
@@ -2,15 +2,40 @@ import {
2
2
  addAddressPadding,
3
3
  Call,
4
4
  CallData,
5
+ getChecksumAddress,
5
6
  hash,
6
7
  typedData,
7
8
  TypedDataRevision,
8
9
  } from "starknet";
9
- import { Policy } from "./types";
10
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
+ }
11
36
 
12
37
  export function normalizeCalls(calls: Call | Call[]) {
13
- return (Array.isArray(calls) ? calls : [calls]).map((call) => {
38
+ return toArray(calls).map((call) => {
14
39
  return {
15
40
  entrypoint: call.entrypoint,
16
41
  contractAddress: addAddressPadding(call.contractAddress),
@@ -19,29 +44,88 @@ export function normalizeCalls(calls: Call | Call[]) {
19
44
  });
20
45
  }
21
46
 
22
- export function toWasmPolicies(policies: Policy[]): wasm.Policy[] {
23
- return policies.map((richPolicy) => {
24
- if ("target" in richPolicy) {
25
- return {
26
- target: richPolicy.target,
27
- method: richPolicy.method,
28
- };
29
- } else {
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) => {
30
98
  const domainHash = typedData.getStructHash(
31
- richPolicy.types,
99
+ p.types,
32
100
  "StarknetDomain",
33
- richPolicy.domain,
101
+ p.domain,
34
102
  TypedDataRevision.ACTIVE,
35
103
  );
36
104
  const typeHash = typedData.getTypeHash(
37
- richPolicy.types,
38
- richPolicy.primaryType,
105
+ p.types,
106
+ p.primaryType,
39
107
  TypedDataRevision.ACTIVE,
40
108
  );
41
109
 
42
110
  return {
43
111
  scope_hash: hash.computePoseidonHash(domainHash, typeHash),
44
112
  };
45
- }
46
- });
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
+ );
47
131
  }