@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.mjs CHANGED
@@ -4,7 +4,7 @@ import { base64urlEncode } from "@phantom/base64url";
4
4
  import bs58 from "bs58";
5
5
  import {
6
6
  parseMessage,
7
- parseTransaction,
7
+ parseTransactionToBase64Url,
8
8
  parseSignMessageResponse,
9
9
  parseTransactionResponse
10
10
  } from "@phantom/parsers";
@@ -28,7 +28,8 @@ var JWTAuth = class {
28
28
  method: "POST",
29
29
  headers: {
30
30
  "Content-Type": "application/json",
31
- Authorization: `Bearer ${options.jwtToken}`
31
+ Authorization: `Bearer ${options.jwtToken}`,
32
+ "X-PHANTOM-APPID": options.appId
32
33
  },
33
34
  body: JSON.stringify({
34
35
  organizationId: options.organizationId,
@@ -129,6 +130,302 @@ async function retryWithBackoff(operation, operationName, logger, maxRetries = 3
129
130
  throw lastError;
130
131
  }
131
132
 
133
+ // src/chains/SolanaChain.ts
134
+ import { EventEmitter } from "eventemitter3";
135
+ import { NetworkId } from "@phantom/constants";
136
+ import { Buffer } from "buffer";
137
+ var EmbeddedSolanaChain = class {
138
+ constructor(provider) {
139
+ this.provider = provider;
140
+ this.currentNetworkId = NetworkId.SOLANA_MAINNET;
141
+ this._connected = false;
142
+ this._publicKey = null;
143
+ this.eventEmitter = new EventEmitter();
144
+ this.setupEventListeners();
145
+ this.syncInitialState();
146
+ }
147
+ // Wallet adapter compliant properties
148
+ get connected() {
149
+ return this._connected;
150
+ }
151
+ get publicKey() {
152
+ return this._publicKey;
153
+ }
154
+ ensureConnected() {
155
+ if (!this.provider.isConnected()) {
156
+ throw new Error("Solana chain not available. Ensure SDK is connected.");
157
+ }
158
+ }
159
+ // Standard wallet adapter methods
160
+ async signMessage(message) {
161
+ this.ensureConnected();
162
+ const messageStr = typeof message === "string" ? message : new TextDecoder().decode(message);
163
+ const result = await this.provider.signMessage({
164
+ message: messageStr,
165
+ networkId: this.currentNetworkId
166
+ });
167
+ const signature = typeof result.signature === "string" ? new Uint8Array(Buffer.from(result.signature, "base64")) : result.signature;
168
+ return {
169
+ signature,
170
+ publicKey: this._publicKey || ""
171
+ };
172
+ }
173
+ signTransaction(_transaction) {
174
+ this.ensureConnected();
175
+ throw new Error("signTransaction not yet implemented for embedded provider");
176
+ }
177
+ async signAndSendTransaction(transaction) {
178
+ this.ensureConnected();
179
+ const result = await this.provider.signAndSendTransaction({
180
+ transaction,
181
+ networkId: this.currentNetworkId
182
+ });
183
+ if (!result.hash) {
184
+ throw new Error("Transaction not submitted");
185
+ }
186
+ return { signature: result.hash };
187
+ }
188
+ async signAllTransactions(transactions) {
189
+ const results = await Promise.all(transactions.map((tx) => this.signTransaction(tx)));
190
+ return results;
191
+ }
192
+ connect(_options) {
193
+ if (!this.provider.isConnected()) {
194
+ throw new Error("Provider not connected. Call provider connect first.");
195
+ }
196
+ const addresses = this.provider.getAddresses();
197
+ const solanaAddr = addresses.find((a) => a.addressType === "Solana");
198
+ if (!solanaAddr)
199
+ throw new Error("No Solana address found");
200
+ this.updateConnectionState(true, solanaAddr.address);
201
+ return Promise.resolve({ publicKey: solanaAddr.address });
202
+ }
203
+ async disconnect() {
204
+ return this.provider.disconnect();
205
+ }
206
+ switchNetwork(network) {
207
+ this.currentNetworkId = network === "mainnet" ? NetworkId.SOLANA_MAINNET : NetworkId.SOLANA_DEVNET;
208
+ return Promise.resolve();
209
+ }
210
+ getPublicKey() {
211
+ if (!this.provider.isConnected())
212
+ return Promise.resolve(null);
213
+ const addresses = this.provider.getAddresses();
214
+ const solanaAddr = addresses.find((a) => a.addressType === "Solana");
215
+ return Promise.resolve(solanaAddr?.address || null);
216
+ }
217
+ isConnected() {
218
+ return this._connected && this.provider.isConnected();
219
+ }
220
+ setupEventListeners() {
221
+ this.provider.on("connect", (data) => {
222
+ const solanaAddress = data.addresses?.find((addr) => addr.addressType === "Solana");
223
+ if (solanaAddress) {
224
+ this.updateConnectionState(true, solanaAddress.address);
225
+ this.eventEmitter.emit("connect", solanaAddress.address);
226
+ }
227
+ });
228
+ this.provider.on("disconnect", () => {
229
+ this.updateConnectionState(false, null);
230
+ this.eventEmitter.emit("disconnect");
231
+ });
232
+ }
233
+ syncInitialState() {
234
+ if (this.provider.isConnected()) {
235
+ const addresses = this.provider.getAddresses();
236
+ const solanaAddress = addresses.find((a) => a.addressType === "Solana");
237
+ if (solanaAddress) {
238
+ this.updateConnectionState(true, solanaAddress.address);
239
+ }
240
+ }
241
+ }
242
+ updateConnectionState(connected, publicKey) {
243
+ this._connected = connected;
244
+ this._publicKey = publicKey;
245
+ }
246
+ // Event methods for interface compliance
247
+ on(event, listener) {
248
+ this.eventEmitter.on(event, listener);
249
+ }
250
+ off(event, listener) {
251
+ this.eventEmitter.off(event, listener);
252
+ }
253
+ };
254
+
255
+ // src/chains/EthereumChain.ts
256
+ import { EventEmitter as EventEmitter2 } from "eventemitter3";
257
+ import { NetworkId as NetworkId2, chainIdToNetworkId, networkIdToChainId } from "@phantom/constants";
258
+ var EmbeddedEthereumChain = class {
259
+ constructor(provider) {
260
+ this.provider = provider;
261
+ this.currentNetworkId = NetworkId2.ETHEREUM_MAINNET;
262
+ this._connected = false;
263
+ this._accounts = [];
264
+ this.eventEmitter = new EventEmitter2();
265
+ this.setupEventListeners();
266
+ this.syncInitialState();
267
+ }
268
+ // EIP-1193 compliant properties
269
+ get connected() {
270
+ return this._connected;
271
+ }
272
+ get chainId() {
273
+ const chainId = networkIdToChainId(this.currentNetworkId) || 1;
274
+ return `0x${chainId.toString(16)}`;
275
+ }
276
+ get accounts() {
277
+ return this._accounts;
278
+ }
279
+ ensureConnected() {
280
+ if (!this.provider.isConnected()) {
281
+ throw new Error("Ethereum chain not available. Ensure SDK is connected.");
282
+ }
283
+ }
284
+ async request(args) {
285
+ this.ensureConnected();
286
+ return this.handleEmbeddedRequest(args);
287
+ }
288
+ // Connection methods
289
+ connect() {
290
+ if (!this.provider.isConnected()) {
291
+ throw new Error("Provider not connected. Call provider connect first.");
292
+ }
293
+ const addresses = this.provider.getAddresses();
294
+ const ethAddresses = addresses.filter((a) => a.addressType === "Ethereum").map((a) => a.address);
295
+ this.updateConnectionState(true, ethAddresses);
296
+ return Promise.resolve(ethAddresses);
297
+ }
298
+ async disconnect() {
299
+ await this.provider.disconnect();
300
+ }
301
+ // Standard compliant methods (return raw values)
302
+ async signPersonalMessage(message, address) {
303
+ return await this.request({
304
+ method: "personal_sign",
305
+ params: [message, address]
306
+ });
307
+ }
308
+ async signTypedData(typedData, address) {
309
+ return await this.request({
310
+ method: "eth_signTypedData_v4",
311
+ params: [address, JSON.stringify(typedData)]
312
+ });
313
+ }
314
+ async sendTransaction(transaction) {
315
+ const result = await this.provider.signAndSendTransaction({
316
+ transaction,
317
+ networkId: this.currentNetworkId
318
+ });
319
+ if (!result.hash) {
320
+ throw new Error("Transaction not submitted");
321
+ }
322
+ return result.hash;
323
+ }
324
+ switchChain(chainId) {
325
+ const networkId = chainIdToNetworkId(chainId);
326
+ if (!networkId) {
327
+ throw new Error(`Unsupported chainId: ${chainId}`);
328
+ }
329
+ this.currentNetworkId = networkId;
330
+ this.eventEmitter.emit("chainChanged", `0x${chainId.toString(16)}`);
331
+ return Promise.resolve();
332
+ }
333
+ getChainId() {
334
+ const chainId = networkIdToChainId(this.currentNetworkId);
335
+ return Promise.resolve(chainId || 1);
336
+ }
337
+ async getAccounts() {
338
+ return this.request({ method: "eth_accounts" });
339
+ }
340
+ isConnected() {
341
+ return this._connected && this.provider.isConnected();
342
+ }
343
+ setupEventListeners() {
344
+ this.provider.on("connect", (data) => {
345
+ const ethAddresses = data.addresses?.filter((addr) => addr.addressType === "Ethereum")?.map((addr) => addr.address) || [];
346
+ if (ethAddresses.length > 0) {
347
+ this.updateConnectionState(true, ethAddresses);
348
+ this.eventEmitter.emit("connect", { chainId: this.chainId });
349
+ this.eventEmitter.emit("accountsChanged", ethAddresses);
350
+ }
351
+ });
352
+ this.provider.on("disconnect", () => {
353
+ this.updateConnectionState(false, []);
354
+ this.eventEmitter.emit("disconnect", { code: 4900, message: "Provider disconnected" });
355
+ this.eventEmitter.emit("accountsChanged", []);
356
+ });
357
+ }
358
+ syncInitialState() {
359
+ if (this.provider.isConnected()) {
360
+ const addresses = this.provider.getAddresses();
361
+ const ethAddresses = addresses.filter((a) => a.addressType === "Ethereum").map((a) => a.address);
362
+ if (ethAddresses.length > 0) {
363
+ this.updateConnectionState(true, ethAddresses);
364
+ }
365
+ }
366
+ }
367
+ updateConnectionState(connected, accounts) {
368
+ this._connected = connected;
369
+ this._accounts = accounts;
370
+ }
371
+ async handleEmbeddedRequest(args) {
372
+ switch (args.method) {
373
+ case "personal_sign": {
374
+ const [message, _address] = args.params;
375
+ const result = await this.provider.signMessage({
376
+ message,
377
+ networkId: this.currentNetworkId
378
+ });
379
+ return result.signature;
380
+ }
381
+ case "eth_signTypedData_v4": {
382
+ const [_typedDataAddress, typedDataStr] = args.params;
383
+ const _typedData = JSON.parse(typedDataStr);
384
+ const typedDataResult = await this.provider.signMessage({
385
+ message: typedDataStr,
386
+ // Pass the stringified typed data as message
387
+ networkId: this.currentNetworkId
388
+ });
389
+ return typedDataResult.signature;
390
+ }
391
+ case "eth_sendTransaction": {
392
+ const [transaction] = args.params;
393
+ const networkIdFromTx = transaction.chainId ? chainIdToNetworkId(
394
+ typeof transaction.chainId === "number" ? transaction.chainId : parseInt(transaction.chainId, 16)
395
+ ) : null;
396
+ const sendResult = await this.provider.signAndSendTransaction({
397
+ transaction,
398
+ networkId: networkIdFromTx || this.currentNetworkId
399
+ });
400
+ return sendResult.hash;
401
+ }
402
+ case "eth_accounts": {
403
+ const addresses = this.provider.getAddresses();
404
+ const ethAddr = addresses.find((a) => a.addressType === "Ethereum");
405
+ return ethAddr ? [ethAddr.address] : [];
406
+ }
407
+ case "eth_chainId": {
408
+ return `0x${(networkIdToChainId(this.currentNetworkId) || 1).toString(16)}`;
409
+ }
410
+ case "wallet_switchEthereumChain": {
411
+ const [{ chainId }] = args.params;
412
+ const numericChainId = parseInt(chainId, 16);
413
+ await this.switchChain(numericChainId);
414
+ return void 0;
415
+ }
416
+ default:
417
+ throw new Error(`Embedded provider doesn't support method: ${args.method}`);
418
+ }
419
+ }
420
+ // Event methods for interface compliance
421
+ on(event, listener) {
422
+ this.eventEmitter.on(event, listener);
423
+ }
424
+ off(event, listener) {
425
+ this.eventEmitter.off(event, listener);
426
+ }
427
+ };
428
+
132
429
  // src/embedded-provider.ts
133
430
  var EmbeddedProvider = class {
134
431
  constructor(config, platform, logger) {
@@ -146,6 +443,8 @@ var EmbeddedProvider = class {
146
443
  this.stamper = platform.stamper;
147
444
  this.jwtAuth = new JWTAuth();
148
445
  config.solanaProvider;
446
+ this.solana = new EmbeddedSolanaChain(this);
447
+ this.ethereum = new EmbeddedEthereumChain(this);
149
448
  this.logger.info("EMBEDDED_PROVIDER", "EmbeddedProvider initialized");
150
449
  }
151
450
  /*
@@ -242,24 +541,13 @@ var EmbeddedProvider = class {
242
541
  }
243
542
  /*
244
543
  * Shared connection logic for both connect() and autoConnect().
245
- * Handles redirect resume, existing session validation, and session initialization.
544
+ * Handles existing session validation, redirect resume, and session initialization.
246
545
  * Returns ConnectResult if connection succeeds, null if should continue with new auth flow.
247
546
  */
248
- async tryExistingConnection() {
547
+ async tryExistingConnection(isAutoConnect) {
249
548
  this.logger.log("EMBEDDED_PROVIDER", "Getting existing session");
250
549
  let session = await this.storage.getSession();
251
550
  session = await this.validateAndCleanSession(session);
252
- this.logger.log("EMBEDDED_PROVIDER", "Checking for redirect resume");
253
- if (this.authProvider.resumeAuthFromRedirect) {
254
- const authResult = this.authProvider.resumeAuthFromRedirect();
255
- if (authResult) {
256
- this.logger.info("EMBEDDED_PROVIDER", "Resuming from redirect", {
257
- walletId: authResult.walletId,
258
- provider: authResult.provider
259
- });
260
- return this.completeAuthConnection(authResult);
261
- }
262
- }
263
551
  if (session && session.status === "completed") {
264
552
  this.logger.info("EMBEDDED_PROVIDER", "Using existing completed session", {
265
553
  sessionId: session.sessionId,
@@ -285,6 +573,33 @@ var EmbeddedProvider = class {
285
573
  });
286
574
  return result;
287
575
  }
576
+ this.logger.log("EMBEDDED_PROVIDER", "No completed session found, checking for redirect resume");
577
+ if (this.authProvider.resumeAuthFromRedirect) {
578
+ const authResult = this.authProvider.resumeAuthFromRedirect();
579
+ if (authResult) {
580
+ this.logger.info("EMBEDDED_PROVIDER", "Resuming from redirect", {
581
+ walletId: authResult.walletId,
582
+ provider: authResult.provider
583
+ });
584
+ try {
585
+ return await this.completeAuthConnection(authResult);
586
+ } catch (error) {
587
+ if (error instanceof Error && error.message.includes("No session found after redirect") && !isAutoConnect) {
588
+ this.logger.warn(
589
+ "EMBEDDED_PROVIDER",
590
+ "Session missing during redirect resume - will start fresh auth flow",
591
+ {
592
+ error: error.message,
593
+ walletId: authResult.walletId
594
+ }
595
+ );
596
+ await this.storage.clearSession();
597
+ return null;
598
+ }
599
+ throw error;
600
+ }
601
+ }
602
+ }
288
603
  return null;
289
604
  }
290
605
  /*
@@ -351,7 +666,7 @@ var EmbeddedProvider = class {
351
666
  try {
352
667
  this.logger.log("EMBEDDED_PROVIDER", "Starting auto-connect attempt");
353
668
  this.emit("connect_start", { source: "auto-connect" });
354
- const result = await this.tryExistingConnection();
669
+ const result = await this.tryExistingConnection(true);
355
670
  if (result) {
356
671
  this.logger.info("EMBEDDED_PROVIDER", "Auto-connect successful", {
357
672
  walletId: result.walletId,
@@ -440,7 +755,7 @@ var EmbeddedProvider = class {
440
755
  source: "manual-connect",
441
756
  authOptions: authOptions ? { provider: authOptions.provider } : void 0
442
757
  });
443
- const existingResult = await this.tryExistingConnection();
758
+ const existingResult = await this.tryExistingConnection(false);
444
759
  if (existingResult) {
445
760
  this.logger.info("EMBEDDED_PROVIDER", "Manual connect using existing connection", {
446
761
  walletId: existingResult.walletId,
@@ -563,7 +878,7 @@ var EmbeddedProvider = class {
563
878
  walletId: this.walletId,
564
879
  networkId: params.networkId
565
880
  });
566
- const parsedTransaction = await parseTransaction(params.transaction, params.networkId);
881
+ const parsedTransaction = await parseTransactionToBase64Url(params.transaction, params.networkId);
567
882
  const session = await this.storage.getSession();
568
883
  const derivationIndex = session?.accountDerivationIndex ?? 0;
569
884
  this.logger.log("EMBEDDED_PROVIDER", "Parsed transaction for signing", {
@@ -629,6 +944,7 @@ var EmbeddedProvider = class {
629
944
  sessionId: generateSessionId(),
630
945
  walletId,
631
946
  organizationId,
947
+ appId: this.config.appId,
632
948
  stamperInfo,
633
949
  authProvider: "app-wallet",
634
950
  userInfo: { embeddedWalletType: this.config.embeddedWalletType },
@@ -660,6 +976,7 @@ var EmbeddedProvider = class {
660
976
  this.logger.log("EMBEDDED_PROVIDER", "Starting JWT authentication");
661
977
  const authResult = await this.jwtAuth.authenticate({
662
978
  organizationId,
979
+ appId: this.config.appId,
663
980
  parentOrganizationId: this.config.organizationId,
664
981
  jwtToken: authOptions.jwtToken,
665
982
  customAuthData: authOptions.customAuthData
@@ -671,6 +988,7 @@ var EmbeddedProvider = class {
671
988
  sessionId: generateSessionId(),
672
989
  walletId,
673
990
  organizationId,
991
+ appId: this.config.appId,
674
992
  stamperInfo,
675
993
  authProvider: authResult.provider,
676
994
  userInfo: authResult.userInfo,
@@ -705,6 +1023,7 @@ var EmbeddedProvider = class {
705
1023
  walletId: `temp-${now}`,
706
1024
  // Temporary ID, will be updated after redirect
707
1025
  organizationId,
1026
+ appId: this.config.appId,
708
1027
  stamperInfo,
709
1028
  authProvider: "phantom-connect",
710
1029
  userInfo: { provider: authOptions?.provider },
@@ -727,19 +1046,19 @@ var EmbeddedProvider = class {
727
1046
  this.logger.info("EMBEDDED_PROVIDER", "Starting Phantom Connect redirect", {
728
1047
  organizationId,
729
1048
  parentOrganizationId: this.config.organizationId,
1049
+ appId: this.config.appId,
730
1050
  provider: authOptions?.provider,
731
1051
  authUrl: this.config.authOptions?.authUrl
732
1052
  });
733
1053
  const authResult = await this.authProvider.authenticate({
734
1054
  organizationId,
1055
+ appId: this.config.appId,
735
1056
  parentOrganizationId: this.config.organizationId,
736
1057
  provider: authOptions?.provider,
737
1058
  redirectUrl: this.config.authOptions?.redirectUrl,
738
1059
  customAuthData: authOptions?.customAuthData,
739
1060
  authUrl: this.config.authOptions?.authUrl,
740
- sessionId,
741
- appName: this.config.appName,
742
- appLogo: this.config.appLogo
1061
+ sessionId
743
1062
  });
744
1063
  if (authResult && "walletId" in authResult) {
745
1064
  this.logger.info("EMBEDDED_PROVIDER", "Authentication completed after redirect", {
@@ -778,7 +1097,7 @@ var EmbeddedProvider = class {
778
1097
  }
779
1098
  /*
780
1099
  * Ensures the authenticator is valid and performs renewal if needed.
781
- * The renewal of the authenticator can only happen meanwhile the previous authenticator is still valid.
1100
+ * The renewal of the authenticator can only happen meanwhile the previous authenticator is still valid.
782
1101
  */
783
1102
  async ensureValidAuthenticator() {
784
1103
  const session = await this.storage.getSession();
@@ -856,7 +1175,9 @@ var EmbeddedProvider = class {
856
1175
  error: error instanceof Error ? error.message : String(error)
857
1176
  });
858
1177
  await this.stamper.rollbackRotation();
859
- throw new Error(`Failed to create new authenticator: ${error instanceof Error ? error.message : String(error)}`);
1178
+ throw new Error(
1179
+ `Failed to create new authenticator: ${error instanceof Error ? error.message : String(error)}`
1180
+ );
860
1181
  }
861
1182
  this.logger.info("EMBEDDED_PROVIDER", "Created new authenticator", {
862
1183
  authenticatorId: authenticatorResult.id
@@ -884,7 +1205,8 @@ var EmbeddedProvider = class {
884
1205
  async initializeClientFromSession(session) {
885
1206
  this.logger.log("EMBEDDED_PROVIDER", "Initializing PhantomClient from session", {
886
1207
  organizationId: session.organizationId,
887
- walletId: session.walletId
1208
+ walletId: session.walletId,
1209
+ appId: session.appId
888
1210
  });
889
1211
  if (!this.stamper.getKeyInfo()) {
890
1212
  await this.stamper.init();
@@ -903,7 +1225,9 @@ var EmbeddedProvider = class {
903
1225
  export {
904
1226
  AUTHENTICATOR_EXPIRATION_TIME_MS,
905
1227
  AUTHENTICATOR_RENEWAL_WINDOW_MS,
1228
+ EmbeddedEthereumChain,
906
1229
  EmbeddedProvider,
1230
+ EmbeddedSolanaChain,
907
1231
  JWTAuth,
908
1232
  generateSessionId,
909
1233
  retryWithBackoff