@cartridge/controller 0.7.14-alpha.2 → 0.8.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 +49 -51
- package/.turbo/turbo-build.log +45 -47
- package/.turbo/turbo-format$colon$check.log +7 -0
- package/.turbo/turbo-format.log +44 -0
- package/dist/__tests__/setup.d.ts +0 -0
- package/dist/controller.d.ts +6 -4
- package/dist/iframe/base.d.ts +1 -0
- package/dist/iframe/profile.d.ts +1 -1
- package/dist/index.js +846 -4906
- package/dist/index.js.map +1 -1
- package/dist/node/index.cjs +2 -2
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.js +2 -2
- package/dist/node/index.js.map +1 -1
- package/dist/{provider-vgdJbfDg.js → provider-ClUbos7A.js} +98 -75
- package/dist/provider-ClUbos7A.js.map +1 -0
- package/dist/session/account.d.ts +2 -2
- package/dist/session.js +3 -3
- package/dist/session.js.map +1 -1
- package/dist/stats.html +1 -1
- package/dist/types.d.ts +7 -6
- package/dist/utils.d.ts +3 -3
- package/dist/wallets/index.d.ts +0 -2
- package/jest.config.ts +2 -0
- package/package.json +5 -5
- 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/controller.ts +97 -21
- package/src/iframe/base.ts +19 -1
- package/src/iframe/profile.ts +0 -11
- package/src/node/account.ts +2 -2
- package/src/session/account.ts +2 -2
- package/src/types.ts +8 -5
- package/src/utils.ts +49 -13
- package/src/wallets/bridge.ts +9 -5
- package/src/wallets/index.ts +0 -2
- package/src/wallets/metamask/index.ts +18 -9
- package/src/wallets/rabby/index.ts +5 -6
- package/tsconfig.json +3 -2
- package/dist/provider-vgdJbfDg.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
|
@@ -76,7 +76,7 @@ type CartridgeID = string;
|
|
|
76
76
|
export type ControllerAccounts = Record<ContractAddress, CartridgeID>;
|
|
77
77
|
export interface Keychain {
|
|
78
78
|
probe(rpcUrl: string): Promise<ProbeReply | ConnectError>;
|
|
79
|
-
connect(policies: SessionPolicies, rpcUrl: string, signupOptions?: AuthOptions): Promise<ConnectReply | ConnectError>;
|
|
79
|
+
connect(policies: SessionPolicies, rpcUrl: string, signupOptions?: AuthOptions, version?: string): Promise<ConnectReply | ConnectError>;
|
|
80
80
|
disconnect(): void;
|
|
81
81
|
reset(): void;
|
|
82
82
|
revoke(origin: string): void;
|
|
@@ -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/index.d.ts
CHANGED
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.8.0",
|
|
4
4
|
"description": "Cartridge Controller",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"starknetkit": "^2.6.1"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
+
"@cartridge/controller-wasm": "0.1.7",
|
|
31
32
|
"@cartridge/penpal": "^6.2.4",
|
|
32
33
|
"ethers": "^6.13.5",
|
|
33
34
|
"@starknet-io/types-js": "^0.7.7",
|
|
@@ -35,12 +36,11 @@
|
|
|
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/account-wasm": "0.7.14-alpha.2",
|
|
40
|
-
"@cartridge/utils": "0.7.14-alpha.2"
|
|
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",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"vite-plugin-node-polyfills": "^0.22.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.8.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/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
|
|
|
37
|
+
isReady(): boolean {
|
|
38
|
+
return !!this.keychain;
|
|
39
|
+
}
|
|
40
|
+
|
|
36
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 = {
|
|
@@ -49,15 +66,48 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
49
66
|
}),
|
|
50
67
|
};
|
|
51
68
|
|
|
52
|
-
this.options = options;
|
|
69
|
+
this.options = { ...options, chains, defaultChainId };
|
|
53
70
|
|
|
54
|
-
this.
|
|
71
|
+
this.initializeChains(chains);
|
|
55
72
|
|
|
56
73
|
if (typeof window !== "undefined") {
|
|
57
74
|
(window as any).starknet_controller = this;
|
|
58
75
|
}
|
|
59
76
|
}
|
|
60
77
|
|
|
78
|
+
async logout() {
|
|
79
|
+
if (!this.keychain) {
|
|
80
|
+
console.error(new NotReadyToConnect().message);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Disconnect the controller/keychain first
|
|
86
|
+
await this.disconnect();
|
|
87
|
+
|
|
88
|
+
// Close all controller iframes
|
|
89
|
+
const iframes = document.querySelectorAll('iframe[id^="controller-"]');
|
|
90
|
+
iframes.forEach((iframe) => {
|
|
91
|
+
const container = iframe.parentElement;
|
|
92
|
+
if (container) {
|
|
93
|
+
container.style.visibility = "hidden";
|
|
94
|
+
container.style.opacity = "0";
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Reset body overflow
|
|
99
|
+
if (document.body) {
|
|
100
|
+
document.body.style.overflow = "auto";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Reload the page to complete logout
|
|
104
|
+
window.location.reload();
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error("Logout failed:", err);
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
61
111
|
async probe(): Promise<WalletAccount | undefined> {
|
|
62
112
|
try {
|
|
63
113
|
await this.waitForKeychain();
|
|
@@ -96,6 +146,7 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
96
146
|
openSettings: () => this.openSettings.bind(this),
|
|
97
147
|
openPurchaseCredits: () => this.openPurchaseCredits.bind(this),
|
|
98
148
|
openExecute: () => this.openExecute.bind(this),
|
|
149
|
+
logout: () => this.logout.bind(this),
|
|
99
150
|
},
|
|
100
151
|
rpcUrl: this.rpcUrl(),
|
|
101
152
|
username,
|
|
@@ -127,9 +178,18 @@ export default class ControllerProvider extends BaseProvider {
|
|
|
127
178
|
|
|
128
179
|
try {
|
|
129
180
|
let response = await this.keychain.connect(
|
|
130
|
-
|
|
181
|
+
// Policy precedence logic:
|
|
182
|
+
// 1. If shouldOverridePresetPolicies is true and policies are provided, use policies
|
|
183
|
+
// 2. Otherwise, if preset is defined, use empty object (let preset take precedence)
|
|
184
|
+
// 3. Otherwise, use provided policies or empty object
|
|
185
|
+
this.options.shouldOverridePresetPolicies && this.options.policies
|
|
186
|
+
? this.options.policies
|
|
187
|
+
: this.options.preset
|
|
188
|
+
? {}
|
|
189
|
+
: this.options.policies || {},
|
|
131
190
|
this.rpcUrl(),
|
|
132
191
|
this.options.signupOptions,
|
|
192
|
+
version,
|
|
133
193
|
);
|
|
134
194
|
if (response.code !== ResponseCodes.SUCCESS) {
|
|
135
195
|
throw new Error(response.message);
|
|
@@ -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/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";
|
package/src/session/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";
|
package/src/types.ts
CHANGED
|
@@ -118,6 +118,7 @@ export interface Keychain {
|
|
|
118
118
|
policies: SessionPolicies,
|
|
119
119
|
rpcUrl: string,
|
|
120
120
|
signupOptions?: AuthOptions,
|
|
121
|
+
version?: string,
|
|
121
122
|
): Promise<ConnectReply | ConnectError>;
|
|
122
123
|
disconnect(): void;
|
|
123
124
|
|
|
@@ -200,8 +201,8 @@ export type Chain = {
|
|
|
200
201
|
};
|
|
201
202
|
|
|
202
203
|
export type ProviderOptions = {
|
|
203
|
-
defaultChainId
|
|
204
|
-
chains
|
|
204
|
+
defaultChainId?: ChainId;
|
|
205
|
+
chains?: Chain[];
|
|
205
206
|
};
|
|
206
207
|
|
|
207
208
|
export type KeychainOptions = IFrameOptions & {
|
|
@@ -216,6 +217,8 @@ export type KeychainOptions = IFrameOptions & {
|
|
|
216
217
|
feeSource?: FeeSource;
|
|
217
218
|
/** Signup options (the order of the options is reflected in the UI. It's recommended to group socials and wallets together ) */
|
|
218
219
|
signupOptions?: AuthOptions;
|
|
220
|
+
/** When true, manually provided policies will override preset policies. Default is false. */
|
|
221
|
+
shouldOverridePresetPolicies?: boolean;
|
|
219
222
|
};
|
|
220
223
|
|
|
221
224
|
export type ProfileOptions = IFrameOptions & {
|
|
@@ -227,8 +230,6 @@ export type ProfileOptions = IFrameOptions & {
|
|
|
227
230
|
namespace?: string;
|
|
228
231
|
/** The tokens to be listed on Inventory modal */
|
|
229
232
|
tokens?: Tokens;
|
|
230
|
-
/** The policies to use for the profile */
|
|
231
|
-
policies?: SessionPolicies;
|
|
232
233
|
};
|
|
233
234
|
|
|
234
235
|
export type ProfileContextTypeVariant =
|
|
@@ -238,6 +239,8 @@ export type ProfileContextTypeVariant =
|
|
|
238
239
|
| "leaderboard"
|
|
239
240
|
| "activity";
|
|
240
241
|
|
|
242
|
+
export type Token = "eth" | "strk" | "lords" | "usdc" | "usdt";
|
|
243
|
+
|
|
241
244
|
export type Tokens = {
|
|
242
|
-
erc20?:
|
|
245
|
+
erc20?: Token[];
|
|
243
246
|
};
|
package/src/utils.ts
CHANGED
|
@@ -5,12 +5,11 @@ import {
|
|
|
5
5
|
constants,
|
|
6
6
|
getChecksumAddress,
|
|
7
7
|
hash,
|
|
8
|
-
Provider,
|
|
9
8
|
shortString,
|
|
10
9
|
typedData,
|
|
11
10
|
TypedDataRevision,
|
|
12
11
|
} from "starknet";
|
|
13
|
-
import
|
|
12
|
+
import { Policy } from "@cartridge/controller-wasm/controller";
|
|
14
13
|
import { Policies, SessionPolicies } from "@cartridge/presets";
|
|
15
14
|
import { ChainId } from "@starknet-io/types-js";
|
|
16
15
|
import { ParsedSessionPolicies } from "./policies";
|
|
@@ -28,8 +27,6 @@ const ALLOWED_PROPERTIES = new Set([
|
|
|
28
27
|
"primaryType",
|
|
29
28
|
]);
|
|
30
29
|
|
|
31
|
-
const LOCAL_HOSTNAMES = ["localhost", "127.0.0.1", "0.0.0.0"];
|
|
32
|
-
|
|
33
30
|
function validatePropertyName(prop: string): void {
|
|
34
31
|
if (!ALLOWED_PROPERTIES.has(prop)) {
|
|
35
32
|
throw new Error(`Invalid property name: ${prop}`);
|
|
@@ -92,7 +89,7 @@ export function toSessionPolicies(policies: Policies): SessionPolicies {
|
|
|
92
89
|
: policies;
|
|
93
90
|
}
|
|
94
91
|
|
|
95
|
-
export function toWasmPolicies(policies: ParsedSessionPolicies):
|
|
92
|
+
export function toWasmPolicies(policies: ParsedSessionPolicies): Policy[] {
|
|
96
93
|
return [
|
|
97
94
|
...Object.entries(policies.contracts ?? {}).flatMap(
|
|
98
95
|
([target, { methods }]) =>
|
|
@@ -139,9 +136,55 @@ export function humanizeString(str: string): string {
|
|
|
139
136
|
);
|
|
140
137
|
}
|
|
141
138
|
|
|
142
|
-
export
|
|
139
|
+
export function parseChainId(url: URL): ChainId {
|
|
143
140
|
const parts = url.pathname.split("/");
|
|
144
141
|
|
|
142
|
+
// Handle localhost URLs by making a synchronous call to getChainId
|
|
143
|
+
if (
|
|
144
|
+
url.hostname === "localhost" ||
|
|
145
|
+
url.hostname === "127.0.0.1" ||
|
|
146
|
+
url.hostname === "0.0.0.0"
|
|
147
|
+
) {
|
|
148
|
+
// Check if we're in a browser environment
|
|
149
|
+
if (typeof XMLHttpRequest === "undefined") {
|
|
150
|
+
// In Node.js environment (like tests), we can't make synchronous HTTP calls
|
|
151
|
+
// For now, we'll use a placeholder chainId for localhost in tests
|
|
152
|
+
console.warn(
|
|
153
|
+
`Cannot make synchronous HTTP call in Node.js environment for ${url.toString()}`,
|
|
154
|
+
);
|
|
155
|
+
return shortString.encodeShortString("LOCALHOST") as ChainId;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Use a synchronous XMLHttpRequest to get the chain ID
|
|
159
|
+
const xhr = new XMLHttpRequest();
|
|
160
|
+
xhr.open("POST", url.toString(), false); // false makes it synchronous
|
|
161
|
+
xhr.setRequestHeader("Content-Type", "application/json");
|
|
162
|
+
|
|
163
|
+
const requestBody = JSON.stringify({
|
|
164
|
+
jsonrpc: "2.0",
|
|
165
|
+
method: "starknet_chainId",
|
|
166
|
+
params: [],
|
|
167
|
+
id: 1,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
xhr.send(requestBody);
|
|
172
|
+
|
|
173
|
+
if (xhr.status === 200) {
|
|
174
|
+
const response = JSON.parse(xhr.responseText);
|
|
175
|
+
if (response.result) {
|
|
176
|
+
return response.result as ChainId;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Failed to get chain ID from ${url.toString()}: ${xhr.status} ${xhr.statusText}`,
|
|
182
|
+
);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
throw new Error(`Failed to connect to ${url.toString()}: ${error}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
145
188
|
if (parts.includes("starknet")) {
|
|
146
189
|
if (parts.includes("mainnet")) {
|
|
147
190
|
return constants.StarknetChainId.SN_MAIN;
|
|
@@ -161,12 +204,5 @@ export async function parseChainId(url: URL): Promise<ChainId> {
|
|
|
161
204
|
}
|
|
162
205
|
}
|
|
163
206
|
|
|
164
|
-
if (LOCAL_HOSTNAMES.includes(url.hostname)) {
|
|
165
|
-
const provider = new Provider({
|
|
166
|
-
nodeUrl: url.toString(),
|
|
167
|
-
});
|
|
168
|
-
return await provider.getChainId();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
207
|
throw new Error(`Chain ${url.toString()} not supported`);
|
|
172
208
|
}
|