@cartridge/controller 0.4.0 → 0.5.0-alpha.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 (80) hide show
  1. package/.turbo/turbo-build$colon$deps.log +1 -1
  2. package/dist/account.d.ts +35 -0
  3. package/dist/{device.js → account.js} +14 -33
  4. package/dist/account.js.map +1 -0
  5. package/dist/constants.d.ts +0 -2
  6. package/dist/constants.js +0 -2
  7. package/dist/constants.js.map +1 -1
  8. package/dist/controller.d.ts +7 -14
  9. package/dist/controller.js +41 -53
  10. package/dist/controller.js.map +1 -1
  11. package/dist/errors.d.ts +0 -3
  12. package/dist/errors.js +0 -6
  13. package/dist/errors.js.map +1 -1
  14. package/dist/icon.d.ts +1 -0
  15. package/dist/icon.js +2 -0
  16. package/dist/icon.js.map +1 -0
  17. package/dist/iframe/base.d.ts +0 -1
  18. package/dist/iframe/base.js +21 -11
  19. package/dist/iframe/base.js.map +1 -1
  20. package/dist/iframe/keychain.d.ts +1 -1
  21. package/dist/iframe/keychain.js +1 -4
  22. package/dist/iframe/keychain.js.map +1 -1
  23. package/dist/index.d.ts +4 -0
  24. package/dist/index.js +5 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/provider.d.ts +18 -0
  27. package/dist/provider.js +128 -0
  28. package/dist/provider.js.map +1 -0
  29. package/dist/session/account.d.ts +32 -0
  30. package/dist/session/account.js +31 -0
  31. package/dist/session/account.js.map +1 -0
  32. package/dist/session/backend.d.ts +58 -0
  33. package/dist/session/backend.js +38 -0
  34. package/dist/session/backend.js.map +1 -0
  35. package/dist/session/index.d.ts +5 -0
  36. package/dist/session/index.js +6 -0
  37. package/dist/session/index.js.map +1 -0
  38. package/dist/session/provider.d.ts +25 -0
  39. package/dist/session/provider.js +89 -0
  40. package/dist/session/provider.js.map +1 -0
  41. package/dist/telegram/backend.d.ts +30 -0
  42. package/dist/telegram/backend.js +39 -0
  43. package/dist/telegram/backend.js.map +1 -0
  44. package/dist/telegram/provider.d.ts +19 -0
  45. package/dist/telegram/provider.js +78 -0
  46. package/dist/telegram/provider.js.map +1 -0
  47. package/dist/types.d.ts +12 -31
  48. package/dist/types.js.map +1 -1
  49. package/dist/utils.d.ts +0 -2
  50. package/dist/utils.js +0 -4
  51. package/dist/utils.js.map +1 -1
  52. package/package.json +11 -9
  53. package/src/{device.ts → account.ts} +23 -66
  54. package/src/constants.ts +0 -2
  55. package/src/controller.ts +50 -79
  56. package/src/errors.ts +0 -8
  57. package/src/icon.ts +2 -0
  58. package/src/iframe/base.ts +27 -12
  59. package/src/iframe/keychain.ts +2 -12
  60. package/src/index.ts +5 -0
  61. package/src/provider.ts +181 -0
  62. package/src/session/account.ts +65 -0
  63. package/src/session/backend.ts +73 -0
  64. package/src/session/index.ts +6 -0
  65. package/src/session/provider.ts +141 -0
  66. package/src/telegram/backend.ts +43 -0
  67. package/src/telegram/provider.ts +131 -0
  68. package/src/types.ts +18 -66
  69. package/src/utils.ts +0 -10
  70. package/tsconfig.tsbuildinfo +1 -1
  71. package/dist/device.d.ts +0 -46
  72. package/dist/device.js.map +0 -1
  73. package/dist/session.d.ts +0 -40
  74. package/dist/session.js +0 -44
  75. package/dist/session.js.map +0 -1
  76. package/dist/signer.d.ts +0 -53
  77. package/dist/signer.js +0 -83
  78. package/dist/signer.js.map +0 -1
  79. package/src/session.ts +0 -92
  80. package/src/signer.ts +0 -144
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { constants, Abi, Call, InvocationsDetails, TypedData, InvokeFunctionResponse, Signature, EstimateFeeDetails, EstimateFee, DeclareContractPayload, BigNumberish, InvocationsSignerDetails, DeployAccountSignerDetails, DeclareSignerDetails } from "starknet";
1
+ import { constants, BigNumberish, Call, AllowArray } from "starknet";
2
+ import { AddInvokeTransactionResult, Signature, TypedData } from "@starknet-io/types-js";
2
3
  import { KeychainIFrame, ProfileIFrame } from "./iframe";
3
4
  import wasm from "@cartridge/account-wasm/controller";
4
5
  export type Session = {
@@ -36,7 +37,7 @@ export type ConnectReply = {
36
37
  address: string;
37
38
  policies: Policy[];
38
39
  };
39
- export type ExecuteReply = (InvokeFunctionResponse & {
40
+ export type ExecuteReply = (AddInvokeTransactionResult & {
40
41
  code: ResponseCodes.SUCCESS;
41
42
  }) | {
42
43
  code: ResponseCodes.USER_INTERACTION_REQUIRED;
@@ -58,24 +59,18 @@ type CartridgeID = string;
58
59
  export type ControllerAccounts = Record<ContractAddress, CartridgeID>;
59
60
  export interface Keychain {
60
61
  probe(rpcUrl: string): Promise<ProbeReply | ConnectError>;
61
- connect(policies: Policy[], rpcUrl: string): Promise<ConnectReply | ConnectError>;
62
+ connect(rpcUrl: string): Promise<ConnectReply | ConnectError>;
62
63
  disconnect(): void;
63
64
  reset(): void;
64
65
  revoke(origin: string): void;
65
- deploy(): Promise<DeployReply | ConnectError>;
66
- estimateDeclareFee(payload: DeclareContractPayload, details?: EstimateFeeDetails): Promise<EstimateFee>;
67
- estimateInvokeFee(calls: Call | Call[], estimateFeeDetails?: EstimateFeeDetails): Promise<EstimateFee>;
68
- execute(calls: Call | Call[], abis?: Abi[], transactionsDetail?: InvocationsDetails, sync?: boolean, paymaster?: PaymasterOptions, error?: ControllerError): Promise<ExecuteReply | ConnectError>;
66
+ execute(calls: AllowArray<Call>, sync?: boolean, error?: ControllerError): Promise<ExecuteReply | ConnectError>;
67
+ signMessage(typedData: TypedData): Promise<Signature | ConnectError>;
69
68
  logout(): Promise<void>;
70
69
  openSettings(): Promise<void | ConnectError>;
71
70
  session(): Promise<Session>;
72
71
  sessions(): Promise<{
73
72
  [key: string]: Session;
74
73
  }>;
75
- signMessage(typedData: TypedData, account: string): Promise<Signature | ConnectError>;
76
- signTransaction(transactions: Call[], transactionsDetail: InvocationsSignerDetails, abis?: Abi[]): Promise<Signature>;
77
- signDeployAccountTransaction(transaction: DeployAccountSignerDetails): Promise<Signature>;
78
- signDeclareTransaction(transaction: DeclareSignerDetails): Promise<Signature>;
79
74
  delegateAccount(): string;
80
75
  username(): string;
81
76
  fetchControllers(contractAddresses: string[]): Promise<ControllerAccounts>;
@@ -90,7 +85,7 @@ export interface Modal {
90
85
  /**
91
86
  * Options for configuring the controller
92
87
  */
93
- export type ControllerOptions = KeychainOptions & ProfileOptions;
88
+ export type ControllerOptions = ProviderOptions & KeychainOptions & ProfileOptions;
94
89
  export type TokenOptions = {
95
90
  tokens: Tokens;
96
91
  };
@@ -107,16 +102,16 @@ export type IFrameOptions = {
107
102
  presets?: ControllerThemePresets;
108
103
  };
109
104
  };
105
+ export type ProviderOptions = {
106
+ /** The URL of the RPC */
107
+ rpc: string;
108
+ };
110
109
  export type KeychainOptions = IFrameOptions & {
111
110
  policies?: Policy[];
112
111
  /** The URL of keychain */
113
112
  url?: string;
114
- /** The URL of the RPC */
115
- rpc?: string;
116
113
  /** The origin of keychain */
117
114
  origin?: string;
118
- /** Paymaster options for transaction fee management */
119
- paymaster?: PaymasterOptions;
120
115
  /** Propagate transaction errors back to caller instead of showing modal */
121
116
  propagateSessionErrors?: boolean;
122
117
  };
@@ -128,21 +123,7 @@ export type ProfileOptions = IFrameOptions & {
128
123
  /** The tokens to be listed on Inventory modal */
129
124
  tokens?: Tokens;
130
125
  };
131
- export type ProfileContextTypeVariant = "quest" | "inventory" | "history";
132
- /**
133
- * Options for configuring a paymaster
134
- */
135
- export type PaymasterOptions = {
136
- /**
137
- * The address of the account paying for the transaction.
138
- * This should be a valid Starknet address or "ANY_CALLER" short string.
139
- */
140
- caller: string;
141
- /**
142
- * The URL of the paymaster. Currently not used.
143
- */
144
- url?: string;
145
- };
126
+ export type ProfileContextTypeVariant = "trophies" | "inventory" | "activity";
146
127
  export type ColorMode = "light" | "dark";
147
128
  export type ControllerTheme = {
148
129
  id: string;
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAkCA,MAAM,CAAN,IAAY,aAMX;AAND,WAAY,aAAa;IACvB,oCAAmB,CAAA;IACnB,gDAA+B,CAAA;IAC/B,gCAAe,CAAA;IACf,sCAAqB,CAAA;IACrB,wEAAuD,CAAA;AACzD,CAAC,EANW,aAAa,KAAb,aAAa,QAMxB"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAwBA,MAAM,CAAN,IAAY,aAMX;AAND,WAAY,aAAa;IACvB,oCAAmB,CAAA;IACnB,gDAA+B,CAAA;IAC/B,gCAAe,CAAA;IACf,sCAAqB,CAAA;IACrB,wEAAuD,CAAA;AACzD,CAAC,EANW,aAAa,KAAb,aAAa,QAMxB"}
package/dist/utils.d.ts CHANGED
@@ -1,6 +1,4 @@
1
- import { Policy } from "./types";
2
1
  import { Call } from "starknet";
3
- export declare function diff(a: Policy[], b: Policy[]): Policy[];
4
2
  export declare function normalizeCalls(calls: Call | Call[]): {
5
3
  entrypoint: string;
6
4
  contractAddress: string;
package/dist/utils.js CHANGED
@@ -1,8 +1,4 @@
1
- import equal from "fast-deep-equal";
2
1
  import { addAddressPadding, CallData } from "starknet";
3
- export function diff(a, b) {
4
- return a.reduce((prev, policyA) => b.some((policyB) => equal(policyB, policyA)) ? prev : [...prev, policyA], []);
5
- }
6
2
  export function normalizeCalls(calls) {
7
3
  return (Array.isArray(calls) ? calls : [calls]).map((call) => {
8
4
  return {
package/dist/utils.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,iBAAiB,CAAC;AAEpC,OAAO,EAAE,iBAAiB,EAAQ,QAAQ,EAAE,MAAM,UAAU,CAAC;AAE7D,MAAM,UAAU,IAAI,CAAC,CAAW,EAAE,CAAW;IAC3C,OAAO,CAAC,CAAC,MAAM,CACb,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAChB,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,EAC1E,EAAc,CACf,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAoB;IACjD,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAC3D,OAAO;YACL,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,eAAe,EAAE,iBAAiB,CAAC,IAAI,CAAC,eAAe,CAAC;YACxD,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;SACxC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAQ,QAAQ,EAAE,MAAM,UAAU,CAAC;AAE7D,MAAM,UAAU,cAAc,CAAC,KAAoB;IACjD,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAC3D,OAAO;YACL,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,eAAe,EAAE,iBAAiB,CAAC,IAAI,CAAC,eAAe,CAAC;YACxD,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;SACxC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,37 +1,39 @@
1
1
  {
2
2
  "name": "@cartridge/controller",
3
- "version": "0.4.0",
3
+ "version": "0.5.0-alpha.0",
4
4
  "description": "Cartridge Controller",
5
- "module": "dist/controller.js",
6
- "types": "dist/controller.d.ts",
5
+ "module": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
7
  "type": "module",
8
8
  "exports": {
9
- ".": "./dist/controller.js",
10
- "./session": "./dist/session.js"
9
+ ".": "./dist/index.js",
10
+ "./session": "./dist/session/index.js"
11
11
  },
12
12
  "typesVersions": {
13
13
  "*": {
14
14
  ".": [
15
- "./dist/controller.d.ts"
15
+ "./dist/index.d.ts"
16
16
  ],
17
17
  "session": [
18
- "./dist/session.d.ts"
18
+ "./dist/session/index.d.ts"
19
19
  ]
20
20
  }
21
21
  },
22
22
  "dependencies": {
23
23
  "@cartridge/penpal": "^6.2.3",
24
+ "@starknet-io/types-js": "^0.7.7",
24
25
  "base64url": "^3.0.1",
25
26
  "cbor-x": "^1.5.0",
26
27
  "fast-deep-equal": "^3.1.3",
27
28
  "query-string": "^7.1.1",
28
29
  "starknet": "^6.11.0",
29
- "@cartridge/account-wasm": "^0.4.0"
30
+ "@telegram-apps/sdk": "^2.4.0",
31
+ "@cartridge/account-wasm": "^0.5.0-alpha.0"
30
32
  },
31
33
  "devDependencies": {
32
34
  "@types/node": "^20.6.0",
33
35
  "typescript": "^5.4.5",
34
- "@cartridge/tsconfig": "^0.4.0"
36
+ "@cartridge/tsconfig": "^0.5.0-alpha.0"
35
37
  },
36
38
  "scripts": {
37
39
  "build:deps": "tsc",
@@ -1,17 +1,13 @@
1
1
  import {
2
- Account,
3
- Abi,
4
- Call,
5
- EstimateFeeDetails,
6
- Signature,
7
2
  InvokeFunctionResponse,
8
- EstimateFee,
9
- DeclareContractPayload,
10
- RpcProvider,
11
3
  TypedData,
12
- InvocationsDetails,
4
+ WalletAccount,
5
+ Call,
6
+ AllowArray,
13
7
  } from "starknet";
14
8
 
9
+ import { SPEC } from "@starknet-io/types-js";
10
+
15
11
  import {
16
12
  ConnectError,
17
13
  Keychain,
@@ -19,62 +15,30 @@ import {
19
15
  Modal,
20
16
  ResponseCodes,
21
17
  } from "./types";
22
- import { Signer } from "./signer";
23
18
  import { AsyncMethodReturns } from "@cartridge/penpal";
19
+ import BaseProvider from "./provider";
24
20
 
25
- class DeviceAccount extends Account {
21
+ class ControllerAccount extends WalletAccount {
26
22
  address: string;
27
23
  private keychain: AsyncMethodReturns<Keychain>;
28
24
  private modal: Modal;
29
25
  private options?: KeychainOptions;
30
26
 
31
27
  constructor(
32
- rpcUrl: string,
28
+ provider: BaseProvider,
33
29
  address: string,
34
30
  keychain: AsyncMethodReturns<Keychain>,
35
31
  options: KeychainOptions,
36
32
  modal: Modal,
37
33
  ) {
38
- super(
39
- new RpcProvider({ nodeUrl: rpcUrl }),
40
- address,
41
- new Signer(keychain, modal),
42
- );
34
+ super({ nodeUrl: provider.rpc.toString() }, provider);
35
+
43
36
  this.address = address;
44
37
  this.keychain = keychain;
45
38
  this.options = options;
46
39
  this.modal = modal;
47
40
  }
48
41
 
49
- /**
50
- * Estimate Fee for a method on starknet
51
- *
52
- * @param calls the invocation object containing:
53
- * - contractAddress - the address of the contract
54
- * - entrypoint - the entrypoint of the contract
55
- * - calldata - (defaults to []) the calldata
56
- * - signature - (defaults to []) the signature
57
- *
58
- * @returns response from addTransaction
59
- */
60
- async estimateInvokeFee(
61
- calls: Call | Call[],
62
- details?: EstimateFeeDetails,
63
- ): Promise<EstimateFee> {
64
- return this.keychain.estimateInvokeFee(calls, {
65
- ...details,
66
- });
67
- }
68
-
69
- async estimateDeclareFee(
70
- payload: DeclareContractPayload,
71
- details?: EstimateFeeDetails,
72
- ): Promise<EstimateFee> {
73
- return this.keychain.estimateDeclareFee(payload, {
74
- ...details,
75
- });
76
- }
77
-
78
42
  /**
79
43
  * Invoke execute function in account contract
80
44
  *
@@ -87,20 +51,11 @@ class DeviceAccount extends Account {
87
51
  *
88
52
  * @returns response from addTransaction
89
53
  */
90
- // @ts-expect-error TODO: fix overload type mismatch
91
- async execute(
92
- calls: Call | Call[],
93
- abis?: Abi[],
94
- transactionsDetail: InvocationsDetails = {},
95
- ): Promise<InvokeFunctionResponse> {
54
+ async execute(calls: AllowArray<Call>): Promise<InvokeFunctionResponse> {
55
+ calls = Array.isArray(calls) ? calls : [calls];
56
+
96
57
  return new Promise(async (resolve, reject) => {
97
- const sessionExecute = await this.keychain.execute(
98
- calls,
99
- abis,
100
- transactionsDetail,
101
- false,
102
- this.options?.paymaster,
103
- );
58
+ const sessionExecute = await this.keychain.execute(calls, false);
104
59
 
105
60
  // Session call succeeded
106
61
  if (sessionExecute.code === ResponseCodes.SUCCESS) {
@@ -119,10 +74,7 @@ class DeviceAccount extends Account {
119
74
  this.modal.open();
120
75
  const manualExecute = await this.keychain.execute(
121
76
  calls,
122
- abis,
123
- transactionsDetail,
124
77
  true,
125
- this.options?.paymaster,
126
78
  (sessionExecute as ConnectError).error,
127
79
  );
128
80
 
@@ -146,12 +98,17 @@ class DeviceAccount extends Account {
146
98
  * @returns the signature of the JSON object
147
99
  * @throws {Error} if the JSON object is not a valid JSON
148
100
  */
149
- async signMessage(typedData: TypedData): Promise<Signature> {
101
+ async signMessage(typedData: TypedData): Promise<SPEC.SIGNATURE> {
150
102
  try {
151
103
  this.modal.open();
152
- const res = await this.keychain.signMessage(typedData, this.address);
104
+ const res = await this.keychain.signMessage(typedData);
153
105
  this.modal.close();
154
- return res as Signature;
106
+
107
+ if ("code" in res) {
108
+ throw res;
109
+ }
110
+
111
+ return res;
155
112
  } catch (e) {
156
113
  console.error(e);
157
114
  throw e;
@@ -159,4 +116,4 @@ class DeviceAccount extends Account {
159
116
  }
160
117
  }
161
118
 
162
- export default DeviceAccount;
119
+ export default ControllerAccount;
package/src/constants.ts CHANGED
@@ -1,4 +1,2 @@
1
1
  export const KEYCHAIN_URL = "https://x.cartridge.gg";
2
2
  export const PROFILE_URL = "https://profile.cartridge.gg";
3
- export const RPC_SEPOLIA = "https://api.cartridge.gg/x/starknet/sepolia";
4
- export const RPC_MAINNET = "https://api.cartridge.gg/x/starknet/mainnet";
package/src/controller.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { AccountInterface, addAddressPadding } from "starknet";
2
1
  import { AsyncMethodReturns } from "@cartridge/penpal";
3
2
 
4
- import DeviceAccount from "./device";
3
+ import ControllerAccount from "./account";
4
+ import { KeychainIFrame, ProfileIFrame } from "./iframe";
5
+ import { NotReadyToConnect } from "./errors";
5
6
  import {
6
7
  Keychain,
7
8
  Policy,
@@ -14,35 +15,21 @@ import {
14
15
  IFrames,
15
16
  ProfileContextTypeVariant,
16
17
  } from "./types";
17
- import { KeychainIFrame, ProfileIFrame } from "./iframe";
18
- import { NotReadyToConnect, ProfileNotReady } from "./errors";
19
- import { RPC_SEPOLIA } from "./constants";
20
-
21
- export * from "./errors";
22
- export * from "./types";
23
- export { defaultPresets } from "./presets";
18
+ import BaseProvider from "./provider";
24
19
 
25
- export default class Controller {
26
- private policies: Policy[];
20
+ export default class ControllerProvider extends BaseProvider {
27
21
  private keychain?: AsyncMethodReturns<Keychain>;
28
22
  private profile?: AsyncMethodReturns<Profile>;
29
23
  private options: ControllerOptions;
30
24
  private iframes: IFrames;
31
- public rpc: URL;
32
- public account?: AccountInterface;
33
-
34
- constructor({
35
- policies,
36
- url,
37
- rpc,
38
- paymaster,
39
- ...options
40
- }: ControllerOptions = {}) {
25
+
26
+ constructor(options: ControllerOptions) {
27
+ const { rpc } = options;
28
+ super({ rpc });
29
+
41
30
  this.iframes = {
42
31
  keychain: new KeychainIFrame({
43
32
  ...options,
44
- url,
45
- paymaster,
46
33
  onClose: this.keychain?.reset,
47
34
  onConnect: (keychain) => {
48
35
  this.keychain = keychain;
@@ -50,37 +37,11 @@ export default class Controller {
50
37
  }),
51
38
  };
52
39
 
53
- this.rpc = new URL(rpc || RPC_SEPOLIA);
54
-
55
- // TODO: remove this on the next major breaking change. pass everthing by url
56
- this.policies =
57
- policies?.map((policy) => ({
58
- ...policy,
59
- target: addAddressPadding(policy.target),
60
- })) || [];
61
-
62
40
  this.options = options;
63
- }
64
41
 
65
- async openSettings() {
66
- if (!this.keychain || !this.iframes.keychain) {
67
- console.error(new NotReadyToConnect().message);
68
- return null;
69
- }
70
- this.iframes.keychain.open();
71
- const res = await this.keychain.openSettings();
72
- this.iframes.keychain.close();
73
- if (res && (res as ConnectError).code === ResponseCodes.NOT_CONNECTED) {
74
- return false;
42
+ if (typeof window !== "undefined") {
43
+ (window as any).starknet_controller = this;
75
44
  }
76
- return true;
77
- }
78
-
79
- ready() {
80
- return this.probe().then(
81
- (res) => !!res,
82
- () => false,
83
- );
84
45
  }
85
46
 
86
47
  async probe() {
@@ -89,23 +50,22 @@ export default class Controller {
89
50
 
90
51
  if (!this.keychain) {
91
52
  console.error(new NotReadyToConnect().message);
92
- return null;
53
+ return;
93
54
  }
94
55
 
95
56
  const response = (await this.keychain.probe(
96
57
  this.rpc.toString(),
97
58
  )) as ProbeReply;
98
59
 
99
- this.account = new DeviceAccount(
100
- this.rpc.toString(),
60
+ this.account = new ControllerAccount(
61
+ this,
101
62
  response.address,
102
63
  this.keychain,
103
64
  this.options,
104
65
  this.iframes.keychain,
105
- ) as AccountInterface;
66
+ );
106
67
  } catch (e) {
107
- console.log(e);
108
- console.error(new NotReadyToConnect().message);
68
+ console.error(e);
109
69
  return;
110
70
  }
111
71
 
@@ -118,7 +78,7 @@ export default class Controller {
118
78
  this.iframes.profile = new ProfileIFrame({
119
79
  profileUrl: this.options.profileUrl,
120
80
  indexerUrl: this.options.indexerUrl,
121
- address: this.account.address,
81
+ address: this.account?.address,
122
82
  username,
123
83
  rpcUrl: this.rpc.toString(),
124
84
  tokens: this.options.tokens,
@@ -128,7 +88,7 @@ export default class Controller {
128
88
  });
129
89
  }
130
90
 
131
- return !!this.account;
91
+ return this.account;
132
92
  }
133
93
 
134
94
  async connect() {
@@ -151,22 +111,19 @@ export default class Controller {
151
111
  this.iframes.keychain.open();
152
112
 
153
113
  try {
154
- let response = await this.keychain.connect(
155
- this.policies,
156
- this.rpc.toString(),
157
- );
114
+ let response = await this.keychain.connect(this.rpc.toString());
158
115
  if (response.code !== ResponseCodes.SUCCESS) {
159
116
  throw new Error(response.message);
160
117
  }
161
118
 
162
119
  response = response as ConnectReply;
163
- this.account = new DeviceAccount(
164
- this.rpc.toString(),
120
+ this.account = new ControllerAccount(
121
+ this,
165
122
  response.address,
166
123
  this.keychain,
167
124
  this.options,
168
125
  this.iframes.keychain,
169
- ) as AccountInterface;
126
+ );
170
127
 
171
128
  return this.account;
172
129
  } catch (e) {
@@ -176,13 +133,30 @@ export default class Controller {
176
133
  }
177
134
  }
178
135
 
136
+ async disconnect() {
137
+ if (!this.keychain) {
138
+ console.error(new NotReadyToConnect().message);
139
+ return;
140
+ }
141
+
142
+ if (!!document.hasStorageAccess) {
143
+ const ok = await document.hasStorageAccess();
144
+ if (!ok) {
145
+ await document.requestStorageAccess();
146
+ }
147
+ }
148
+
149
+ this.account = undefined;
150
+ return this.keychain.disconnect();
151
+ }
152
+
179
153
  openProfile(tab: ProfileContextTypeVariant = "inventory") {
180
154
  if (!this.options.indexerUrl) {
181
155
  console.error("`indexerUrl` option is required to open profile");
182
156
  return;
183
157
  }
184
158
  if (!this.profile || !this.iframes.profile) {
185
- console.error(new ProfileNotReady().message);
159
+ console.error("Profile is not ready");
186
160
  return;
187
161
  }
188
162
 
@@ -190,21 +164,18 @@ export default class Controller {
190
164
  this.iframes.profile.open();
191
165
  }
192
166
 
193
- async disconnect() {
194
- if (!this.keychain) {
167
+ async openSettings() {
168
+ if (!this.keychain || !this.iframes.keychain) {
195
169
  console.error(new NotReadyToConnect().message);
196
- return;
170
+ return null;
197
171
  }
198
-
199
- if (!!document.hasStorageAccess) {
200
- const ok = await document.hasStorageAccess();
201
- if (!ok) {
202
- await document.requestStorageAccess();
203
- }
172
+ this.iframes.keychain.open();
173
+ const res = await this.keychain.openSettings();
174
+ this.iframes.keychain.close();
175
+ if (res && (res as ConnectError).code === ResponseCodes.NOT_CONNECTED) {
176
+ return false;
204
177
  }
205
-
206
- this.account = undefined;
207
- return this.keychain.disconnect();
178
+ return true;
208
179
  }
209
180
 
210
181
  revoke(origin: string, _policy: Policy[]) {
package/src/errors.ts CHANGED
@@ -5,11 +5,3 @@ export class NotReadyToConnect extends Error {
5
5
  Object.setPrototypeOf(this, NotReadyToConnect.prototype);
6
6
  }
7
7
  }
8
-
9
- export class ProfileNotReady extends Error {
10
- constructor() {
11
- super("Profile is not ready");
12
-
13
- Object.setPrototypeOf(this, NotReadyToConnect.prototype);
14
- }
15
- }
package/src/icon.ts ADDED
@@ -0,0 +1,2 @@
1
+ export const icon =
2
+ "data:image/png;base64,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";
@@ -63,6 +63,7 @@ export class IFrame<CallSender extends {}> implements Modal {
63
63
  }
64
64
 
65
65
  const container = document.createElement("div");
66
+ container.id = "controller";
66
67
  container.style.position = "fixed";
67
68
  container.style.height = "100%";
68
69
  container.style.width = "100%";
@@ -89,13 +90,32 @@ export class IFrame<CallSender extends {}> implements Modal {
89
90
  this.resize();
90
91
  window.addEventListener("resize", () => this.resize());
91
92
 
92
- if (
93
- document.readyState === "complete" ||
94
- document.readyState === "interactive"
95
- ) {
96
- this.append();
97
- } else {
98
- document.addEventListener("DOMContentLoaded", this.append);
93
+ const observer = new MutationObserver(() => {
94
+ const existingController = document.getElementById("controller");
95
+ if (document.body) {
96
+ if (
97
+ (id === "controller-keychain" && !existingController) ||
98
+ id === "controller-profile"
99
+ ) {
100
+ document.body.appendChild(container);
101
+ observer.disconnect();
102
+ }
103
+ }
104
+ });
105
+
106
+ observer.observe(document.documentElement, {
107
+ childList: true,
108
+ subtree: true,
109
+ });
110
+
111
+ const existingController = document.getElementById("controller");
112
+ if (document.body) {
113
+ if (
114
+ (id === "controller-keychain" && !existingController) ||
115
+ id === "controller-profile"
116
+ ) {
117
+ document.body.appendChild(container);
118
+ }
99
119
  }
100
120
 
101
121
  this.onClose = onClose;
@@ -119,11 +139,6 @@ export class IFrame<CallSender extends {}> implements Modal {
119
139
  this.container.style.opacity = "0";
120
140
  }
121
141
 
122
- private append() {
123
- if (!this.container) return;
124
- document.body.appendChild(this.container);
125
- }
126
-
127
142
  private resize() {
128
143
  if (!this.iframe) return;
129
144
  if (window.innerWidth < 768) {