@cartridge/controller 0.13.5 → 0.13.6
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 +18 -18
- package/.turbo/turbo-build.log +17 -17
- package/dist/index-BdTFKueB.js +1072 -0
- package/dist/index-BdTFKueB.js.map +1 -0
- package/dist/index.js +1809 -2478
- 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.js +4 -4
- package/dist/node/index.js.map +1 -1
- package/dist/session/provider.d.ts +7 -2
- package/dist/session.js +141 -142
- package/dist/session.js.map +1 -1
- package/dist/stats.html +1 -1
- package/dist/utils.d.ts +5 -3
- package/package.json +2 -2
- package/src/__tests__/toWasmPolicies.test.ts +89 -40
- package/src/iframe/keychain.ts +7 -0
- package/src/session/provider.ts +75 -48
- package/src/utils.ts +19 -5
- package/dist/provider-NKp7_oNj.js +0 -387
- package/dist/provider-NKp7_oNj.js.map +0 -1
package/dist/utils.d.ts
CHANGED
|
@@ -8,13 +8,15 @@ export declare function normalizeCalls(calls: Call | Call[]): {
|
|
|
8
8
|
contractAddress: string;
|
|
9
9
|
calldata: import('starknet').HexCalldata;
|
|
10
10
|
}[];
|
|
11
|
+
export declare function getPresetSessionPolicies(config: Record<string, unknown>, chainId: string): SessionPolicies | undefined;
|
|
11
12
|
export declare function toSessionPolicies(policies: Policies): SessionPolicies;
|
|
12
13
|
/**
|
|
13
14
|
* Converts parsed session policies to WASM-compatible Policy objects.
|
|
14
15
|
*
|
|
15
|
-
* IMPORTANT: Policies are sorted canonically
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* IMPORTANT: Policies are sorted canonically and addresses are normalized
|
|
17
|
+
* via getChecksumAddress before hashing. Without this, Object.keys/entries
|
|
18
|
+
* reordering or inconsistent address casing can cause identical policies to
|
|
19
|
+
* produce different merkle roots, leading to "session/not-registered" errors.
|
|
18
20
|
* See: https://github.com/cartridge-gg/controller/issues/2357
|
|
19
21
|
*/
|
|
20
22
|
export declare function toWasmPolicies(policies: ParsedSessionPolicies): Policy[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cartridge/controller",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.6",
|
|
4
4
|
"description": "Cartridge Controller",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"vite-plugin-node-polyfills": "^0.23.0",
|
|
57
57
|
"vite-plugin-top-level-await": "^1.4.4",
|
|
58
58
|
"vite-plugin-wasm": "^3.4.1",
|
|
59
|
-
"@cartridge/tsconfig": "0.13.
|
|
59
|
+
"@cartridge/tsconfig": "0.13.6"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"build:deps": "pnpm build",
|
|
@@ -1,16 +1,27 @@
|
|
|
1
|
+
import { getChecksumAddress } from "starknet";
|
|
1
2
|
import { toWasmPolicies } from "../utils";
|
|
2
3
|
import { ParsedSessionPolicies } from "../policies";
|
|
3
4
|
|
|
5
|
+
// Valid hex addresses for testing (short but valid for getChecksumAddress)
|
|
6
|
+
const ADDR_A = "0x0aaa";
|
|
7
|
+
const ADDR_B = "0x0bbb";
|
|
8
|
+
const ADDR_C = "0x0ccc";
|
|
9
|
+
|
|
10
|
+
// Pre-compute checksummed forms
|
|
11
|
+
const ADDR_A_CS = getChecksumAddress(ADDR_A);
|
|
12
|
+
const ADDR_B_CS = getChecksumAddress(ADDR_B);
|
|
13
|
+
const ADDR_C_CS = getChecksumAddress(ADDR_C);
|
|
14
|
+
|
|
4
15
|
describe("toWasmPolicies", () => {
|
|
5
16
|
describe("canonical ordering", () => {
|
|
6
17
|
test("sorts contracts by address regardless of input order", () => {
|
|
7
18
|
const policies1: ParsedSessionPolicies = {
|
|
8
19
|
verified: false,
|
|
9
20
|
contracts: {
|
|
10
|
-
|
|
21
|
+
[ADDR_A]: {
|
|
11
22
|
methods: [{ entrypoint: "foo", authorized: true }],
|
|
12
23
|
},
|
|
13
|
-
|
|
24
|
+
[ADDR_B]: {
|
|
14
25
|
methods: [{ entrypoint: "bar", authorized: true }],
|
|
15
26
|
},
|
|
16
27
|
},
|
|
@@ -19,10 +30,10 @@ describe("toWasmPolicies", () => {
|
|
|
19
30
|
const policies2: ParsedSessionPolicies = {
|
|
20
31
|
verified: false,
|
|
21
32
|
contracts: {
|
|
22
|
-
|
|
33
|
+
[ADDR_B]: {
|
|
23
34
|
methods: [{ entrypoint: "bar", authorized: true }],
|
|
24
35
|
},
|
|
25
|
-
|
|
36
|
+
[ADDR_A]: {
|
|
26
37
|
methods: [{ entrypoint: "foo", authorized: true }],
|
|
27
38
|
},
|
|
28
39
|
},
|
|
@@ -32,16 +43,16 @@ describe("toWasmPolicies", () => {
|
|
|
32
43
|
const result2 = toWasmPolicies(policies2);
|
|
33
44
|
|
|
34
45
|
expect(result1).toEqual(result2);
|
|
35
|
-
// First policy should be for
|
|
36
|
-
expect(result1[0]).toHaveProperty("target",
|
|
37
|
-
expect(result1[1]).toHaveProperty("target",
|
|
46
|
+
// First policy should be for ADDR_A (sorted alphabetically)
|
|
47
|
+
expect(result1[0]).toHaveProperty("target", ADDR_A_CS);
|
|
48
|
+
expect(result1[1]).toHaveProperty("target", ADDR_B_CS);
|
|
38
49
|
});
|
|
39
50
|
|
|
40
51
|
test("sorts methods within contracts by entrypoint", () => {
|
|
41
52
|
const policies1: ParsedSessionPolicies = {
|
|
42
53
|
verified: false,
|
|
43
54
|
contracts: {
|
|
44
|
-
|
|
55
|
+
[ADDR_A]: {
|
|
45
56
|
methods: [
|
|
46
57
|
{ entrypoint: "zebra", authorized: true },
|
|
47
58
|
{ entrypoint: "apple", authorized: true },
|
|
@@ -54,7 +65,7 @@ describe("toWasmPolicies", () => {
|
|
|
54
65
|
const policies2: ParsedSessionPolicies = {
|
|
55
66
|
verified: false,
|
|
56
67
|
contracts: {
|
|
57
|
-
|
|
68
|
+
[ADDR_A]: {
|
|
58
69
|
methods: [
|
|
59
70
|
{ entrypoint: "mango", authorized: true },
|
|
60
71
|
{ entrypoint: "zebra", authorized: true },
|
|
@@ -74,19 +85,19 @@ describe("toWasmPolicies", () => {
|
|
|
74
85
|
const policies1: ParsedSessionPolicies = {
|
|
75
86
|
verified: false,
|
|
76
87
|
contracts: {
|
|
77
|
-
|
|
88
|
+
[ADDR_C]: {
|
|
78
89
|
methods: [
|
|
79
90
|
{ entrypoint: "c_method", authorized: true },
|
|
80
91
|
{ entrypoint: "a_method", authorized: true },
|
|
81
92
|
],
|
|
82
93
|
},
|
|
83
|
-
|
|
94
|
+
[ADDR_A]: {
|
|
84
95
|
methods: [
|
|
85
96
|
{ entrypoint: "z_method", authorized: true },
|
|
86
97
|
{ entrypoint: "a_method", authorized: true },
|
|
87
98
|
],
|
|
88
99
|
},
|
|
89
|
-
|
|
100
|
+
[ADDR_B]: {
|
|
90
101
|
methods: [{ entrypoint: "b_method", authorized: true }],
|
|
91
102
|
},
|
|
92
103
|
},
|
|
@@ -96,16 +107,16 @@ describe("toWasmPolicies", () => {
|
|
|
96
107
|
const policies2: ParsedSessionPolicies = {
|
|
97
108
|
verified: false,
|
|
98
109
|
contracts: {
|
|
99
|
-
|
|
110
|
+
[ADDR_B]: {
|
|
100
111
|
methods: [{ entrypoint: "b_method", authorized: true }],
|
|
101
112
|
},
|
|
102
|
-
|
|
113
|
+
[ADDR_A]: {
|
|
103
114
|
methods: [
|
|
104
115
|
{ entrypoint: "a_method", authorized: true },
|
|
105
116
|
{ entrypoint: "z_method", authorized: true },
|
|
106
117
|
],
|
|
107
118
|
},
|
|
108
|
-
|
|
119
|
+
[ADDR_C]: {
|
|
109
120
|
methods: [
|
|
110
121
|
{ entrypoint: "a_method", authorized: true },
|
|
111
122
|
{ entrypoint: "c_method", authorized: true },
|
|
@@ -119,21 +130,21 @@ describe("toWasmPolicies", () => {
|
|
|
119
130
|
|
|
120
131
|
expect(result1).toEqual(result2);
|
|
121
132
|
|
|
122
|
-
// Verify order:
|
|
123
|
-
// Within
|
|
124
|
-
expect(result1[0]).toHaveProperty("target",
|
|
125
|
-
expect(result1[2]).toHaveProperty("target",
|
|
126
|
-
expect(result1[3]).toHaveProperty("target",
|
|
133
|
+
// Verify order: ADDR_A first, then ADDR_B, then ADDR_C
|
|
134
|
+
// Within ADDR_A: a_method before z_method
|
|
135
|
+
expect(result1[0]).toHaveProperty("target", ADDR_A_CS);
|
|
136
|
+
expect(result1[2]).toHaveProperty("target", ADDR_B_CS);
|
|
137
|
+
expect(result1[3]).toHaveProperty("target", ADDR_C_CS);
|
|
127
138
|
});
|
|
128
139
|
|
|
129
140
|
test("handles case-insensitive address sorting", () => {
|
|
130
141
|
const policies1: ParsedSessionPolicies = {
|
|
131
142
|
verified: false,
|
|
132
143
|
contracts: {
|
|
133
|
-
"
|
|
144
|
+
"0x0aaa": {
|
|
134
145
|
methods: [{ entrypoint: "foo", authorized: true }],
|
|
135
146
|
},
|
|
136
|
-
"
|
|
147
|
+
"0x0AAB": {
|
|
137
148
|
methods: [{ entrypoint: "bar", authorized: true }],
|
|
138
149
|
},
|
|
139
150
|
},
|
|
@@ -142,10 +153,10 @@ describe("toWasmPolicies", () => {
|
|
|
142
153
|
const policies2: ParsedSessionPolicies = {
|
|
143
154
|
verified: false,
|
|
144
155
|
contracts: {
|
|
145
|
-
"
|
|
156
|
+
"0x0AAB": {
|
|
146
157
|
methods: [{ entrypoint: "bar", authorized: true }],
|
|
147
158
|
},
|
|
148
|
-
"
|
|
159
|
+
"0x0aaa": {
|
|
149
160
|
methods: [{ entrypoint: "foo", authorized: true }],
|
|
150
161
|
},
|
|
151
162
|
},
|
|
@@ -157,6 +168,34 @@ describe("toWasmPolicies", () => {
|
|
|
157
168
|
expect(result1).toEqual(result2);
|
|
158
169
|
});
|
|
159
170
|
|
|
171
|
+
test("normalizes address casing via getChecksumAddress", () => {
|
|
172
|
+
const addr =
|
|
173
|
+
"0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";
|
|
174
|
+
const policies1: ParsedSessionPolicies = {
|
|
175
|
+
verified: false,
|
|
176
|
+
contracts: {
|
|
177
|
+
[addr.toLowerCase()]: {
|
|
178
|
+
methods: [{ entrypoint: "transfer", authorized: true }],
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const policies2: ParsedSessionPolicies = {
|
|
184
|
+
verified: false,
|
|
185
|
+
contracts: {
|
|
186
|
+
[addr.toUpperCase().replace("0X", "0x")]: {
|
|
187
|
+
methods: [{ entrypoint: "transfer", authorized: true }],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const result1 = toWasmPolicies(policies1);
|
|
193
|
+
const result2 = toWasmPolicies(policies2);
|
|
194
|
+
|
|
195
|
+
expect(result1).toEqual(result2);
|
|
196
|
+
expect(result1[0]).toHaveProperty("target", getChecksumAddress(addr));
|
|
197
|
+
});
|
|
198
|
+
|
|
160
199
|
test("handles empty policies", () => {
|
|
161
200
|
const policies: ParsedSessionPolicies = {
|
|
162
201
|
verified: false,
|
|
@@ -179,15 +218,21 @@ describe("toWasmPolicies", () => {
|
|
|
179
218
|
});
|
|
180
219
|
|
|
181
220
|
describe("ApprovalPolicy handling", () => {
|
|
221
|
+
const TOKEN =
|
|
222
|
+
"0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";
|
|
223
|
+
const TOKEN_CS = getChecksumAddress(TOKEN);
|
|
224
|
+
const SPENDER =
|
|
225
|
+
"0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
|
|
226
|
+
|
|
182
227
|
test("creates ApprovalPolicy for approve methods with spender and amount", () => {
|
|
183
228
|
const policies: ParsedSessionPolicies = {
|
|
184
229
|
verified: false,
|
|
185
230
|
contracts: {
|
|
186
|
-
|
|
231
|
+
[TOKEN]: {
|
|
187
232
|
methods: [
|
|
188
233
|
{
|
|
189
234
|
entrypoint: "approve",
|
|
190
|
-
spender:
|
|
235
|
+
spender: SPENDER,
|
|
191
236
|
amount: "1000000000000000000",
|
|
192
237
|
authorized: true,
|
|
193
238
|
},
|
|
@@ -200,8 +245,8 @@ describe("toWasmPolicies", () => {
|
|
|
200
245
|
|
|
201
246
|
expect(result).toHaveLength(1);
|
|
202
247
|
expect(result[0]).toEqual({
|
|
203
|
-
target:
|
|
204
|
-
spender:
|
|
248
|
+
target: TOKEN_CS,
|
|
249
|
+
spender: SPENDER,
|
|
205
250
|
amount: "1000000000000000000",
|
|
206
251
|
});
|
|
207
252
|
// Should NOT have method or authorized fields
|
|
@@ -213,11 +258,11 @@ describe("toWasmPolicies", () => {
|
|
|
213
258
|
const policies: ParsedSessionPolicies = {
|
|
214
259
|
verified: false,
|
|
215
260
|
contracts: {
|
|
216
|
-
|
|
261
|
+
[TOKEN]: {
|
|
217
262
|
methods: [
|
|
218
263
|
{
|
|
219
264
|
entrypoint: "approve",
|
|
220
|
-
spender:
|
|
265
|
+
spender: SPENDER,
|
|
221
266
|
amount: 1000000000000000000,
|
|
222
267
|
authorized: true,
|
|
223
268
|
},
|
|
@@ -237,7 +282,7 @@ describe("toWasmPolicies", () => {
|
|
|
237
282
|
const policies: ParsedSessionPolicies = {
|
|
238
283
|
verified: false,
|
|
239
284
|
contracts: {
|
|
240
|
-
|
|
285
|
+
[TOKEN]: {
|
|
241
286
|
methods: [
|
|
242
287
|
{
|
|
243
288
|
entrypoint: "approve",
|
|
@@ -267,11 +312,11 @@ describe("toWasmPolicies", () => {
|
|
|
267
312
|
const policies: ParsedSessionPolicies = {
|
|
268
313
|
verified: false,
|
|
269
314
|
contracts: {
|
|
270
|
-
|
|
315
|
+
[TOKEN]: {
|
|
271
316
|
methods: [
|
|
272
317
|
{
|
|
273
318
|
entrypoint: "approve",
|
|
274
|
-
spender:
|
|
319
|
+
spender: SPENDER,
|
|
275
320
|
authorized: true,
|
|
276
321
|
},
|
|
277
322
|
],
|
|
@@ -290,10 +335,14 @@ describe("toWasmPolicies", () => {
|
|
|
290
335
|
});
|
|
291
336
|
|
|
292
337
|
test("creates CallPolicy for non-approve methods", () => {
|
|
338
|
+
const CONTRACT =
|
|
339
|
+
"0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49";
|
|
340
|
+
const CONTRACT_CS = getChecksumAddress(CONTRACT);
|
|
341
|
+
|
|
293
342
|
const policies: ParsedSessionPolicies = {
|
|
294
343
|
verified: false,
|
|
295
344
|
contracts: {
|
|
296
|
-
|
|
345
|
+
[CONTRACT]: {
|
|
297
346
|
methods: [
|
|
298
347
|
{
|
|
299
348
|
entrypoint: "transfer",
|
|
@@ -307,7 +356,7 @@ describe("toWasmPolicies", () => {
|
|
|
307
356
|
const result = toWasmPolicies(policies);
|
|
308
357
|
|
|
309
358
|
expect(result).toHaveLength(1);
|
|
310
|
-
expect(result[0]).toHaveProperty("target",
|
|
359
|
+
expect(result[0]).toHaveProperty("target", CONTRACT_CS);
|
|
311
360
|
expect(result[0]).toHaveProperty("method");
|
|
312
361
|
expect(result[0]).toHaveProperty("authorized", true);
|
|
313
362
|
});
|
|
@@ -316,11 +365,11 @@ describe("toWasmPolicies", () => {
|
|
|
316
365
|
const policies: ParsedSessionPolicies = {
|
|
317
366
|
verified: false,
|
|
318
367
|
contracts: {
|
|
319
|
-
|
|
368
|
+
[TOKEN]: {
|
|
320
369
|
methods: [
|
|
321
370
|
{
|
|
322
371
|
entrypoint: "approve",
|
|
323
|
-
spender:
|
|
372
|
+
spender: SPENDER,
|
|
324
373
|
amount: "1000",
|
|
325
374
|
authorized: true,
|
|
326
375
|
},
|
|
@@ -339,7 +388,7 @@ describe("toWasmPolicies", () => {
|
|
|
339
388
|
|
|
340
389
|
// First should be approve (sorted alphabetically)
|
|
341
390
|
const approvePolicy = result[0];
|
|
342
|
-
expect(approvePolicy).toHaveProperty("spender",
|
|
391
|
+
expect(approvePolicy).toHaveProperty("spender", SPENDER);
|
|
343
392
|
expect(approvePolicy).toHaveProperty("amount", "1000");
|
|
344
393
|
|
|
345
394
|
// Second should be transfer
|
|
@@ -352,12 +401,12 @@ describe("toWasmPolicies", () => {
|
|
|
352
401
|
const policies: ParsedSessionPolicies = {
|
|
353
402
|
verified: false,
|
|
354
403
|
contracts: {
|
|
355
|
-
|
|
404
|
+
[TOKEN]: {
|
|
356
405
|
methods: [
|
|
357
406
|
{ entrypoint: "transfer", authorized: true },
|
|
358
407
|
{
|
|
359
408
|
entrypoint: "approve",
|
|
360
|
-
spender:
|
|
409
|
+
spender: SPENDER,
|
|
361
410
|
amount: "1000",
|
|
362
411
|
authorized: true,
|
|
363
412
|
},
|
package/src/iframe/keychain.ts
CHANGED
|
@@ -104,6 +104,13 @@ export class KeychainIFrame extends IFrame<Keychain> {
|
|
|
104
104
|
);
|
|
105
105
|
} else if (preset) {
|
|
106
106
|
_url.searchParams.set("preset", preset);
|
|
107
|
+
if (policies) {
|
|
108
|
+
console.warn(
|
|
109
|
+
"[Controller] Both `preset` and `policies` provided to ControllerProvider. " +
|
|
110
|
+
"Policies are ignored when preset is set. " +
|
|
111
|
+
"Use `shouldOverridePresetPolicies: true` to override.",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
107
114
|
}
|
|
108
115
|
|
|
109
116
|
// Add encrypted blob to URL fragment (hash) if present
|
package/src/session/provider.ts
CHANGED
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
signerToGuid,
|
|
5
5
|
subscribeCreateSession,
|
|
6
6
|
} from "@cartridge/controller-wasm";
|
|
7
|
-
import { SessionPolicies } from "@cartridge/presets";
|
|
7
|
+
import { loadConfig, SessionPolicies } from "@cartridge/presets";
|
|
8
8
|
import { AddStarknetChainParameters } from "@starknet-io/types-js";
|
|
9
9
|
import { encode } from "starknet";
|
|
10
10
|
import { API_URL, KEYCHAIN_URL } from "../constants";
|
|
11
|
-
import { ParsedSessionPolicies } from "../policies";
|
|
11
|
+
import { parsePolicies, ParsedSessionPolicies } from "../policies";
|
|
12
12
|
import BaseProvider from "../provider";
|
|
13
13
|
import { AuthOptions } from "../types";
|
|
14
|
-
import { toWasmPolicies } from "../utils";
|
|
14
|
+
import { getPresetSessionPolicies, toWasmPolicies } from "../utils";
|
|
15
15
|
import SessionAccount from "./account";
|
|
16
16
|
|
|
17
17
|
interface SessionRegistration {
|
|
@@ -29,7 +29,9 @@ interface SessionRegistration {
|
|
|
29
29
|
export type SessionOptions = {
|
|
30
30
|
rpc: string;
|
|
31
31
|
chainId: string;
|
|
32
|
-
policies
|
|
32
|
+
policies?: SessionPolicies;
|
|
33
|
+
preset?: string;
|
|
34
|
+
shouldOverridePresetPolicies?: boolean;
|
|
33
35
|
redirectUrl: string;
|
|
34
36
|
disconnectRedirectUrl?: string;
|
|
35
37
|
keychainUrl?: string;
|
|
@@ -47,10 +49,12 @@ export default class SessionProvider extends BaseProvider {
|
|
|
47
49
|
protected _redirectUrl: string;
|
|
48
50
|
protected _disconnectRedirectUrl?: string;
|
|
49
51
|
protected _policies: ParsedSessionPolicies;
|
|
52
|
+
protected _preset?: string;
|
|
53
|
+
private _ready: Promise<void>;
|
|
50
54
|
protected _keychainUrl: string;
|
|
51
55
|
protected _apiUrl: string;
|
|
52
|
-
protected _publicKey
|
|
53
|
-
protected _sessionKeyGuid
|
|
56
|
+
protected _publicKey!: string;
|
|
57
|
+
protected _sessionKeyGuid!: string;
|
|
54
58
|
protected _signupOptions?: AuthOptions;
|
|
55
59
|
public reopenBrowser: boolean = true;
|
|
56
60
|
|
|
@@ -58,6 +62,8 @@ export default class SessionProvider extends BaseProvider {
|
|
|
58
62
|
rpc,
|
|
59
63
|
chainId,
|
|
60
64
|
policies,
|
|
65
|
+
preset,
|
|
66
|
+
shouldOverridePresetPolicies,
|
|
61
67
|
redirectUrl,
|
|
62
68
|
disconnectRedirectUrl,
|
|
63
69
|
keychainUrl,
|
|
@@ -66,27 +72,27 @@ export default class SessionProvider extends BaseProvider {
|
|
|
66
72
|
}: SessionOptions) {
|
|
67
73
|
super();
|
|
68
74
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
89
|
-
}
|
|
75
|
+
if (!policies && !preset) {
|
|
76
|
+
throw new Error("Either `policies` or `preset` must be provided");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Policy precedence logic (matching ControllerProvider):
|
|
80
|
+
// 1. If shouldOverridePresetPolicies is true and policies are provided, use policies
|
|
81
|
+
// 2. Otherwise, if preset is defined, resolve policies from preset
|
|
82
|
+
// 3. Otherwise, use provided policies
|
|
83
|
+
if ((!preset || shouldOverridePresetPolicies) && policies) {
|
|
84
|
+
this._policies = parsePolicies(policies);
|
|
85
|
+
} else {
|
|
86
|
+
this._preset = preset;
|
|
87
|
+
if (policies) {
|
|
88
|
+
console.warn(
|
|
89
|
+
"[Controller] Both `preset` and `policies` provided to SessionProvider. " +
|
|
90
|
+
"Policies are ignored when preset is set. " +
|
|
91
|
+
"Use `shouldOverridePresetPolicies: true` to override.",
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
this._policies = { verified: false };
|
|
95
|
+
}
|
|
90
96
|
|
|
91
97
|
this._rpcUrl = rpc;
|
|
92
98
|
this._chainId = chainId;
|
|
@@ -96,6 +102,33 @@ export default class SessionProvider extends BaseProvider {
|
|
|
96
102
|
this._apiUrl = apiUrl ?? API_URL;
|
|
97
103
|
this._signupOptions = signupOptions;
|
|
98
104
|
|
|
105
|
+
// Eagerly start async init: resolve preset policies (if any),
|
|
106
|
+
// then try to restore an existing session from storage.
|
|
107
|
+
// All public async methods await this before proceeding.
|
|
108
|
+
this._ready = this._init();
|
|
109
|
+
|
|
110
|
+
if (typeof window !== "undefined") {
|
|
111
|
+
(window as any).starknet_controller_session = this;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async _init(): Promise<void> {
|
|
116
|
+
if (this._preset) {
|
|
117
|
+
const config = await loadConfig(this._preset);
|
|
118
|
+
if (!config) {
|
|
119
|
+
throw new Error(`Failed to load preset: ${this._preset}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const sessionPolicies = getPresetSessionPolicies(config, this._chainId);
|
|
123
|
+
if (!sessionPolicies) {
|
|
124
|
+
throw new Error(
|
|
125
|
+
`No policies found for chain ${this._chainId} in preset ${this._preset}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this._policies = parsePolicies(sessionPolicies);
|
|
130
|
+
}
|
|
131
|
+
|
|
99
132
|
const account = this.tryRetrieveFromQueryOrStorage();
|
|
100
133
|
if (!account) {
|
|
101
134
|
const pk = stark.randomAddress();
|
|
@@ -125,10 +158,6 @@ export default class SessionProvider extends BaseProvider {
|
|
|
125
158
|
starknet: { privateKey: encode.addHexPrefix(jsonPk.privKey) },
|
|
126
159
|
});
|
|
127
160
|
}
|
|
128
|
-
|
|
129
|
-
if (typeof window !== "undefined") {
|
|
130
|
-
(window as any).starknet_controller_session = this;
|
|
131
|
-
}
|
|
132
161
|
}
|
|
133
162
|
|
|
134
163
|
private validatePoliciesSubset(
|
|
@@ -218,25 +247,18 @@ export default class SessionProvider extends BaseProvider {
|
|
|
218
247
|
}
|
|
219
248
|
|
|
220
249
|
async username() {
|
|
221
|
-
await this.
|
|
250
|
+
await this._ready;
|
|
222
251
|
return this._username;
|
|
223
252
|
}
|
|
224
253
|
|
|
225
254
|
async probe(): Promise<WalletAccount | undefined> {
|
|
226
|
-
|
|
227
|
-
return this.account;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
this.account = this.tryRetrieveFromQueryOrStorage();
|
|
255
|
+
await this._ready;
|
|
231
256
|
return this.account;
|
|
232
257
|
}
|
|
233
258
|
|
|
234
259
|
async connect(): Promise<WalletAccount | undefined> {
|
|
235
|
-
|
|
236
|
-
return this.account;
|
|
237
|
-
}
|
|
260
|
+
await this._ready;
|
|
238
261
|
|
|
239
|
-
this.account = this.tryRetrieveFromQueryOrStorage();
|
|
240
262
|
if (this.account) {
|
|
241
263
|
return this.account;
|
|
242
264
|
}
|
|
@@ -259,13 +281,18 @@ export default class SessionProvider extends BaseProvider {
|
|
|
259
281
|
this._sessionKeyGuid = signerToGuid({
|
|
260
282
|
starknet: { privateKey: encode.addHexPrefix(pk) },
|
|
261
283
|
});
|
|
262
|
-
let url =
|
|
263
|
-
this._keychainUrl
|
|
264
|
-
|
|
265
|
-
this._redirectUrl
|
|
266
|
-
|
|
267
|
-
this.
|
|
268
|
-
|
|
284
|
+
let url =
|
|
285
|
+
`${this._keychainUrl}` +
|
|
286
|
+
`/session?public_key=${this._publicKey}` +
|
|
287
|
+
`&redirect_uri=${this._redirectUrl}` +
|
|
288
|
+
`&redirect_query_name=startapp` +
|
|
289
|
+
`&rpc_url=${this._rpcUrl}`;
|
|
290
|
+
|
|
291
|
+
if (this._preset) {
|
|
292
|
+
url += `&preset=${encodeURIComponent(this._preset)}`;
|
|
293
|
+
} else {
|
|
294
|
+
url += `&policies=${encodeURIComponent(JSON.stringify(this._policies))}`;
|
|
295
|
+
}
|
|
269
296
|
|
|
270
297
|
if (this._signupOptions) {
|
|
271
298
|
url += `&signers=${encodeURIComponent(JSON.stringify(this._signupOptions))}`;
|
package/src/utils.ts
CHANGED
|
@@ -48,6 +48,19 @@ export function normalizeCalls(calls: Call | Call[]) {
|
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export function getPresetSessionPolicies(
|
|
52
|
+
config: Record<string, unknown>,
|
|
53
|
+
chainId: string,
|
|
54
|
+
): SessionPolicies | undefined {
|
|
55
|
+
const decodedChainId = shortString.decodeShortString(chainId);
|
|
56
|
+
const chains = config.chains as
|
|
57
|
+
| Record<string, Record<string, unknown>>
|
|
58
|
+
| undefined;
|
|
59
|
+
const chainConfig = chains?.[decodedChainId];
|
|
60
|
+
if (!chainConfig?.policies) return undefined;
|
|
61
|
+
return toSessionPolicies(chainConfig.policies as Policies);
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
export function toSessionPolicies(policies: Policies): SessionPolicies {
|
|
52
65
|
return Array.isArray(policies)
|
|
53
66
|
? policies.reduce<SessionPolicies>(
|
|
@@ -92,9 +105,10 @@ export function toSessionPolicies(policies: Policies): SessionPolicies {
|
|
|
92
105
|
/**
|
|
93
106
|
* Converts parsed session policies to WASM-compatible Policy objects.
|
|
94
107
|
*
|
|
95
|
-
* IMPORTANT: Policies are sorted canonically
|
|
96
|
-
*
|
|
97
|
-
*
|
|
108
|
+
* IMPORTANT: Policies are sorted canonically and addresses are normalized
|
|
109
|
+
* via getChecksumAddress before hashing. Without this, Object.keys/entries
|
|
110
|
+
* reordering or inconsistent address casing can cause identical policies to
|
|
111
|
+
* produce different merkle roots, leading to "session/not-registered" errors.
|
|
98
112
|
* See: https://github.com/cartridge-gg/controller/issues/2357
|
|
99
113
|
*/
|
|
100
114
|
export function toWasmPolicies(policies: ParsedSessionPolicies): Policy[] {
|
|
@@ -110,7 +124,7 @@ export function toWasmPolicies(policies: ParsedSessionPolicies): Policy[] {
|
|
|
110
124
|
if (m.entrypoint === "approve") {
|
|
111
125
|
if ("spender" in m && "amount" in m && m.spender && m.amount) {
|
|
112
126
|
const approvalPolicy: ApprovalPolicy = {
|
|
113
|
-
target,
|
|
127
|
+
target: getChecksumAddress(target),
|
|
114
128
|
spender: m.spender,
|
|
115
129
|
amount: String(m.amount),
|
|
116
130
|
};
|
|
@@ -127,7 +141,7 @@ export function toWasmPolicies(policies: ParsedSessionPolicies): Policy[] {
|
|
|
127
141
|
|
|
128
142
|
// For non-approve methods and legacy approve, create a regular CallPolicy
|
|
129
143
|
return {
|
|
130
|
-
target,
|
|
144
|
+
target: getChecksumAddress(target),
|
|
131
145
|
method: hash.getSelectorFromName(m.entrypoint),
|
|
132
146
|
authorized: !!m.authorized,
|
|
133
147
|
};
|