@keplr-wallet/provider 0.12.132 → 0.12.133-rc.1

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/src/inject.ts CHANGED
@@ -24,6 +24,10 @@ import {
24
24
  EIP6963EventNames,
25
25
  EIP6963ProviderInfo,
26
26
  EIP6963ProviderDetail,
27
+ IStarknetProvider,
28
+ WalletEvents,
29
+ AccountChangeEventHandler,
30
+ NetworkChangeEventHandler,
27
31
  } from "@keplr-wallet/types";
28
32
  import {
29
33
  Result,
@@ -36,6 +40,13 @@ import deepmerge from "deepmerge";
36
40
  import Long from "long";
37
41
  import { KeplrCoreTypes } from "./core-types";
38
42
  import EventEmitter from "events";
43
+ import {
44
+ AccountInterface,
45
+ Call,
46
+ DeployAccountSignerDetails,
47
+ InvocationsSignerDetails,
48
+ ProviderInterface,
49
+ } from "starknet";
39
50
 
40
51
  export interface ProxyRequest {
41
52
  type: "proxy-request";
@@ -43,6 +54,7 @@ export interface ProxyRequest {
43
54
  method: keyof (Keplr & KeplrCoreTypes);
44
55
  args: any[];
45
56
  ethereumProviderMethod?: keyof IEthereumProvider;
57
+ starknetProviderMethod?: keyof IStarknetProvider;
46
58
  }
47
59
 
48
60
  export interface ProxyRequestResponse {
@@ -91,6 +103,12 @@ export function injectKeplrToWindow(keplr: IKeplr): void {
91
103
  "getEnigmaUtils",
92
104
  keplr.getEnigmaUtils
93
105
  );
106
+
107
+ defineUnwritablePropertyIfPossible(
108
+ window,
109
+ "starknet_braavos",
110
+ keplr.starknet
111
+ );
94
112
  }
95
113
 
96
114
  /**
@@ -148,6 +166,7 @@ export class InjectedKeplr implements IKeplr, KeplrCoreTypes {
148
166
  if (
149
167
  !keplr[message.method] ||
150
168
  (message.method !== "ethereum" &&
169
+ message.method !== "starknet" &&
151
170
  typeof keplr[message.method] !== "function")
152
171
  ) {
153
172
  throw new Error(`Invalid method: ${message.method}`);
@@ -305,6 +324,76 @@ export class InjectedKeplr implements IKeplr, KeplrCoreTypes {
305
324
  );
306
325
  }
307
326
 
327
+ if (method === "starknet") {
328
+ const starknetProviderMethod = message.starknetProviderMethod;
329
+
330
+ if (starknetProviderMethod?.startsWith("protected")) {
331
+ throw new Error("Rejected");
332
+ }
333
+
334
+ if (starknetProviderMethod === "id") {
335
+ throw new Error("id is not function");
336
+ }
337
+
338
+ if (starknetProviderMethod === "name") {
339
+ throw new Error("name is not function");
340
+ }
341
+
342
+ if (starknetProviderMethod === "version") {
343
+ throw new Error("version is not function");
344
+ }
345
+
346
+ if (starknetProviderMethod === "icon") {
347
+ throw new Error("icon is not function");
348
+ }
349
+
350
+ if (starknetProviderMethod === "chainId") {
351
+ throw new Error("chainId is not function");
352
+ }
353
+
354
+ if (starknetProviderMethod === "selectedAddress") {
355
+ throw new Error("selectedAddress is not function");
356
+ }
357
+
358
+ if (starknetProviderMethod === "isConnected") {
359
+ throw new Error("isConnected is not function");
360
+ }
361
+
362
+ if (starknetProviderMethod === "account") {
363
+ throw new Error("account is not function");
364
+ }
365
+
366
+ if (starknetProviderMethod === "provider") {
367
+ throw new Error("provider is not function");
368
+ }
369
+
370
+ if (
371
+ starknetProviderMethod === undefined ||
372
+ typeof keplr.starknet?.[starknetProviderMethod] !== "function"
373
+ ) {
374
+ throw new Error(
375
+ `${message.starknetProviderMethod} is not function or invalid Starknet provider method`
376
+ );
377
+ }
378
+
379
+ const messageArgs = JSONUint8Array.unwrap(message.args);
380
+ if (starknetProviderMethod === "request") {
381
+ return await keplr.starknet.request(
382
+ typeof messageArgs === "string"
383
+ ? JSON.parse(messageArgs)
384
+ : messageArgs
385
+ );
386
+ }
387
+
388
+ return await keplr.starknet[starknetProviderMethod](
389
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
390
+ // @ts-ignore
391
+ ...(typeof messageArgs === "string"
392
+ ? JSON.parse(messageArgs)
393
+ : messageArgs)
394
+ );
395
+ }
396
+
308
397
  return await keplr[method](
309
398
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
310
399
  // @ts-ignore
@@ -410,6 +499,14 @@ export class InjectedKeplr implements IKeplr, KeplrCoreTypes {
410
499
  constructor(
411
500
  public readonly version: string,
412
501
  public readonly mode: KeplrMode,
502
+ protected readonly onStarknetStateChange: (state: {
503
+ selectedAddress: string | null;
504
+ chainId: string | null;
505
+ rpc: string | null;
506
+ }) => void,
507
+ protected readonly onStarknetAccountChange: (state: {
508
+ selectedAddress: string | null;
509
+ }) => void,
413
510
  protected readonly eventListener: {
414
511
  addMessageListener: (fn: (e: any) => void) => void;
415
512
  removeMessageListener: (fn: (e: any) => void) => void;
@@ -422,8 +519,13 @@ export class InjectedKeplr implements IKeplr, KeplrCoreTypes {
422
519
  postMessage: (message) =>
423
520
  window.postMessage(message, window.location.origin),
424
521
  },
425
- protected readonly parseMessage?: (message: any) => any,
426
- protected readonly eip6963ProviderInfo?: EIP6963ProviderInfo
522
+ protected readonly parseMessage: ((message: any) => any) | undefined,
523
+ protected readonly eip6963ProviderInfo: EIP6963ProviderInfo | undefined,
524
+ protected readonly starknetProviderInfo: {
525
+ id: string;
526
+ name: string;
527
+ icon: string;
528
+ }
427
529
  ) {
428
530
  // Freeze fields/method except for "defaultOptions"
429
531
  // Intentionally, "defaultOptions" can be mutated to allow a webpage to change the options with cosmjs usage.
@@ -870,12 +972,77 @@ export class InjectedKeplr implements IKeplr, KeplrCoreTypes {
870
972
  return await this.requestMethod("__core__webpageClosed", []);
871
973
  }
872
974
 
975
+ async getStarknetKey(chainId: string): Promise<{
976
+ name: string;
977
+ hexAddress: string;
978
+ pubKey: Uint8Array;
979
+ address: Uint8Array;
980
+ }> {
981
+ return await this.requestMethod("getStarknetKey", [chainId]);
982
+ }
983
+
984
+ async getStarknetKeysSettled(chainIds: string[]): Promise<
985
+ SettledResponses<{
986
+ name: string;
987
+ hexAddress: string;
988
+ pubKey: Uint8Array;
989
+ address: Uint8Array;
990
+ }>
991
+ > {
992
+ return await this.requestMethod("getStarknetKeysSettled", [chainIds]);
993
+ }
994
+
995
+ async signStarknetTx(
996
+ chainId: string,
997
+ transactions: Call[],
998
+ details: InvocationsSignerDetails
999
+ ): Promise<{
1000
+ transactions: Call[];
1001
+ details: InvocationsSignerDetails;
1002
+ signature: string[];
1003
+ }> {
1004
+ return await this.requestMethod("signStarknetTx", [
1005
+ chainId,
1006
+ transactions,
1007
+ details,
1008
+ ]);
1009
+ }
1010
+
1011
+ async signStarknetDeployAccountTransaction(
1012
+ chainId: string,
1013
+ transaction: DeployAccountSignerDetails
1014
+ ): Promise<{
1015
+ transaction: DeployAccountSignerDetails;
1016
+ signature: string[];
1017
+ }> {
1018
+ return await this.requestMethod("signStarknetDeployAccountTransaction", [
1019
+ chainId,
1020
+ transaction,
1021
+ ]);
1022
+ }
1023
+
1024
+ generateStarknetProvider(): IStarknetProvider {
1025
+ return new StarknetProvider(
1026
+ this.starknetProviderInfo.id,
1027
+ this.starknetProviderInfo.name,
1028
+ this.version,
1029
+ this.starknetProviderInfo.icon,
1030
+ () => this,
1031
+ this.onStarknetStateChange,
1032
+ this.onStarknetAccountChange,
1033
+ this.eventListener,
1034
+ this.parseMessage
1035
+ );
1036
+ }
1037
+
873
1038
  public readonly ethereum = new EthereumProvider(
874
- this,
1039
+ () => this,
875
1040
  this.eventListener,
876
1041
  this.parseMessage,
877
1042
  this.eip6963ProviderInfo
878
1043
  );
1044
+
1045
+ public readonly starknet = this.generateStarknetProvider();
879
1046
  }
880
1047
 
881
1048
  class EthereumProvider extends EventEmitter implements IEthereumProvider {
@@ -893,7 +1060,7 @@ class EthereumProvider extends EventEmitter implements IEthereumProvider {
893
1060
  protected _currentChainId: string | null = null;
894
1061
 
895
1062
  constructor(
896
- protected readonly injectedKeplr: InjectedKeplr,
1063
+ protected readonly injectedKeplr: () => InjectedKeplr,
897
1064
  protected readonly eventListener: {
898
1065
  addMessageListener: (fn: (e: any) => void) => void;
899
1066
  removeMessageListener: (fn: (e: any) => void) => void;
@@ -915,13 +1082,13 @@ class EthereumProvider extends EventEmitter implements IEthereumProvider {
915
1082
 
916
1083
  window.addEventListener("keplr_keystorechange", async () => {
917
1084
  if (this._currentChainId) {
918
- const chainInfo = await injectedKeplr.getChainInfoWithoutEndpoints(
1085
+ const chainInfo = await injectedKeplr().getChainInfoWithoutEndpoints(
919
1086
  this._currentChainId
920
1087
  );
921
1088
 
922
1089
  if (chainInfo) {
923
1090
  const selectedAddress = (
924
- await injectedKeplr.getKey(this._currentChainId)
1091
+ await injectedKeplr().getKey(this._currentChainId)
925
1092
  ).ethereumHexAddress;
926
1093
  this._handleAccountsChanged(selectedAddress);
927
1094
  }
@@ -1145,3 +1312,267 @@ class EthereumProvider extends EventEmitter implements IEthereumProvider {
1145
1312
  })) as string;
1146
1313
  };
1147
1314
  }
1315
+
1316
+ class StarknetProvider implements IStarknetProvider {
1317
+ isConnected: boolean = false;
1318
+
1319
+ // It must be in plain text format not hexadecimal string. e.g. "SN_MAIN"
1320
+ chainId?: string = undefined;
1321
+
1322
+ selectedAddress?: string = undefined;
1323
+
1324
+ account?: AccountInterface = undefined;
1325
+
1326
+ provider?: ProviderInterface = undefined;
1327
+
1328
+ // It must be in the CAIP-2 chain ID format. e.g. "starknet:SN_MAIN"
1329
+ protected _currentChainId?: string = undefined;
1330
+
1331
+ protected _userWalletEvents: WalletEvents[] = [];
1332
+
1333
+ constructor(
1334
+ public readonly id: string,
1335
+ public readonly name: string,
1336
+ public readonly version: string,
1337
+ public readonly icon: string,
1338
+
1339
+ protected readonly _injectedKeplr: () => InjectedKeplr,
1340
+ protected readonly onStateChange: (state: {
1341
+ selectedAddress: string | null;
1342
+ chainId: string | null;
1343
+ rpc: string | null;
1344
+ }) => void,
1345
+ protected readonly onAccountChange: (state: {
1346
+ selectedAddress: string | null;
1347
+ }) => void,
1348
+ protected readonly _eventListener: {
1349
+ addMessageListener: (fn: (e: any) => void) => void;
1350
+ removeMessageListener: (fn: (e: any) => void) => void;
1351
+ postMessage: (message: any) => void;
1352
+ } = {
1353
+ addMessageListener: (fn: (e: any) => void) =>
1354
+ window.addEventListener("message", fn),
1355
+ removeMessageListener: (fn: (e: any) => void) =>
1356
+ window.removeEventListener("message", fn),
1357
+ postMessage: (message) =>
1358
+ window.postMessage(message, window.location.origin),
1359
+ },
1360
+
1361
+ protected readonly _parseMessage?: (message: any) => any
1362
+ ) {
1363
+ this._initProviderState();
1364
+
1365
+ window.addEventListener("keplr_keystorechange", async () => {
1366
+ if (this._currentChainId) {
1367
+ const selectedAddress = (
1368
+ await this._injectedKeplr().getStarknetKey(this._currentChainId)
1369
+ ).hexAddress;
1370
+
1371
+ this.selectedAddress = selectedAddress;
1372
+
1373
+ this.onAccountChange({
1374
+ selectedAddress,
1375
+ });
1376
+
1377
+ this._userWalletEvents.forEach((userWalletEvent) => {
1378
+ if (userWalletEvent.type === "accountsChanged") {
1379
+ userWalletEvent.handler([selectedAddress]);
1380
+ }
1381
+ });
1382
+ }
1383
+ });
1384
+
1385
+ window.addEventListener("keplr_starknetChainChanged", (event) => {
1386
+ const origin = (event as CustomEvent).detail.origin;
1387
+ const starknetChainId = (event as CustomEvent).detail.starknetChainId;
1388
+
1389
+ this.chainId = starknetChainId;
1390
+
1391
+ if (origin === window.location.origin) {
1392
+ this._userWalletEvents.forEach((userWalletEvent) => {
1393
+ if (userWalletEvent.type === "networkChanged") {
1394
+ userWalletEvent.handler(starknetChainId);
1395
+ }
1396
+ });
1397
+ }
1398
+ });
1399
+ }
1400
+
1401
+ protected async _requestMethod<T = unknown>(
1402
+ method: keyof IStarknetProvider,
1403
+ args: Record<string, any>
1404
+ ): Promise<T> {
1405
+ const bytes = new Uint8Array(8);
1406
+ const id: string = Array.from(crypto.getRandomValues(bytes))
1407
+ .map((value) => {
1408
+ return value.toString(16);
1409
+ })
1410
+ .join("");
1411
+
1412
+ const proxyMessage: ProxyRequest = {
1413
+ type: "proxy-request",
1414
+ id,
1415
+ method: "starknet",
1416
+ args: JSONUint8Array.wrap(args),
1417
+ starknetProviderMethod: method,
1418
+ };
1419
+
1420
+ return new Promise<T>((resolve, reject) => {
1421
+ const receiveResponse = (e: any) => {
1422
+ const proxyResponse: ProxyRequestResponse = this._parseMessage
1423
+ ? this._parseMessage(e.data)
1424
+ : e.data;
1425
+
1426
+ if (!proxyResponse || proxyResponse.type !== "proxy-request-response") {
1427
+ return;
1428
+ }
1429
+
1430
+ if (proxyResponse.id !== id) {
1431
+ return;
1432
+ }
1433
+
1434
+ this._eventListener.removeMessageListener(receiveResponse);
1435
+
1436
+ const result = JSONUint8Array.unwrap(proxyResponse.result);
1437
+
1438
+ if (!result) {
1439
+ reject(new Error("Result is null"));
1440
+ return;
1441
+ }
1442
+
1443
+ if (result.error) {
1444
+ const error = result.error;
1445
+ reject(
1446
+ error.code && !error.module
1447
+ ? new EthereumProviderRpcError(
1448
+ error.code,
1449
+ error.message,
1450
+ error.data
1451
+ )
1452
+ : new Error(error)
1453
+ );
1454
+ return;
1455
+ }
1456
+
1457
+ resolve(result.return as T);
1458
+ };
1459
+
1460
+ this._eventListener.addMessageListener(receiveResponse);
1461
+
1462
+ this._eventListener.postMessage(proxyMessage);
1463
+ });
1464
+ }
1465
+
1466
+ protected async _initProviderState() {
1467
+ const { currentChainId, selectedAddress, rpc } = await this.request<{
1468
+ currentChainId: string | null;
1469
+ selectedAddress: string | null;
1470
+ rpc: string | null;
1471
+ }>({
1472
+ type: "keplr_initStarknetProviderState",
1473
+ });
1474
+
1475
+ if (currentChainId != null && selectedAddress != null && rpc != null) {
1476
+ this.onStateChange({
1477
+ selectedAddress,
1478
+ chainId: currentChainId,
1479
+ rpc,
1480
+ });
1481
+
1482
+ this._currentChainId = currentChainId;
1483
+ this.chainId = currentChainId.replace("starknet:", "");
1484
+ this.selectedAddress = selectedAddress;
1485
+ this.isConnected = true;
1486
+ } else {
1487
+ this.onStateChange({
1488
+ selectedAddress: null,
1489
+ chainId: null,
1490
+ rpc: null,
1491
+ });
1492
+ }
1493
+ }
1494
+
1495
+ async request<T = unknown>({
1496
+ type,
1497
+ params,
1498
+ }: {
1499
+ type: string;
1500
+ params?: unknown[] | Record<string, unknown>;
1501
+ }): Promise<T> {
1502
+ if (typeof type !== "string") {
1503
+ throw new Error("Invalid parameter: `type` must be a string");
1504
+ }
1505
+
1506
+ return await this._requestMethod<T>("request", {
1507
+ type,
1508
+ params,
1509
+ });
1510
+ }
1511
+ async enable(_options?: {
1512
+ starknetVersion?: "v4" | "v5";
1513
+ }): Promise<string[]> {
1514
+ const { currentChainId, selectedAddress, rpc } = await this.request<{
1515
+ currentChainId: string;
1516
+ selectedAddress: string;
1517
+ rpc: string;
1518
+ }>({
1519
+ type: "keplr_enableStarknetProvider",
1520
+ });
1521
+
1522
+ this.onStateChange({
1523
+ selectedAddress,
1524
+ chainId: currentChainId,
1525
+ rpc,
1526
+ });
1527
+
1528
+ this._currentChainId = currentChainId;
1529
+ this.chainId = currentChainId.replace("starknet:", "");
1530
+ this.selectedAddress = selectedAddress;
1531
+ this.isConnected = true;
1532
+
1533
+ return [selectedAddress];
1534
+ }
1535
+ async isPreauthorized(): Promise<boolean> {
1536
+ const { currentChainId, selectedAddress } = await this.request<{
1537
+ currentChainId: string | null;
1538
+ selectedAddress: string | null;
1539
+ }>({
1540
+ type: "keplr_initStarknetProviderState",
1541
+ });
1542
+
1543
+ if (currentChainId != null && selectedAddress != null) {
1544
+ return true;
1545
+ }
1546
+
1547
+ return false;
1548
+ }
1549
+ on<E extends WalletEvents>(event: E["type"], handleEvent: E["handler"]) {
1550
+ if (event === "accountsChanged") {
1551
+ this._userWalletEvents.push({
1552
+ type: "accountsChanged",
1553
+ handler: handleEvent as AccountChangeEventHandler,
1554
+ });
1555
+ } else if (event === "networkChanged") {
1556
+ this._userWalletEvents.push({
1557
+ type: "networkChanged",
1558
+ handler: handleEvent as NetworkChangeEventHandler,
1559
+ });
1560
+ } else {
1561
+ throw new Error("Invalid event type");
1562
+ }
1563
+ }
1564
+ off<E extends WalletEvents>(event: E["type"], handleEvent: E["handler"]) {
1565
+ if (event !== "accountsChanged" && event !== "networkChanged") {
1566
+ throw new Error("Invalid event type");
1567
+ }
1568
+
1569
+ const eventIndex = this._userWalletEvents.findIndex(
1570
+ (userEvent) =>
1571
+ userEvent.type === event && userEvent.handler === handleEvent
1572
+ );
1573
+
1574
+ if (eventIndex >= 0) {
1575
+ this._userWalletEvents.splice(eventIndex, 1);
1576
+ }
1577
+ }
1578
+ }