@phantom/embedded-provider-core 0.1.10 → 1.0.0-beta.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/dist/index.js CHANGED
@@ -32,7 +32,9 @@ var src_exports = {};
32
32
  __export(src_exports, {
33
33
  AUTHENTICATOR_EXPIRATION_TIME_MS: () => AUTHENTICATOR_EXPIRATION_TIME_MS,
34
34
  AUTHENTICATOR_RENEWAL_WINDOW_MS: () => AUTHENTICATOR_RENEWAL_WINDOW_MS,
35
+ EmbeddedEthereumChain: () => EmbeddedEthereumChain,
35
36
  EmbeddedProvider: () => EmbeddedProvider,
37
+ EmbeddedSolanaChain: () => EmbeddedSolanaChain,
36
38
  JWTAuth: () => JWTAuth,
37
39
  generateSessionId: () => generateSessionId,
38
40
  retryWithBackoff: () => retryWithBackoff
@@ -64,7 +66,8 @@ var JWTAuth = class {
64
66
  method: "POST",
65
67
  headers: {
66
68
  "Content-Type": "application/json",
67
- Authorization: `Bearer ${options.jwtToken}`
69
+ Authorization: `Bearer ${options.jwtToken}`,
70
+ "X-PHANTOM-APPID": options.appId
68
71
  },
69
72
  body: JSON.stringify({
70
73
  organizationId: options.organizationId,
@@ -165,6 +168,302 @@ async function retryWithBackoff(operation, operationName, logger, maxRetries = 3
165
168
  throw lastError;
166
169
  }
167
170
 
171
+ // src/chains/SolanaChain.ts
172
+ var import_eventemitter3 = require("eventemitter3");
173
+ var import_constants = require("@phantom/constants");
174
+ var import_buffer = require("buffer");
175
+ var EmbeddedSolanaChain = class {
176
+ constructor(provider) {
177
+ this.provider = provider;
178
+ this.currentNetworkId = import_constants.NetworkId.SOLANA_MAINNET;
179
+ this._connected = false;
180
+ this._publicKey = null;
181
+ this.eventEmitter = new import_eventemitter3.EventEmitter();
182
+ this.setupEventListeners();
183
+ this.syncInitialState();
184
+ }
185
+ // Wallet adapter compliant properties
186
+ get connected() {
187
+ return this._connected;
188
+ }
189
+ get publicKey() {
190
+ return this._publicKey;
191
+ }
192
+ ensureConnected() {
193
+ if (!this.provider.isConnected()) {
194
+ throw new Error("Solana chain not available. Ensure SDK is connected.");
195
+ }
196
+ }
197
+ // Standard wallet adapter methods
198
+ async signMessage(message) {
199
+ this.ensureConnected();
200
+ const messageStr = typeof message === "string" ? message : new TextDecoder().decode(message);
201
+ const result = await this.provider.signMessage({
202
+ message: messageStr,
203
+ networkId: this.currentNetworkId
204
+ });
205
+ const signature = typeof result.signature === "string" ? new Uint8Array(import_buffer.Buffer.from(result.signature, "base64")) : result.signature;
206
+ return {
207
+ signature,
208
+ publicKey: this._publicKey || ""
209
+ };
210
+ }
211
+ signTransaction(_transaction) {
212
+ this.ensureConnected();
213
+ throw new Error("signTransaction not yet implemented for embedded provider");
214
+ }
215
+ async signAndSendTransaction(transaction) {
216
+ this.ensureConnected();
217
+ const result = await this.provider.signAndSendTransaction({
218
+ transaction,
219
+ networkId: this.currentNetworkId
220
+ });
221
+ if (!result.hash) {
222
+ throw new Error("Transaction not submitted");
223
+ }
224
+ return { signature: result.hash };
225
+ }
226
+ async signAllTransactions(transactions) {
227
+ const results = await Promise.all(transactions.map((tx) => this.signTransaction(tx)));
228
+ return results;
229
+ }
230
+ connect(_options) {
231
+ if (!this.provider.isConnected()) {
232
+ throw new Error("Provider not connected. Call provider connect first.");
233
+ }
234
+ const addresses = this.provider.getAddresses();
235
+ const solanaAddr = addresses.find((a) => a.addressType === "Solana");
236
+ if (!solanaAddr)
237
+ throw new Error("No Solana address found");
238
+ this.updateConnectionState(true, solanaAddr.address);
239
+ return Promise.resolve({ publicKey: solanaAddr.address });
240
+ }
241
+ async disconnect() {
242
+ return this.provider.disconnect();
243
+ }
244
+ switchNetwork(network) {
245
+ this.currentNetworkId = network === "mainnet" ? import_constants.NetworkId.SOLANA_MAINNET : import_constants.NetworkId.SOLANA_DEVNET;
246
+ return Promise.resolve();
247
+ }
248
+ getPublicKey() {
249
+ if (!this.provider.isConnected())
250
+ return Promise.resolve(null);
251
+ const addresses = this.provider.getAddresses();
252
+ const solanaAddr = addresses.find((a) => a.addressType === "Solana");
253
+ return Promise.resolve(solanaAddr?.address || null);
254
+ }
255
+ isConnected() {
256
+ return this._connected && this.provider.isConnected();
257
+ }
258
+ setupEventListeners() {
259
+ this.provider.on("connect", (data) => {
260
+ const solanaAddress = data.addresses?.find((addr) => addr.addressType === "Solana");
261
+ if (solanaAddress) {
262
+ this.updateConnectionState(true, solanaAddress.address);
263
+ this.eventEmitter.emit("connect", solanaAddress.address);
264
+ }
265
+ });
266
+ this.provider.on("disconnect", () => {
267
+ this.updateConnectionState(false, null);
268
+ this.eventEmitter.emit("disconnect");
269
+ });
270
+ }
271
+ syncInitialState() {
272
+ if (this.provider.isConnected()) {
273
+ const addresses = this.provider.getAddresses();
274
+ const solanaAddress = addresses.find((a) => a.addressType === "Solana");
275
+ if (solanaAddress) {
276
+ this.updateConnectionState(true, solanaAddress.address);
277
+ }
278
+ }
279
+ }
280
+ updateConnectionState(connected, publicKey) {
281
+ this._connected = connected;
282
+ this._publicKey = publicKey;
283
+ }
284
+ // Event methods for interface compliance
285
+ on(event, listener) {
286
+ this.eventEmitter.on(event, listener);
287
+ }
288
+ off(event, listener) {
289
+ this.eventEmitter.off(event, listener);
290
+ }
291
+ };
292
+
293
+ // src/chains/EthereumChain.ts
294
+ var import_eventemitter32 = require("eventemitter3");
295
+ var import_constants2 = require("@phantom/constants");
296
+ var EmbeddedEthereumChain = class {
297
+ constructor(provider) {
298
+ this.provider = provider;
299
+ this.currentNetworkId = import_constants2.NetworkId.ETHEREUM_MAINNET;
300
+ this._connected = false;
301
+ this._accounts = [];
302
+ this.eventEmitter = new import_eventemitter32.EventEmitter();
303
+ this.setupEventListeners();
304
+ this.syncInitialState();
305
+ }
306
+ // EIP-1193 compliant properties
307
+ get connected() {
308
+ return this._connected;
309
+ }
310
+ get chainId() {
311
+ const chainId = (0, import_constants2.networkIdToChainId)(this.currentNetworkId) || 1;
312
+ return `0x${chainId.toString(16)}`;
313
+ }
314
+ get accounts() {
315
+ return this._accounts;
316
+ }
317
+ ensureConnected() {
318
+ if (!this.provider.isConnected()) {
319
+ throw new Error("Ethereum chain not available. Ensure SDK is connected.");
320
+ }
321
+ }
322
+ async request(args) {
323
+ this.ensureConnected();
324
+ return this.handleEmbeddedRequest(args);
325
+ }
326
+ // Connection methods
327
+ connect() {
328
+ if (!this.provider.isConnected()) {
329
+ throw new Error("Provider not connected. Call provider connect first.");
330
+ }
331
+ const addresses = this.provider.getAddresses();
332
+ const ethAddresses = addresses.filter((a) => a.addressType === "Ethereum").map((a) => a.address);
333
+ this.updateConnectionState(true, ethAddresses);
334
+ return Promise.resolve(ethAddresses);
335
+ }
336
+ async disconnect() {
337
+ await this.provider.disconnect();
338
+ }
339
+ // Standard compliant methods (return raw values)
340
+ async signPersonalMessage(message, address) {
341
+ return await this.request({
342
+ method: "personal_sign",
343
+ params: [message, address]
344
+ });
345
+ }
346
+ async signTypedData(typedData, address) {
347
+ return await this.request({
348
+ method: "eth_signTypedData_v4",
349
+ params: [address, JSON.stringify(typedData)]
350
+ });
351
+ }
352
+ async sendTransaction(transaction) {
353
+ const result = await this.provider.signAndSendTransaction({
354
+ transaction,
355
+ networkId: this.currentNetworkId
356
+ });
357
+ if (!result.hash) {
358
+ throw new Error("Transaction not submitted");
359
+ }
360
+ return result.hash;
361
+ }
362
+ switchChain(chainId) {
363
+ const networkId = (0, import_constants2.chainIdToNetworkId)(chainId);
364
+ if (!networkId) {
365
+ throw new Error(`Unsupported chainId: ${chainId}`);
366
+ }
367
+ this.currentNetworkId = networkId;
368
+ this.eventEmitter.emit("chainChanged", `0x${chainId.toString(16)}`);
369
+ return Promise.resolve();
370
+ }
371
+ getChainId() {
372
+ const chainId = (0, import_constants2.networkIdToChainId)(this.currentNetworkId);
373
+ return Promise.resolve(chainId || 1);
374
+ }
375
+ async getAccounts() {
376
+ return this.request({ method: "eth_accounts" });
377
+ }
378
+ isConnected() {
379
+ return this._connected && this.provider.isConnected();
380
+ }
381
+ setupEventListeners() {
382
+ this.provider.on("connect", (data) => {
383
+ const ethAddresses = data.addresses?.filter((addr) => addr.addressType === "Ethereum")?.map((addr) => addr.address) || [];
384
+ if (ethAddresses.length > 0) {
385
+ this.updateConnectionState(true, ethAddresses);
386
+ this.eventEmitter.emit("connect", { chainId: this.chainId });
387
+ this.eventEmitter.emit("accountsChanged", ethAddresses);
388
+ }
389
+ });
390
+ this.provider.on("disconnect", () => {
391
+ this.updateConnectionState(false, []);
392
+ this.eventEmitter.emit("disconnect", { code: 4900, message: "Provider disconnected" });
393
+ this.eventEmitter.emit("accountsChanged", []);
394
+ });
395
+ }
396
+ syncInitialState() {
397
+ if (this.provider.isConnected()) {
398
+ const addresses = this.provider.getAddresses();
399
+ const ethAddresses = addresses.filter((a) => a.addressType === "Ethereum").map((a) => a.address);
400
+ if (ethAddresses.length > 0) {
401
+ this.updateConnectionState(true, ethAddresses);
402
+ }
403
+ }
404
+ }
405
+ updateConnectionState(connected, accounts) {
406
+ this._connected = connected;
407
+ this._accounts = accounts;
408
+ }
409
+ async handleEmbeddedRequest(args) {
410
+ switch (args.method) {
411
+ case "personal_sign": {
412
+ const [message, _address] = args.params;
413
+ const result = await this.provider.signMessage({
414
+ message,
415
+ networkId: this.currentNetworkId
416
+ });
417
+ return result.signature;
418
+ }
419
+ case "eth_signTypedData_v4": {
420
+ const [_typedDataAddress, typedDataStr] = args.params;
421
+ const _typedData = JSON.parse(typedDataStr);
422
+ const typedDataResult = await this.provider.signMessage({
423
+ message: typedDataStr,
424
+ // Pass the stringified typed data as message
425
+ networkId: this.currentNetworkId
426
+ });
427
+ return typedDataResult.signature;
428
+ }
429
+ case "eth_sendTransaction": {
430
+ const [transaction] = args.params;
431
+ const networkIdFromTx = transaction.chainId ? (0, import_constants2.chainIdToNetworkId)(
432
+ typeof transaction.chainId === "number" ? transaction.chainId : parseInt(transaction.chainId, 16)
433
+ ) : null;
434
+ const sendResult = await this.provider.signAndSendTransaction({
435
+ transaction,
436
+ networkId: networkIdFromTx || this.currentNetworkId
437
+ });
438
+ return sendResult.hash;
439
+ }
440
+ case "eth_accounts": {
441
+ const addresses = this.provider.getAddresses();
442
+ const ethAddr = addresses.find((a) => a.addressType === "Ethereum");
443
+ return ethAddr ? [ethAddr.address] : [];
444
+ }
445
+ case "eth_chainId": {
446
+ return `0x${((0, import_constants2.networkIdToChainId)(this.currentNetworkId) || 1).toString(16)}`;
447
+ }
448
+ case "wallet_switchEthereumChain": {
449
+ const [{ chainId }] = args.params;
450
+ const numericChainId = parseInt(chainId, 16);
451
+ await this.switchChain(numericChainId);
452
+ return void 0;
453
+ }
454
+ default:
455
+ throw new Error(`Embedded provider doesn't support method: ${args.method}`);
456
+ }
457
+ }
458
+ // Event methods for interface compliance
459
+ on(event, listener) {
460
+ this.eventEmitter.on(event, listener);
461
+ }
462
+ off(event, listener) {
463
+ this.eventEmitter.off(event, listener);
464
+ }
465
+ };
466
+
168
467
  // src/embedded-provider.ts
169
468
  var EmbeddedProvider = class {
170
469
  constructor(config, platform, logger) {
@@ -182,6 +481,8 @@ var EmbeddedProvider = class {
182
481
  this.stamper = platform.stamper;
183
482
  this.jwtAuth = new JWTAuth();
184
483
  config.solanaProvider;
484
+ this.solana = new EmbeddedSolanaChain(this);
485
+ this.ethereum = new EmbeddedEthereumChain(this);
185
486
  this.logger.info("EMBEDDED_PROVIDER", "EmbeddedProvider initialized");
186
487
  }
187
488
  /*
@@ -278,24 +579,13 @@ var EmbeddedProvider = class {
278
579
  }
279
580
  /*
280
581
  * Shared connection logic for both connect() and autoConnect().
281
- * Handles redirect resume, existing session validation, and session initialization.
582
+ * Handles existing session validation, redirect resume, and session initialization.
282
583
  * Returns ConnectResult if connection succeeds, null if should continue with new auth flow.
283
584
  */
284
- async tryExistingConnection() {
585
+ async tryExistingConnection(isAutoConnect) {
285
586
  this.logger.log("EMBEDDED_PROVIDER", "Getting existing session");
286
587
  let session = await this.storage.getSession();
287
588
  session = await this.validateAndCleanSession(session);
288
- this.logger.log("EMBEDDED_PROVIDER", "Checking for redirect resume");
289
- if (this.authProvider.resumeAuthFromRedirect) {
290
- const authResult = this.authProvider.resumeAuthFromRedirect();
291
- if (authResult) {
292
- this.logger.info("EMBEDDED_PROVIDER", "Resuming from redirect", {
293
- walletId: authResult.walletId,
294
- provider: authResult.provider
295
- });
296
- return this.completeAuthConnection(authResult);
297
- }
298
- }
299
589
  if (session && session.status === "completed") {
300
590
  this.logger.info("EMBEDDED_PROVIDER", "Using existing completed session", {
301
591
  sessionId: session.sessionId,
@@ -321,6 +611,33 @@ var EmbeddedProvider = class {
321
611
  });
322
612
  return result;
323
613
  }
614
+ this.logger.log("EMBEDDED_PROVIDER", "No completed session found, checking for redirect resume");
615
+ if (this.authProvider.resumeAuthFromRedirect) {
616
+ const authResult = this.authProvider.resumeAuthFromRedirect();
617
+ if (authResult) {
618
+ this.logger.info("EMBEDDED_PROVIDER", "Resuming from redirect", {
619
+ walletId: authResult.walletId,
620
+ provider: authResult.provider
621
+ });
622
+ try {
623
+ return await this.completeAuthConnection(authResult);
624
+ } catch (error) {
625
+ if (error instanceof Error && error.message.includes("No session found after redirect") && !isAutoConnect) {
626
+ this.logger.warn(
627
+ "EMBEDDED_PROVIDER",
628
+ "Session missing during redirect resume - will start fresh auth flow",
629
+ {
630
+ error: error.message,
631
+ walletId: authResult.walletId
632
+ }
633
+ );
634
+ await this.storage.clearSession();
635
+ return null;
636
+ }
637
+ throw error;
638
+ }
639
+ }
640
+ }
324
641
  return null;
325
642
  }
326
643
  /*
@@ -387,7 +704,7 @@ var EmbeddedProvider = class {
387
704
  try {
388
705
  this.logger.log("EMBEDDED_PROVIDER", "Starting auto-connect attempt");
389
706
  this.emit("connect_start", { source: "auto-connect" });
390
- const result = await this.tryExistingConnection();
707
+ const result = await this.tryExistingConnection(true);
391
708
  if (result) {
392
709
  this.logger.info("EMBEDDED_PROVIDER", "Auto-connect successful", {
393
710
  walletId: result.walletId,
@@ -476,7 +793,7 @@ var EmbeddedProvider = class {
476
793
  source: "manual-connect",
477
794
  authOptions: authOptions ? { provider: authOptions.provider } : void 0
478
795
  });
479
- const existingResult = await this.tryExistingConnection();
796
+ const existingResult = await this.tryExistingConnection(false);
480
797
  if (existingResult) {
481
798
  this.logger.info("EMBEDDED_PROVIDER", "Manual connect using existing connection", {
482
799
  walletId: existingResult.walletId,
@@ -599,7 +916,7 @@ var EmbeddedProvider = class {
599
916
  walletId: this.walletId,
600
917
  networkId: params.networkId
601
918
  });
602
- const parsedTransaction = await (0, import_parsers.parseTransaction)(params.transaction, params.networkId);
919
+ const parsedTransaction = await (0, import_parsers.parseTransactionToBase64Url)(params.transaction, params.networkId);
603
920
  const session = await this.storage.getSession();
604
921
  const derivationIndex = session?.accountDerivationIndex ?? 0;
605
922
  this.logger.log("EMBEDDED_PROVIDER", "Parsed transaction for signing", {
@@ -665,6 +982,7 @@ var EmbeddedProvider = class {
665
982
  sessionId: generateSessionId(),
666
983
  walletId,
667
984
  organizationId,
985
+ appId: this.config.appId,
668
986
  stamperInfo,
669
987
  authProvider: "app-wallet",
670
988
  userInfo: { embeddedWalletType: this.config.embeddedWalletType },
@@ -696,6 +1014,7 @@ var EmbeddedProvider = class {
696
1014
  this.logger.log("EMBEDDED_PROVIDER", "Starting JWT authentication");
697
1015
  const authResult = await this.jwtAuth.authenticate({
698
1016
  organizationId,
1017
+ appId: this.config.appId,
699
1018
  parentOrganizationId: this.config.organizationId,
700
1019
  jwtToken: authOptions.jwtToken,
701
1020
  customAuthData: authOptions.customAuthData
@@ -707,6 +1026,7 @@ var EmbeddedProvider = class {
707
1026
  sessionId: generateSessionId(),
708
1027
  walletId,
709
1028
  organizationId,
1029
+ appId: this.config.appId,
710
1030
  stamperInfo,
711
1031
  authProvider: authResult.provider,
712
1032
  userInfo: authResult.userInfo,
@@ -741,6 +1061,7 @@ var EmbeddedProvider = class {
741
1061
  walletId: `temp-${now}`,
742
1062
  // Temporary ID, will be updated after redirect
743
1063
  organizationId,
1064
+ appId: this.config.appId,
744
1065
  stamperInfo,
745
1066
  authProvider: "phantom-connect",
746
1067
  userInfo: { provider: authOptions?.provider },
@@ -763,19 +1084,19 @@ var EmbeddedProvider = class {
763
1084
  this.logger.info("EMBEDDED_PROVIDER", "Starting Phantom Connect redirect", {
764
1085
  organizationId,
765
1086
  parentOrganizationId: this.config.organizationId,
1087
+ appId: this.config.appId,
766
1088
  provider: authOptions?.provider,
767
1089
  authUrl: this.config.authOptions?.authUrl
768
1090
  });
769
1091
  const authResult = await this.authProvider.authenticate({
770
1092
  organizationId,
1093
+ appId: this.config.appId,
771
1094
  parentOrganizationId: this.config.organizationId,
772
1095
  provider: authOptions?.provider,
773
1096
  redirectUrl: this.config.authOptions?.redirectUrl,
774
1097
  customAuthData: authOptions?.customAuthData,
775
1098
  authUrl: this.config.authOptions?.authUrl,
776
- sessionId,
777
- appName: this.config.appName,
778
- appLogo: this.config.appLogo
1099
+ sessionId
779
1100
  });
780
1101
  if (authResult && "walletId" in authResult) {
781
1102
  this.logger.info("EMBEDDED_PROVIDER", "Authentication completed after redirect", {
@@ -814,7 +1135,7 @@ var EmbeddedProvider = class {
814
1135
  }
815
1136
  /*
816
1137
  * Ensures the authenticator is valid and performs renewal if needed.
817
- * The renewal of the authenticator can only happen meanwhile the previous authenticator is still valid.
1138
+ * The renewal of the authenticator can only happen meanwhile the previous authenticator is still valid.
818
1139
  */
819
1140
  async ensureValidAuthenticator() {
820
1141
  const session = await this.storage.getSession();
@@ -892,7 +1213,9 @@ var EmbeddedProvider = class {
892
1213
  error: error instanceof Error ? error.message : String(error)
893
1214
  });
894
1215
  await this.stamper.rollbackRotation();
895
- throw new Error(`Failed to create new authenticator: ${error instanceof Error ? error.message : String(error)}`);
1216
+ throw new Error(
1217
+ `Failed to create new authenticator: ${error instanceof Error ? error.message : String(error)}`
1218
+ );
896
1219
  }
897
1220
  this.logger.info("EMBEDDED_PROVIDER", "Created new authenticator", {
898
1221
  authenticatorId: authenticatorResult.id
@@ -920,7 +1243,8 @@ var EmbeddedProvider = class {
920
1243
  async initializeClientFromSession(session) {
921
1244
  this.logger.log("EMBEDDED_PROVIDER", "Initializing PhantomClient from session", {
922
1245
  organizationId: session.organizationId,
923
- walletId: session.walletId
1246
+ walletId: session.walletId,
1247
+ appId: session.appId
924
1248
  });
925
1249
  if (!this.stamper.getKeyInfo()) {
926
1250
  await this.stamper.init();
@@ -940,7 +1264,9 @@ var EmbeddedProvider = class {
940
1264
  0 && (module.exports = {
941
1265
  AUTHENTICATOR_EXPIRATION_TIME_MS,
942
1266
  AUTHENTICATOR_RENEWAL_WINDOW_MS,
1267
+ EmbeddedEthereumChain,
943
1268
  EmbeddedProvider,
1269
+ EmbeddedSolanaChain,
944
1270
  JWTAuth,
945
1271
  generateSessionId,
946
1272
  retryWithBackoff