@cartridge/controller 0.7.14-alpha.3 → 0.9.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.
- package/.turbo/turbo-build$colon$deps.log +51 -49
- package/.turbo/turbo-build.log +47 -45
- package/dist/__tests__/setup.d.ts +0 -0
- package/dist/account.d.ts +2 -3
- package/dist/controller.d.ts +7 -5
- package/dist/iframe/base.d.ts +1 -0
- package/dist/iframe/keychain.d.ts +4 -2
- package/dist/iframe/profile.d.ts +1 -1
- package/dist/index.js +885 -4955
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +3 -3
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +2 -2
- package/dist/node/index.d.ts +2 -2
- package/dist/node/index.js +3 -3
- package/dist/node/index.js.map +1 -1
- package/dist/{provider-sFJ4sl5V.js → provider-B9Ikz5hr.js} +98 -75
- package/dist/provider-B9Ikz5hr.js.map +1 -0
- package/dist/provider.d.ts +1 -1
- package/dist/session/account.d.ts +2 -2
- package/dist/session.js +10 -10
- package/dist/session.js.map +1 -1
- package/dist/stats.html +1 -1
- package/dist/types.d.ts +6 -5
- package/dist/utils.d.ts +3 -3
- package/dist/wallets/bridge.d.ts +2 -2
- package/dist/wallets/index.d.ts +0 -2
- package/dist/wallets/metamask/index.d.ts +2 -2
- package/dist/wallets/rabby/index.d.ts +1 -1
- package/dist/wallets/types.d.ts +2 -2
- package/jest.config.ts +2 -0
- package/package.json +8 -8
- package/src/__tests__/controllerDefaults.test.ts +80 -0
- package/src/__tests__/parseChainId.test.ts +0 -6
- package/src/__tests__/setup.ts +29 -0
- package/src/account.ts +5 -8
- package/src/controller.ts +98 -22
- package/src/iframe/base.ts +19 -1
- package/src/iframe/keychain.ts +14 -2
- package/src/iframe/profile.ts +0 -11
- package/src/node/account.ts +3 -3
- package/src/provider.ts +9 -9
- package/src/session/account.ts +3 -4
- package/src/types.ts +7 -5
- package/src/utils.ts +49 -13
- package/src/wallets/bridge.ts +8 -19
- package/src/wallets/index.ts +0 -2
- package/src/wallets/metamask/index.ts +18 -13
- package/src/wallets/rabby/index.ts +14 -10
- package/src/wallets/types.ts +5 -2
- package/tsconfig.json +3 -2
- package/.turbo/turbo-format$colon$check.log +0 -7
- package/.turbo/turbo-format.log +0 -42
- package/dist/provider-sFJ4sl5V.js.map +0 -1
- package/dist/wallets/turnkey/index.d.ts +0 -20
- package/dist/wallets/wallet-connect/index.d.ts +0 -19
- package/src/wallets/turnkey/index.ts +0 -195
- package/src/wallets/wallet-connect/index.ts +0 -206
package/dist/types.d.ts
CHANGED
|
@@ -122,8 +122,8 @@ export type Chain = {
|
|
|
122
122
|
rpcUrl: string;
|
|
123
123
|
};
|
|
124
124
|
export type ProviderOptions = {
|
|
125
|
-
defaultChainId
|
|
126
|
-
chains
|
|
125
|
+
defaultChainId?: ChainId;
|
|
126
|
+
chains?: Chain[];
|
|
127
127
|
};
|
|
128
128
|
export type KeychainOptions = IFrameOptions & {
|
|
129
129
|
policies?: SessionPolicies;
|
|
@@ -137,6 +137,8 @@ export type KeychainOptions = IFrameOptions & {
|
|
|
137
137
|
feeSource?: FeeSource;
|
|
138
138
|
/** Signup options (the order of the options is reflected in the UI. It's recommended to group socials and wallets together ) */
|
|
139
139
|
signupOptions?: AuthOptions;
|
|
140
|
+
/** When true, manually provided policies will override preset policies. Default is false. */
|
|
141
|
+
shouldOverridePresetPolicies?: boolean;
|
|
140
142
|
};
|
|
141
143
|
export type ProfileOptions = IFrameOptions & {
|
|
142
144
|
/** The URL of profile. Mainly for internal development purpose */
|
|
@@ -147,11 +149,10 @@ export type ProfileOptions = IFrameOptions & {
|
|
|
147
149
|
namespace?: string;
|
|
148
150
|
/** The tokens to be listed on Inventory modal */
|
|
149
151
|
tokens?: Tokens;
|
|
150
|
-
/** The policies to use for the profile */
|
|
151
|
-
policies?: SessionPolicies;
|
|
152
152
|
};
|
|
153
153
|
export type ProfileContextTypeVariant = "inventory" | "trophies" | "achievements" | "leaderboard" | "activity";
|
|
154
|
+
export type Token = "eth" | "strk" | "lords" | "usdc" | "usdt";
|
|
154
155
|
export type Tokens = {
|
|
155
|
-
erc20?:
|
|
156
|
+
erc20?: Token[];
|
|
156
157
|
};
|
|
157
158
|
export {};
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Call } from 'starknet';
|
|
2
|
-
import {
|
|
2
|
+
import { Policy } from '@cartridge/controller-wasm/controller';
|
|
3
3
|
import { Policies, SessionPolicies } from '@cartridge/presets';
|
|
4
4
|
import { ChainId } from '@starknet-io/types-js';
|
|
5
5
|
import { ParsedSessionPolicies } from './policies';
|
|
@@ -9,7 +9,7 @@ export declare function normalizeCalls(calls: Call | Call[]): {
|
|
|
9
9
|
calldata: import('starknet').HexCalldata;
|
|
10
10
|
}[];
|
|
11
11
|
export declare function toSessionPolicies(policies: Policies): SessionPolicies;
|
|
12
|
-
export declare function toWasmPolicies(policies: ParsedSessionPolicies):
|
|
12
|
+
export declare function toWasmPolicies(policies: ParsedSessionPolicies): Policy[];
|
|
13
13
|
export declare function toArray<T>(val: T | T[]): T[];
|
|
14
14
|
export declare function humanizeString(str: string): string;
|
|
15
|
-
export declare function parseChainId(url: URL):
|
|
15
|
+
export declare function parseChainId(url: URL): ChainId;
|
package/dist/wallets/bridge.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare class WalletBridge {
|
|
|
4
4
|
constructor();
|
|
5
5
|
getIFrameMethods(): {
|
|
6
6
|
externalDetectWallets: (_origin: string) => () => Promise<ExternalWallet[]>;
|
|
7
|
-
externalConnectWallet: (_origin: string) => (type: ExternalWalletType
|
|
7
|
+
externalConnectWallet: (_origin: string) => (type: ExternalWalletType) => Promise<ExternalWalletResponse<unknown>>;
|
|
8
8
|
externalSignMessage: (_origin: string) => (identifier: ExternalWalletType | string, message: string) => Promise<ExternalWalletResponse<unknown>>;
|
|
9
9
|
externalSignTypedData: (_origin: string) => (identifier: ExternalWalletType | string, data: any) => Promise<ExternalWalletResponse<unknown>>;
|
|
10
10
|
externalSendTransaction: (_origin: string) => (identifier: ExternalWalletType | string, txn: any) => Promise<ExternalWalletResponse<unknown>>;
|
|
@@ -13,7 +13,7 @@ export declare class WalletBridge {
|
|
|
13
13
|
detectWallets(): Promise<ExternalWallet[]>;
|
|
14
14
|
private getWalletAdapterByType;
|
|
15
15
|
private handleError;
|
|
16
|
-
connectWallet(type: ExternalWalletType
|
|
16
|
+
connectWallet(type: ExternalWalletType): Promise<ExternalWalletResponse>;
|
|
17
17
|
private getConnectedWalletAdapter;
|
|
18
18
|
signMessage(identifier: ExternalWalletType | string, message: string): Promise<ExternalWalletResponse>;
|
|
19
19
|
signTypedData(identifier: ExternalWalletType | string, data: any): Promise<ExternalWalletResponse>;
|
package/dist/wallets/index.d.ts
CHANGED
|
@@ -9,10 +9,10 @@ export declare class MetaMaskWallet implements WalletAdapter {
|
|
|
9
9
|
constructor();
|
|
10
10
|
isAvailable(): boolean;
|
|
11
11
|
getInfo(): ExternalWallet;
|
|
12
|
-
connect(
|
|
12
|
+
connect(): Promise<ExternalWalletResponse<any>>;
|
|
13
13
|
getConnectedAccounts(): string[];
|
|
14
14
|
signTransaction(transaction: any): Promise<ExternalWalletResponse<any>>;
|
|
15
|
-
signMessage(message: string): Promise<ExternalWalletResponse<any>>;
|
|
15
|
+
signMessage(message: string, address?: string): Promise<ExternalWalletResponse<any>>;
|
|
16
16
|
signTypedData(data: any): Promise<ExternalWalletResponse<any>>;
|
|
17
17
|
sendTransaction(_txn: any): Promise<ExternalWalletResponse<any>>;
|
|
18
18
|
switchChain(chainId: string): Promise<boolean>;
|
|
@@ -12,7 +12,7 @@ export declare class RabbyWallet implements WalletAdapter {
|
|
|
12
12
|
connect(address?: string): Promise<ExternalWalletResponse<any>>;
|
|
13
13
|
getConnectedAccounts(): string[];
|
|
14
14
|
signTransaction(transaction: any): Promise<ExternalWalletResponse<any>>;
|
|
15
|
-
signMessage(message: `0x${string}
|
|
15
|
+
signMessage(message: `0x${string}`, address?: string): Promise<ExternalWalletResponse<any>>;
|
|
16
16
|
signTypedData(data: any): Promise<ExternalWalletResponse<any>>;
|
|
17
17
|
sendTransaction(_txn: any): Promise<ExternalWalletResponse<any>>;
|
|
18
18
|
switchChain(chainId: string): Promise<boolean>;
|
package/dist/wallets/types.d.ts
CHANGED
|
@@ -22,8 +22,8 @@ export interface WalletAdapter {
|
|
|
22
22
|
isAvailable(): boolean;
|
|
23
23
|
getInfo(): ExternalWallet;
|
|
24
24
|
getConnectedAccounts(): string[];
|
|
25
|
-
connect(
|
|
26
|
-
signMessage?(message: string): Promise<ExternalWalletResponse<any>>;
|
|
25
|
+
connect(): Promise<ExternalWalletResponse<any>>;
|
|
26
|
+
signMessage?(message: string, address?: string): Promise<ExternalWalletResponse<any>>;
|
|
27
27
|
signTypedData?(data: any): Promise<ExternalWalletResponse<any>>;
|
|
28
28
|
sendTransaction(tx: any): Promise<ExternalWalletResponse<any>>;
|
|
29
29
|
getBalance(tokenAddress?: string): Promise<ExternalWalletResponse<any>>;
|
package/jest.config.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cartridge/controller",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Cartridge Controller",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,23 +24,23 @@
|
|
|
24
24
|
"@metamask/sdk": "^0.32.1",
|
|
25
25
|
"@solana/web3.js": "^1.98.0",
|
|
26
26
|
"open": "^10.1.0",
|
|
27
|
-
"starknet": "^6.
|
|
27
|
+
"starknet": "^7.6.2",
|
|
28
28
|
"starknetkit": "^2.6.1"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@cartridge/controller-wasm": "0.2.1",
|
|
31
32
|
"@cartridge/penpal": "^6.2.4",
|
|
32
33
|
"ethers": "^6.13.5",
|
|
33
|
-
"@starknet-io/types-js": "^0.
|
|
34
|
+
"@starknet-io/types-js": "^0.8.4",
|
|
34
35
|
"@telegram-apps/sdk": "^2.4.0",
|
|
35
36
|
"@turnkey/sdk-browser": "^4.0.0",
|
|
36
37
|
"cbor-x": "^1.5.0",
|
|
37
38
|
"mipd": "^0.0.7",
|
|
38
|
-
"@walletconnect/ethereum-provider": "^2.20.0"
|
|
39
|
-
"@cartridge/ui": "0.7.14-alpha.3",
|
|
40
|
-
"@cartridge/account-wasm": "0.7.14-alpha.3"
|
|
39
|
+
"@walletconnect/ethereum-provider": "^2.20.0"
|
|
41
40
|
},
|
|
42
41
|
"devDependencies": {
|
|
43
42
|
"@types/jest": "^29.5.14",
|
|
43
|
+
"@types/mocha": "^10.0.10",
|
|
44
44
|
"@types/node": "^18.0.6",
|
|
45
45
|
"jest": "^29.7.0",
|
|
46
46
|
"prettier": "^3.4.2",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"typescript": "^5.7.3",
|
|
51
51
|
"vite": "^6.0.0",
|
|
52
52
|
"vite-plugin-dts": "^4.5.3",
|
|
53
|
-
"vite-plugin-node-polyfills": "^0.
|
|
53
|
+
"vite-plugin-node-polyfills": "^0.23.0",
|
|
54
54
|
"vite-plugin-top-level-await": "^1.4.4",
|
|
55
55
|
"vite-plugin-wasm": "^3.4.1",
|
|
56
|
-
"@cartridge/tsconfig": "0.
|
|
56
|
+
"@cartridge/tsconfig": "0.9.0"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
59
|
"build:deps": "pnpm build",
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { constants } from "starknet";
|
|
2
|
+
import ControllerProvider from "../controller";
|
|
3
|
+
|
|
4
|
+
describe("ControllerProvider defaults", () => {
|
|
5
|
+
let originalConsoleError: any;
|
|
6
|
+
let originalConsoleWarn: any;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Mock console methods to suppress expected errors/warnings
|
|
10
|
+
originalConsoleError = console.error;
|
|
11
|
+
originalConsoleWarn = console.warn;
|
|
12
|
+
console.error = jest.fn();
|
|
13
|
+
console.warn = jest.fn();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
console.error = originalConsoleError;
|
|
18
|
+
console.warn = originalConsoleWarn;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("should use default chains and chainId when not provided", () => {
|
|
22
|
+
const controller = new ControllerProvider({});
|
|
23
|
+
|
|
24
|
+
expect(controller.rpcUrl()).toBe(
|
|
25
|
+
"https://api.cartridge.gg/x/starknet/mainnet",
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("should use custom chains when provided", () => {
|
|
30
|
+
const customChains = [
|
|
31
|
+
{ rpcUrl: "https://api.cartridge.gg/x/starknet/sepolia" },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const controller = new ControllerProvider({
|
|
35
|
+
chains: customChains,
|
|
36
|
+
defaultChainId: constants.StarknetChainId.SN_SEPOLIA,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(controller.rpcUrl()).toBe(
|
|
40
|
+
"https://api.cartridge.gg/x/starknet/sepolia",
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("should throw error when using non-Cartridge RPC for mainnet", async () => {
|
|
45
|
+
const invalidChains = [
|
|
46
|
+
{ rpcUrl: "https://some-other-provider.com/starknet/mainnet" },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
expect(() => {
|
|
50
|
+
new ControllerProvider({
|
|
51
|
+
chains: invalidChains,
|
|
52
|
+
defaultChainId: constants.StarknetChainId.SN_MAIN,
|
|
53
|
+
});
|
|
54
|
+
}).toThrow("Only Cartridge RPC providers are allowed for mainnet");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("should throw error when using non-Cartridge RPC for sepolia", async () => {
|
|
58
|
+
const invalidChains = [
|
|
59
|
+
{ rpcUrl: "https://some-other-provider.com/starknet/sepolia" },
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
expect(() => {
|
|
63
|
+
new ControllerProvider({
|
|
64
|
+
chains: invalidChains,
|
|
65
|
+
defaultChainId: constants.StarknetChainId.SN_SEPOLIA,
|
|
66
|
+
});
|
|
67
|
+
}).toThrow("Only Cartridge RPC providers are allowed for sepolia");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("should allow non-Cartridge RPC for custom chains", () => {
|
|
71
|
+
const customChains = [{ rpcUrl: "http://localhost:5050" }];
|
|
72
|
+
|
|
73
|
+
// This should not throw
|
|
74
|
+
expect(() => {
|
|
75
|
+
new ControllerProvider({
|
|
76
|
+
chains: customChains,
|
|
77
|
+
});
|
|
78
|
+
}).not.toThrow();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -23,12 +23,6 @@ describe("parseChainId", () => {
|
|
|
23
23
|
).toBe(shortString.encodeShortString("WP_SLOT"));
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
test("identifies slot chain on localhost", () => {
|
|
27
|
-
expect(parseChainId(new URL("http://localhost:8001/x/slot/katana"))).toBe(
|
|
28
|
-
shortString.encodeShortString("WP_SLOT"),
|
|
29
|
-
);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
26
|
test("identifies slot chain with hyphenated name", () => {
|
|
33
27
|
expect(
|
|
34
28
|
parseChainId(
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Jest setup file to handle cleanup of timers and open handles
|
|
2
|
+
|
|
3
|
+
// Mock starknetkit to prevent it from creating timers
|
|
4
|
+
jest.mock("starknetkit", () => ({
|
|
5
|
+
connect: jest.fn(),
|
|
6
|
+
StarknetWindowObject: {},
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock("starknetkit/injected", () => ({
|
|
10
|
+
InjectedConnector: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
// Clean up any remaining timers after each test
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
jest.clearAllTimers();
|
|
16
|
+
jest.useRealTimers();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Force cleanup after all tests
|
|
20
|
+
afterAll(() => {
|
|
21
|
+
// Clear any remaining timers
|
|
22
|
+
jest.clearAllTimers();
|
|
23
|
+
jest.useRealTimers();
|
|
24
|
+
|
|
25
|
+
// Force garbage collection if available
|
|
26
|
+
if (global.gc) {
|
|
27
|
+
global.gc();
|
|
28
|
+
}
|
|
29
|
+
});
|
package/src/account.ts
CHANGED
|
@@ -6,8 +6,6 @@ import {
|
|
|
6
6
|
AllowArray,
|
|
7
7
|
} from "starknet";
|
|
8
8
|
|
|
9
|
-
import { SPEC } from "@starknet-io/types-js";
|
|
10
|
-
|
|
11
9
|
import {
|
|
12
10
|
ConnectError,
|
|
13
11
|
Keychain,
|
|
@@ -18,9 +16,9 @@ import {
|
|
|
18
16
|
import { AsyncMethodReturns } from "@cartridge/penpal";
|
|
19
17
|
import BaseProvider from "./provider";
|
|
20
18
|
import { toArray } from "./utils";
|
|
19
|
+
import { SIGNATURE } from "@starknet-io/types-js";
|
|
21
20
|
|
|
22
21
|
class ControllerAccount extends WalletAccount {
|
|
23
|
-
address: string;
|
|
24
22
|
private keychain: AsyncMethodReturns<Keychain>;
|
|
25
23
|
private modal: Modal;
|
|
26
24
|
private options?: KeychainOptions;
|
|
@@ -33,9 +31,8 @@ class ControllerAccount extends WalletAccount {
|
|
|
33
31
|
options: KeychainOptions,
|
|
34
32
|
modal: Modal,
|
|
35
33
|
) {
|
|
36
|
-
super({ nodeUrl: rpcUrl }, provider);
|
|
34
|
+
super({ nodeUrl: rpcUrl }, provider, address);
|
|
37
35
|
|
|
38
|
-
this.address = address;
|
|
39
36
|
this.keychain = keychain;
|
|
40
37
|
this.options = options;
|
|
41
38
|
this.modal = modal;
|
|
@@ -111,13 +108,13 @@ class ControllerAccount extends WalletAccount {
|
|
|
111
108
|
* @returns the signature of the JSON object
|
|
112
109
|
* @throws {Error} if the JSON object is not a valid JSON
|
|
113
110
|
*/
|
|
114
|
-
async signMessage(typedData: TypedData): Promise<
|
|
111
|
+
async signMessage(typedData: TypedData): Promise<SIGNATURE> {
|
|
115
112
|
return new Promise(async (resolve, reject) => {
|
|
116
113
|
const sessionSign = await this.keychain.signMessage(typedData, "", true);
|
|
117
114
|
|
|
118
115
|
// Session sign succeeded
|
|
119
116
|
if (!("code" in sessionSign)) {
|
|
120
|
-
resolve(sessionSign as
|
|
117
|
+
resolve(sessionSign as SIGNATURE);
|
|
121
118
|
return;
|
|
122
119
|
}
|
|
123
120
|
|
|
@@ -126,7 +123,7 @@ class ControllerAccount extends WalletAccount {
|
|
|
126
123
|
const manualSign = await this.keychain.signMessage(typedData, "", false);
|
|
127
124
|
|
|
128
125
|
if (!("code" in manualSign)) {
|
|
129
|
-
resolve(manualSign as
|
|
126
|
+
resolve(manualSign as SIGNATURE);
|
|
130
127
|
} else {
|
|
131
128
|
reject((manualSign as ConnectError).error);
|
|
132
129
|
}
|
package/src/controller.ts
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import { AsyncMethodReturns } from "@cartridge/penpal";
|
|
2
2
|
|
|
3
|
+
import { Policy } from "@cartridge/presets";
|
|
4
|
+
import {
|
|
5
|
+
AddInvokeTransactionResult,
|
|
6
|
+
AddStarknetChainParameters,
|
|
7
|
+
ChainId,
|
|
8
|
+
} from "@starknet-io/types-js";
|
|
9
|
+
import { constants, shortString, WalletAccount } from "starknet";
|
|
10
|
+
import { version } from "../package.json";
|
|
3
11
|
import ControllerAccount from "./account";
|
|
4
|
-
import { KeychainIFrame, ProfileIFrame } from "./iframe";
|
|
5
12
|
import { NotReadyToConnect } from "./errors";
|
|
13
|
+
import { KeychainIFrame, ProfileIFrame } from "./iframe";
|
|
14
|
+
import BaseProvider from "./provider";
|
|
6
15
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
Chain,
|
|
17
|
+
ConnectError,
|
|
9
18
|
ConnectReply,
|
|
10
|
-
ProbeReply,
|
|
11
19
|
ControllerOptions,
|
|
12
|
-
ConnectError,
|
|
13
|
-
Profile,
|
|
14
20
|
IFrames,
|
|
21
|
+
Keychain,
|
|
22
|
+
ProbeReply,
|
|
23
|
+
Profile,
|
|
15
24
|
ProfileContextTypeVariant,
|
|
16
|
-
|
|
25
|
+
ResponseCodes,
|
|
17
26
|
} from "./types";
|
|
18
|
-
import BaseProvider from "./provider";
|
|
19
|
-
import { shortString, WalletAccount } from "starknet";
|
|
20
|
-
import { Policy } from "@cartridge/presets";
|
|
21
|
-
import {
|
|
22
|
-
AddInvokeTransactionResult,
|
|
23
|
-
AddStarknetChainParameters,
|
|
24
|
-
ChainId,
|
|
25
|
-
} from "@starknet-io/types-js";
|
|
26
27
|
import { parseChainId } from "./utils";
|
|
27
28
|
|
|
28
29
|
export default class ControllerProvider extends BaseProvider {
|
|
@@ -33,10 +34,26 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
33
34
|
private selectedChain: ChainId;
|
|
34
35
|
private chains: Map<ChainId, Chain>;
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
isReady(): boolean {
|
|
38
|
+
return !!this.keychain;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
constructor(options: ControllerOptions = {}) {
|
|
37
42
|
super();
|
|
38
43
|
|
|
39
|
-
|
|
44
|
+
// Default Cartridge chains that are always available
|
|
45
|
+
const cartridgeChains: Chain[] = [
|
|
46
|
+
{ rpcUrl: "https://api.cartridge.gg/x/starknet/sepolia" },
|
|
47
|
+
{ rpcUrl: "https://api.cartridge.gg/x/starknet/mainnet" },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// Merge user chains with default chains
|
|
51
|
+
// User chains take precedence if they specify the same network
|
|
52
|
+
const chains = [...cartridgeChains, ...(options.chains || [])];
|
|
53
|
+
const defaultChainId =
|
|
54
|
+
options.defaultChainId || constants.StarknetChainId.SN_MAIN;
|
|
55
|
+
|
|
56
|
+
this.selectedChain = defaultChainId;
|
|
40
57
|
this.chains = new Map<ChainId, Chain>();
|
|
41
58
|
|
|
42
59
|
this.iframes = {
|
|
@@ -46,18 +63,52 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
46
63
|
onConnect: (keychain) => {
|
|
47
64
|
this.keychain = keychain;
|
|
48
65
|
},
|
|
66
|
+
version: version,
|
|
49
67
|
}),
|
|
50
68
|
};
|
|
51
69
|
|
|
52
|
-
this.options = options;
|
|
70
|
+
this.options = { ...options, chains, defaultChainId };
|
|
53
71
|
|
|
54
|
-
this.
|
|
72
|
+
this.initializeChains(chains);
|
|
55
73
|
|
|
56
74
|
if (typeof window !== "undefined") {
|
|
57
75
|
(window as any).starknet_controller = this;
|
|
58
76
|
}
|
|
59
77
|
}
|
|
60
78
|
|
|
79
|
+
async logout() {
|
|
80
|
+
if (!this.keychain) {
|
|
81
|
+
console.error(new NotReadyToConnect().message);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Disconnect the controller/keychain first
|
|
87
|
+
await this.disconnect();
|
|
88
|
+
|
|
89
|
+
// Close all controller iframes
|
|
90
|
+
const iframes = document.querySelectorAll('iframe[id^="controller-"]');
|
|
91
|
+
iframes.forEach((iframe) => {
|
|
92
|
+
const container = iframe.parentElement;
|
|
93
|
+
if (container) {
|
|
94
|
+
container.style.visibility = "hidden";
|
|
95
|
+
container.style.opacity = "0";
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Reset body overflow
|
|
100
|
+
if (document.body) {
|
|
101
|
+
document.body.style.overflow = "auto";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Reload the page to complete logout
|
|
105
|
+
window.location.reload();
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error("Logout failed:", err);
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
61
112
|
async probe(): Promise<WalletAccount | undefined> {
|
|
62
113
|
try {
|
|
63
114
|
await this.waitForKeychain();
|
|
@@ -96,6 +147,7 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
96
147
|
openSettings: () => this.openSettings.bind(this),
|
|
97
148
|
openPurchaseCredits: () => this.openPurchaseCredits.bind(this),
|
|
98
149
|
openExecute: () => this.openExecute.bind(this),
|
|
150
|
+
logout: () => this.logout.bind(this),
|
|
99
151
|
},
|
|
100
152
|
rpcUrl: this.rpcUrl(),
|
|
101
153
|
username,
|
|
@@ -127,7 +179,15 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
127
179
|
|
|
128
180
|
try {
|
|
129
181
|
let response = await this.keychain.connect(
|
|
130
|
-
|
|
182
|
+
// Policy precedence logic:
|
|
183
|
+
// 1. If shouldOverridePresetPolicies is true and policies are provided, use policies
|
|
184
|
+
// 2. Otherwise, if preset is defined, use empty object (let preset take precedence)
|
|
185
|
+
// 3. Otherwise, use provided policies or empty object
|
|
186
|
+
this.options.shouldOverridePresetPolicies && this.options.policies
|
|
187
|
+
? this.options.policies
|
|
188
|
+
: this.options.preset
|
|
189
|
+
? {}
|
|
190
|
+
: this.options.policies || {},
|
|
131
191
|
this.rpcUrl(),
|
|
132
192
|
this.options.signupOptions,
|
|
133
193
|
);
|
|
@@ -368,14 +428,30 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
368
428
|
return await this.keychain.delegateAccount();
|
|
369
429
|
}
|
|
370
430
|
|
|
371
|
-
private
|
|
431
|
+
private initializeChains(chains: Chain[]) {
|
|
372
432
|
for (const chain of chains) {
|
|
373
433
|
try {
|
|
374
434
|
const url = new URL(chain.rpcUrl);
|
|
375
|
-
const chainId =
|
|
435
|
+
const chainId = parseChainId(url);
|
|
436
|
+
|
|
437
|
+
// Validate that mainnet and sepolia must use Cartridge RPC
|
|
438
|
+
const isMainnet = chainId === constants.StarknetChainId.SN_MAIN;
|
|
439
|
+
const isSepolia = chainId === constants.StarknetChainId.SN_SEPOLIA;
|
|
440
|
+
const isCartridgeRpc = url.hostname === "api.cartridge.gg";
|
|
441
|
+
const isLocalhost =
|
|
442
|
+
url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
443
|
+
|
|
444
|
+
if ((isMainnet || isSepolia) && !(isCartridgeRpc || isLocalhost)) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
`Only Cartridge RPC providers are allowed for ${isMainnet ? "mainnet" : "sepolia"}. ` +
|
|
447
|
+
`Please use: https://api.cartridge.gg/x/starknet/${isMainnet ? "mainnet" : "sepolia"}`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
376
451
|
this.chains.set(chainId, chain);
|
|
377
452
|
} catch (error) {
|
|
378
453
|
console.error(`Failed to parse chainId for ${chain.rpcUrl}:`, error);
|
|
454
|
+
throw error; // Re-throw to ensure invalid chains fail fast
|
|
379
455
|
}
|
|
380
456
|
}
|
|
381
457
|
|
package/src/iframe/base.ts
CHANGED
|
@@ -14,6 +14,7 @@ export class IFrame<CallSender extends {}> implements Modal {
|
|
|
14
14
|
private iframe?: HTMLIFrameElement;
|
|
15
15
|
private container?: HTMLDivElement;
|
|
16
16
|
private onClose?: () => void;
|
|
17
|
+
private child?: AsyncMethodReturns<CallSender>;
|
|
17
18
|
|
|
18
19
|
constructor({
|
|
19
20
|
id,
|
|
@@ -72,6 +73,20 @@ export class IFrame<CallSender extends {}> implements Modal {
|
|
|
72
73
|
container.style.pointerEvents = "auto";
|
|
73
74
|
container.appendChild(iframe);
|
|
74
75
|
|
|
76
|
+
// Add click event listener to close iframe when clicking outside
|
|
77
|
+
container.addEventListener("click", (e) => {
|
|
78
|
+
if (e.target === container) {
|
|
79
|
+
// Attempting to reset(clear context) for keychain iframe (identified by ID)
|
|
80
|
+
if (id === "controller-keychain" && this.child) {
|
|
81
|
+
// Type assertion for keychain child only
|
|
82
|
+
(this.child as any)
|
|
83
|
+
.reset?.()
|
|
84
|
+
.catch((e: any) => console.error("Error resetting context:", e));
|
|
85
|
+
}
|
|
86
|
+
this.close();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
75
90
|
this.iframe = iframe;
|
|
76
91
|
this.container = container;
|
|
77
92
|
|
|
@@ -98,7 +113,10 @@ export class IFrame<CallSender extends {}> implements Modal {
|
|
|
98
113
|
reload: (_origin: string) => () => window.location.reload(),
|
|
99
114
|
...methods,
|
|
100
115
|
},
|
|
101
|
-
}).promise.then(
|
|
116
|
+
}).promise.then((child) => {
|
|
117
|
+
this.child = child;
|
|
118
|
+
onConnect(child);
|
|
119
|
+
});
|
|
102
120
|
|
|
103
121
|
this.resize();
|
|
104
122
|
window.addEventListener("resize", () => this.resize());
|
package/src/iframe/keychain.ts
CHANGED
|
@@ -3,12 +3,20 @@ import { Keychain, KeychainOptions } from "../types";
|
|
|
3
3
|
import { WalletBridge } from "../wallets/bridge";
|
|
4
4
|
import { IFrame, IFrameOptions } from "./base";
|
|
5
5
|
|
|
6
|
-
type KeychainIframeOptions = IFrameOptions<Keychain> &
|
|
6
|
+
type KeychainIframeOptions = IFrameOptions<Keychain> &
|
|
7
|
+
KeychainOptions & {
|
|
8
|
+
version?: string;
|
|
9
|
+
};
|
|
7
10
|
|
|
8
11
|
export class KeychainIFrame extends IFrame<Keychain> {
|
|
9
12
|
private walletBridge: WalletBridge;
|
|
10
13
|
|
|
11
|
-
constructor({
|
|
14
|
+
constructor({
|
|
15
|
+
url,
|
|
16
|
+
policies,
|
|
17
|
+
version,
|
|
18
|
+
...iframeOptions
|
|
19
|
+
}: KeychainIframeOptions) {
|
|
12
20
|
const _url = new URL(url ?? KEYCHAIN_URL);
|
|
13
21
|
const walletBridge = new WalletBridge();
|
|
14
22
|
|
|
@@ -19,6 +27,10 @@ export class KeychainIFrame extends IFrame<Keychain> {
|
|
|
19
27
|
);
|
|
20
28
|
}
|
|
21
29
|
|
|
30
|
+
if (version) {
|
|
31
|
+
_url.searchParams.set("v", encodeURIComponent(version));
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
super({
|
|
23
35
|
...iframeOptions,
|
|
24
36
|
id: "controller-keychain",
|
package/src/iframe/profile.ts
CHANGED
|
@@ -20,7 +20,6 @@ export class ProfileIFrame extends IFrame<Profile> {
|
|
|
20
20
|
slot,
|
|
21
21
|
namespace,
|
|
22
22
|
tokens,
|
|
23
|
-
policies,
|
|
24
23
|
...iframeOptions
|
|
25
24
|
}: ProfileIFrameOptions) {
|
|
26
25
|
const _profileUrl = (profileUrl || PROFILE_URL).replace(/\/$/, "");
|
|
@@ -51,16 +50,6 @@ export class ProfileIFrame extends IFrame<Profile> {
|
|
|
51
50
|
);
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
if (policies?.contracts) {
|
|
55
|
-
const methods = Object.values(policies.contracts).flatMap(
|
|
56
|
-
(contract) => contract.methods,
|
|
57
|
-
);
|
|
58
|
-
_url.searchParams.set(
|
|
59
|
-
"methods",
|
|
60
|
-
encodeURIComponent(JSON.stringify(methods)),
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
53
|
super({
|
|
65
54
|
...iframeOptions,
|
|
66
55
|
id: "controller-profile",
|
package/src/node/account.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Policy } from "@cartridge/
|
|
2
|
-
import { CartridgeSessionAccount } from "@cartridge/
|
|
1
|
+
import { Policy } from "@cartridge/controller-wasm";
|
|
2
|
+
import { CartridgeSessionAccount } from "@cartridge/controller-wasm/session";
|
|
3
3
|
import { Call, InvokeFunctionResponse, WalletAccount } from "starknet";
|
|
4
4
|
|
|
5
5
|
import { normalizeCalls } from "../utils";
|
|
@@ -37,7 +37,7 @@ export default class SessionAccount extends WalletAccount {
|
|
|
37
37
|
sessionKeyGuid: string;
|
|
38
38
|
},
|
|
39
39
|
) {
|
|
40
|
-
super({ nodeUrl: rpcUrl }, provider);
|
|
40
|
+
super({ nodeUrl: rpcUrl }, provider, address);
|
|
41
41
|
|
|
42
42
|
this.address = address;
|
|
43
43
|
this.controller = CartridgeSessionAccount.newAsRegistered(
|