@d13co/use-wallet 4.5.4 → 4.5.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/dist/index.js CHANGED
@@ -5736,10 +5736,9 @@ var WalletId = /* @__PURE__ */ ((WalletId2) => {
5736
5736
  WalletId2["KMD"] = "kmd";
5737
5737
  WalletId2["LUTE"] = "lute";
5738
5738
  WalletId2["MAGIC"] = "magic";
5739
- WalletId2["METAMASK"] = "metamask";
5740
5739
  WalletId2["MNEMONIC"] = "mnemonic";
5741
5740
  WalletId2["PERA"] = "pera";
5742
- WalletId2["RAINBOW"] = "rainbow";
5741
+ WalletId2["RAINBOWKIT"] = "rainbowkit";
5743
5742
  WalletId2["WALLETCONNECT"] = "walletconnect";
5744
5743
  WalletId2["WEB3AUTH"] = "web3auth";
5745
5744
  WalletId2["W3_WALLET"] = "w3-wallet";
@@ -5981,6 +5980,14 @@ var BaseWallet = class {
5981
5980
  return networkConfig[activeNetwork];
5982
5981
  }
5983
5982
  // ---------- Protected Methods ------------------------------------- //
5983
+ /**
5984
+ * Dynamically update wallet metadata (e.g., after learning the actual
5985
+ * connector name/icon during connect).
5986
+ */
5987
+ updateMetadata(updates) {
5988
+ ;
5989
+ this.metadata = { ...this.metadata, ...updates };
5990
+ }
5984
5991
  onDisconnect = () => {
5985
5992
  this.logger.debug(`Removing wallet from store...`);
5986
5993
  removeWallet(this.store, { walletId: this.walletKey });
@@ -8194,109 +8201,142 @@ var MagicAuth = class extends BaseWallet {
8194
8201
  };
8195
8202
  };
8196
8203
 
8197
- // src/wallets/liquid-evm-base.ts
8204
+ // src/wallets/mnemonic.ts
8198
8205
  import algosdk9 from "algosdk";
8199
- var LiquidEvmBaseWallet = class extends BaseWallet {
8200
- liquidEvmSdk = null;
8201
- algorandClient = null;
8202
- evmAddressMap = /* @__PURE__ */ new Map();
8203
- // algorandAddress -> evmAddress
8206
+ var LOCAL_STORAGE_MNEMONIC_KEY = `${LOCAL_STORAGE_KEY}_mnemonic`;
8207
+ var ICON11 = `data:image/svg+xml;base64,${btoa(`
8208
+ <svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
8209
+ <rect fill="#525252" width="400" height="400" />
8210
+ <path fill="#FFFFFF" d="M309.2,309.3H275l-22.2-82.7l-47.9,82.7h-38.3l73.9-128l-11.9-44.5l-99.6,172.6H90.8L217.1,90.6 h33.5l14.7,54.3h34.6l-23.6,41L309.2,309.3z" />
8211
+ </svg>
8212
+ `)}`;
8213
+ var MnemonicWallet = class extends BaseWallet {
8214
+ account = null;
8204
8215
  options;
8205
8216
  store;
8206
- constructor(params) {
8207
- super(params);
8208
- this.options = params.options || {};
8209
- this.store = params.store;
8210
- }
8211
- /**
8212
- * Default metadata for Liquid EVM wallets.
8213
- * Subclasses MUST override this with their own metadata including isLiquid: "EVM"
8214
- */
8215
- static defaultMetadata;
8216
- /**
8217
- * Ensure the wallet is on the Algorand chain (4160).
8218
- * Queries the current chain first, and only switches/adds if needed.
8219
- */
8220
- async ensureAlgorandChain() {
8221
- const provider = await this.getEvmProvider();
8222
- const { ALGORAND_CHAIN_ID_HEX, ALGORAND_EVM_CHAIN_CONFIG } = await import("liquid-accounts-evm");
8223
- const currentChainId = await provider.request({ method: "eth_chainId" });
8224
- if (currentChainId === ALGORAND_CHAIN_ID_HEX) {
8225
- return;
8217
+ constructor({
8218
+ id,
8219
+ store,
8220
+ subscribe,
8221
+ getAlgodClient,
8222
+ options,
8223
+ metadata = {}
8224
+ }) {
8225
+ super({ id, metadata, getAlgodClient, store, subscribe });
8226
+ const {
8227
+ persistToStorage = false,
8228
+ promptForMnemonic = () => Promise.resolve(prompt("Enter 25-word mnemonic passphrase:"))
8229
+ } = options || {};
8230
+ this.options = { persistToStorage, promptForMnemonic };
8231
+ this.store = store;
8232
+ if (this.options.persistToStorage) {
8233
+ this.logger.warn(
8234
+ "Persisting mnemonics to storage is insecure. Any private key mnemonics used should never hold real Algos (i.e., on MainNet). Use with caution!"
8235
+ );
8226
8236
  }
8227
- this.logger.info(
8228
- `Wrong chain (${currentChainId}), switching to Algorand (${ALGORAND_CHAIN_ID_HEX})...`
8229
- );
8237
+ }
8238
+ static defaultMetadata = {
8239
+ name: "Mnemonic",
8240
+ icon: ICON11
8241
+ };
8242
+ loadMnemonicFromStorage() {
8243
+ return StorageAdapter.getItem(LOCAL_STORAGE_MNEMONIC_KEY);
8244
+ }
8245
+ saveMnemonicToStorage(mnemonic) {
8246
+ StorageAdapter.setItem(LOCAL_STORAGE_MNEMONIC_KEY, mnemonic);
8247
+ }
8248
+ removeMnemonicFromStorage() {
8249
+ StorageAdapter.removeItem(LOCAL_STORAGE_MNEMONIC_KEY);
8250
+ }
8251
+ checkMainnet() {
8230
8252
  try {
8231
- await provider.request({
8232
- method: "wallet_switchEthereumChain",
8233
- params: [{ chainId: ALGORAND_CHAIN_ID_HEX }]
8234
- });
8235
- } catch (switchError) {
8236
- if (switchError.code === 4902) {
8237
- this.logger.info("Algorand chain not found, adding it...");
8238
- await provider.request({
8239
- method: "wallet_addEthereumChain",
8240
- params: [ALGORAND_EVM_CHAIN_CONFIG]
8241
- });
8242
- } else {
8243
- throw switchError;
8253
+ const network = this.activeNetworkConfig;
8254
+ if (!network.isTestnet) {
8255
+ this.logger.warn(
8256
+ "The Mnemonic wallet provider is insecure and intended for testing only. Any private key mnemonics used should never hold real Algos (i.e., on MainNet)."
8257
+ );
8258
+ throw new Error("Production network detected. Aborting.");
8244
8259
  }
8260
+ } catch (error) {
8261
+ this.disconnect();
8262
+ throw error;
8245
8263
  }
8246
8264
  }
8247
- /**
8248
- * Initialize the Liquid EVM SDK for deriving Algorand addresses
8249
- */
8250
- async initializeEvmSdk() {
8251
- if (!this.liquidEvmSdk) {
8252
- this.logger.info("Initializing Liquid EVM SDK...");
8253
- if (!this.algorandClient) {
8254
- const { AlgorandClient } = await import("@algorandfoundation/algokit-utils");
8255
- const algodClient = this.getAlgodClient();
8256
- this.algorandClient = AlgorandClient.fromClients({
8257
- algod: algodClient
8258
- });
8265
+ async initializeAccount() {
8266
+ let mnemonic = this.loadMnemonicFromStorage();
8267
+ if (!mnemonic) {
8268
+ mnemonic = await this.options.promptForMnemonic();
8269
+ if (!mnemonic) {
8270
+ this.account = null;
8271
+ this.logger.error("No mnemonic provided");
8272
+ throw new Error("No mnemonic provided");
8273
+ }
8274
+ if (this.options.persistToStorage) {
8275
+ this.logger.warn("Mnemonic saved to localStorage.");
8276
+ this.saveMnemonicToStorage(mnemonic);
8259
8277
  }
8260
- const { LiquidEvmSdk } = await import("liquid-accounts-evm");
8261
- this.liquidEvmSdk = new LiquidEvmSdk({ algorand: this.algorandClient });
8262
- this.logger.info("Liquid EVM SDK initialized");
8263
8278
  }
8264
- return this.liquidEvmSdk;
8279
+ const account = algosdk9.mnemonicToSecretKey(mnemonic);
8280
+ this.account = account;
8281
+ return account;
8265
8282
  }
8266
- /**
8267
- * Derive Algorand accounts from EVM addresses
8268
- */
8269
- async deriveAlgorandAccounts(evmAddresses) {
8270
- const liquidEvmSdk = await this.initializeEvmSdk();
8271
- const walletAccounts = [];
8272
- for (let i = 0; i < evmAddresses.length; i++) {
8273
- const evmAddress = evmAddresses[i];
8274
- const algorandAddress = await liquidEvmSdk.getAddress({ evmAddress });
8275
- this.evmAddressMap.set(algorandAddress, evmAddress);
8276
- walletAccounts.push({
8277
- name: `${this.metadata.name} ${evmAddress}`,
8278
- address: algorandAddress,
8279
- metadata: { evmAddress }
8280
- });
8283
+ connect = async () => {
8284
+ this.checkMainnet();
8285
+ this.logger.info("Connecting...");
8286
+ const account = await this.initializeAccount();
8287
+ const walletAccount = {
8288
+ name: `${this.metadata.name} Account`,
8289
+ address: account.addr.toString()
8290
+ };
8291
+ const walletState = {
8292
+ accounts: [walletAccount],
8293
+ activeAccount: walletAccount
8294
+ };
8295
+ addWallet(this.store, {
8296
+ walletId: this.id,
8297
+ wallet: walletState
8298
+ });
8299
+ this.logger.info("Connected successfully", walletState);
8300
+ return [walletAccount];
8301
+ };
8302
+ disconnect = async () => {
8303
+ this.logger.info("Disconnecting...");
8304
+ this.onDisconnect();
8305
+ this.account = null;
8306
+ this.removeMnemonicFromStorage();
8307
+ this.logger.info("Disconnected");
8308
+ };
8309
+ resumeSession = async () => {
8310
+ this.checkMainnet();
8311
+ const state = this.store.state;
8312
+ const walletState = state.wallets[this.id];
8313
+ if (!walletState) {
8314
+ this.logger.info("No session to resume");
8315
+ return;
8281
8316
  }
8282
- return walletAccounts;
8283
- }
8284
- /**
8285
- * Convert bytes to hex string with 0x prefix
8286
- */
8287
- bytesToHex(bytes) {
8288
- return "0x" + Array.from(bytes).map((b3) => b3.toString(16).padStart(2, "0")).join("");
8289
- }
8317
+ this.logger.info("Resuming session...");
8318
+ if (this.options.persistToStorage) {
8319
+ try {
8320
+ await this.initializeAccount();
8321
+ this.logger.info("Session resumed successfully");
8322
+ } catch (error) {
8323
+ this.logger.error("Error resuming session:", error.message);
8324
+ this.disconnect();
8325
+ throw error;
8326
+ }
8327
+ } else {
8328
+ this.logger.info("No session to resume, disconnecting...");
8329
+ this.disconnect();
8330
+ }
8331
+ };
8290
8332
  processTxns(txnGroup, indexesToSign) {
8291
8333
  const txnsToSign = [];
8292
8334
  txnGroup.forEach((txn, index) => {
8293
8335
  const isIndexMatch = !indexesToSign || indexesToSign.includes(index);
8294
8336
  const signer = txn.sender.toString();
8295
- const canSignTxn = this.addresses.includes(signer);
8337
+ const canSignTxn = signer === this.account.addr.toString();
8296
8338
  if (isIndexMatch && canSignTxn) {
8297
- txnsToSign.push({ txn });
8298
- } else {
8299
- txnsToSign.push({ txn, signers: [] });
8339
+ txnsToSign.push(txn);
8300
8340
  }
8301
8341
  });
8302
8342
  return txnsToSign;
@@ -8309,22 +8349,17 @@ var LiquidEvmBaseWallet = class extends BaseWallet {
8309
8349
  const txn = isSigned ? algosdk9.decodeSignedTransaction(txnBuffer).txn : algosdk9.decodeUnsignedTransaction(txnBuffer);
8310
8350
  const isIndexMatch = !indexesToSign || indexesToSign.includes(index);
8311
8351
  const signer = txn.sender.toString();
8312
- const canSignTxn = !isSigned && this.addresses.includes(signer);
8352
+ const canSignTxn = !isSigned && signer === this.account.addr.toString();
8313
8353
  if (isIndexMatch && canSignTxn) {
8314
- txnsToSign.push({ txn });
8315
- } else {
8316
- txnsToSign.push({ txn, signers: [] });
8354
+ txnsToSign.push(txn);
8317
8355
  }
8318
8356
  });
8319
8357
  return txnsToSign;
8320
8358
  }
8321
- /**
8322
- * Sign Algorand transactions using EVM wallet signatures
8323
- */
8324
8359
  signTransactions = async (txnGroup, indexesToSign) => {
8360
+ this.checkMainnet();
8325
8361
  try {
8326
8362
  this.logger.debug("Signing transactions...", { txnGroup, indexesToSign });
8327
- const liquidEvmSdk = await this.initializeEvmSdk();
8328
8363
  let txnsToSign = [];
8329
8364
  if (isTransactionArray(txnGroup)) {
8330
8365
  const flatTxns = flattenTxnGroup(txnGroup);
@@ -8333,412 +8368,19 @@ var LiquidEvmBaseWallet = class extends BaseWallet {
8333
8368
  const flatTxns = flattenTxnGroup(txnGroup);
8334
8369
  txnsToSign = this.processEncodedTxns(flatTxns, indexesToSign);
8335
8370
  }
8336
- const firstTxn = txnsToSign[0];
8337
- const algorandAddress = firstTxn.txn.sender.toString();
8338
- const evmAddress = this.evmAddressMap.get(algorandAddress);
8339
- if (!evmAddress) {
8340
- throw new Error(`No EVM address found for Algorand address: ${algorandAddress}`);
8341
- }
8342
- const onBeforeSign = this.options.uiHooks?.onBeforeSign ?? this.managerUIHooks?.onBeforeSign;
8343
- if (onBeforeSign) {
8344
- this.logger.debug("Running onBeforeSign hook", { txnGroup, indexesToSign });
8345
- const txnsAsUint8 = txnsToSign.map(({ txn }) => algosdk9.encodeUnsignedTransaction(txn));
8346
- await onBeforeSign(txnsAsUint8, indexesToSign);
8347
- }
8348
- await this.ensureAlgorandChain();
8349
- const { signer: evmSigner } = await liquidEvmSdk.getSigner({
8350
- evmAddress,
8351
- signMessage: (typedData) => this.signWithProvider(typedData, evmAddress)
8352
- });
8353
- const allTxns = txnsToSign.map((t) => t.txn);
8354
- const signIndexes = txnsToSign.reduce((acc, t, i) => {
8355
- if (!("signers" in t)) acc.push(i);
8356
- return acc;
8357
- }, []);
8358
- const signedBlobs = await evmSigner(allTxns, signIndexes);
8359
- const onAfterSign = this.options.uiHooks?.onAfterSign ?? this.managerUIHooks?.onAfterSign;
8360
- if (onAfterSign) {
8361
- this.logger.debug("Running onAfterSign hook");
8362
- try {
8363
- onAfterSign(true);
8364
- } catch (e) {
8365
- }
8366
- }
8367
- let signedIdx = 0;
8368
- const result = txnsToSign.map((_2, index) => {
8369
- if (signIndexes.includes(index)) {
8370
- return signedBlobs[signedIdx++];
8371
- }
8372
- return null;
8373
- });
8374
- this.logger.debug("Transactions signed successfully", result);
8375
- return result;
8371
+ const signedTxns = txnsToSign.map((txn) => txn.signTxn(this.account.sk));
8372
+ this.logger.debug("Transactions signed successfully", { signedTxns });
8373
+ return signedTxns;
8376
8374
  } catch (error) {
8377
- try {
8378
- const onAfterSignCleanup = this.options.uiHooks?.onAfterSign ?? this.managerUIHooks?.onAfterSign;
8379
- onAfterSignCleanup?.(false, error.message);
8380
- } catch (e) {
8381
- }
8382
8375
  this.logger.error("Error signing transactions:", error.message);
8383
8376
  throw error;
8384
8377
  }
8385
8378
  };
8386
- /**
8387
- * Helper to compare and update accounts if needed during session resume
8388
- */
8389
- async resumeWithAccounts(evmAddresses, setAccountsFn) {
8390
- const state = this.store.state;
8391
- const walletState = state.wallets[this.id];
8392
- if (!walletState) {
8393
- this.logger.info("No session to resume");
8394
- return;
8395
- }
8396
- for (const account of walletState.accounts) {
8397
- const evmAddr = account.metadata?.evmAddress;
8398
- if (evmAddr) {
8399
- this.evmAddressMap.set(account.address, evmAddr);
8400
- }
8401
- }
8402
- const walletAccounts = await this.deriveAlgorandAccounts(evmAddresses);
8403
- const match = compareAccounts(walletAccounts, walletState.accounts);
8404
- if (!match) {
8405
- this.logger.warn("Session accounts mismatch, updating accounts", {
8406
- prev: walletState.accounts,
8407
- current: walletAccounts
8408
- });
8409
- setAccountsFn(walletAccounts);
8410
- }
8411
- this.logger.info("Session resumed");
8412
- }
8413
- notifyConnect(evmAddress, algorandAddress) {
8414
- const onConnect = this.options.uiHooks?.onConnect ?? this.managerUIHooks?.onConnect;
8415
- if (onConnect) {
8416
- onConnect({ evmAddress, algorandAddress });
8417
- }
8418
- }
8419
8379
  };
8420
8380
 
8421
- // src/wallets/metamask.ts
8422
- var ICON11 = `data:image/svg+xml;base64,${btoa(`
8423
- <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
8424
- viewBox="0 0 142 136.878" style="enable-background:new 0 0 142 136.878;" xml:space="preserve">
8425
- <path style="fill:#FF5C16;" d="M132.682,132.192l-30.583-9.106l-23.063,13.787l-16.092-0.007l-23.077-13.78l-30.569,9.106L0,100.801
8426
- l9.299-34.839L0,36.507L9.299,0l47.766,28.538h27.85L132.682,0l9.299,36.507l-9.299,29.455l9.299,34.839L132.682,132.192
8427
- L132.682,132.192z"/>
8428
- <path style="fill:#FF5C16;" d="M9.305,0l47.767,28.558l-1.899,19.599L9.305,0z M39.875,100.814l21.017,16.01l-21.017,6.261
8429
- C39.875,123.085,39.875,100.814,39.875,100.814z M59.212,74.345l-4.039-26.174L29.317,65.97l-0.014-0.007v0.013l0.08,18.321
8430
- l10.485-9.951L59.212,74.345L59.212,74.345z M132.682,0L84.915,28.558l1.893,19.599L132.682,0z M102.113,100.814l-21.018,16.01
8431
- l21.018,6.261V100.814z M112.678,65.975h0.007H112.678v-0.013l-0.006,0.007L86.815,48.171l-4.039,26.174h19.336l10.492,9.95
8432
- C112.604,84.295,112.678,65.975,112.678,65.975z"/>
8433
- <path style="fill:#E34807;" d="M39.868,123.085l-30.569,9.106L0,100.814h39.868C39.868,100.814,39.868,123.085,39.868,123.085z
8434
- M59.205,74.338l5.839,37.84l-8.093-21.04L29.37,84.295l10.491-9.956h19.344L59.205,74.338z M102.112,123.085l30.57,9.106
8435
- l9.299-31.378h-39.869C102.112,100.814,102.112,123.085,102.112,123.085z M82.776,74.338l-5.839,37.84l8.092-21.04l27.583-6.843
8436
- l-10.498-9.956H82.776V74.338z"/>
8437
- <path style="fill:#FF8D5D;" d="M0,100.801l9.299-34.839h19.997l0.073,18.327l27.584,6.843l8.092,21.039l-4.16,4.633l-21.017-16.01H0
8438
- V100.801z M141.981,100.801l-9.299-34.839h-19.998l-0.073,18.327l-27.582,6.843l-8.093,21.039l4.159,4.633l21.018-16.01h39.868
8439
- V100.801z M84.915,28.538h-27.85l-1.891,19.599l9.872,64.013h11.891l9.878-64.013L84.915,28.538z"/>
8440
- <path style="fill:#661800;" d="M9.299,0L0,36.507l9.299,29.455h19.997l25.87-17.804L9.299,0z M53.426,81.938h-9.059l-4.932,4.835
8441
- l17.524,4.344l-3.533-9.186V81.938z M132.682,0l9.299,36.507l-9.299,29.455h-19.998L86.815,48.158L132.682,0z M88.568,81.938h9.072
8442
- l4.932,4.841l-17.544,4.353l3.54-9.201V81.938z M79.029,124.385l2.067-7.567l-4.16-4.633h-11.9l-4.159,4.633l2.066,7.567"/>
8443
- <path style="fill:#C0C4CD;" d="M79.029,124.384v12.495H62.945v-12.495L79.029,124.384L79.029,124.384z"/>
8444
- <path style="fill:#E7EBF6;" d="M39.875,123.072l23.083,13.8v-12.495l-2.067-7.566C60.891,116.811,39.875,123.072,39.875,123.072z
8445
- M102.113,123.072l-23.084,13.8v-12.495l2.067-7.566C81.096,116.811,102.113,123.072,102.113,123.072z"/>
8446
- </svg>
8447
- `)}`;
8448
- var MetaMaskWallet = class extends LiquidEvmBaseWallet {
8449
- metamaskSdk = null;
8450
- provider = null;
8451
- options;
8452
- constructor(params) {
8453
- super(params);
8454
- this.options = params.options || {};
8455
- }
8456
- static defaultMetadata = {
8457
- name: "MetaMask",
8458
- icon: ICON11,
8459
- isLiquid: "EVM"
8460
- };
8461
- async initializeProvider() {
8462
- if (!this.metamaskSdk) {
8463
- this.logger.info("Initializing MetaMask SDK...");
8464
- const { MetaMaskSDK } = await import("@metamask/sdk");
8465
- this.metamaskSdk = new MetaMaskSDK({
8466
- dappMetadata: {
8467
- name: this.options.dappMetadata?.name || "Algorand dApp",
8468
- url: this.options.dappMetadata?.url || (typeof window !== "undefined" ? window.location.href : ""),
8469
- ...this.options.dappMetadata?.iconUrl && { iconUrl: this.options.dappMetadata.iconUrl }
8470
- }
8471
- });
8472
- this.logger.info("MetaMask SDK initialized");
8473
- }
8474
- }
8475
- async getEvmProvider() {
8476
- if (!this.provider) {
8477
- await this.initializeProvider();
8478
- this.provider = this.metamaskSdk.getProvider() || null;
8479
- if (!this.provider) {
8480
- throw new Error("MetaMask provider not available. Please install MetaMask.");
8481
- }
8482
- }
8483
- return this.provider;
8484
- }
8485
- async signWithProvider(typedData, evmAddress) {
8486
- const provider = await this.getEvmProvider();
8487
- return await provider.request({
8488
- method: "eth_signTypedData_v4",
8489
- params: [evmAddress, JSON.stringify(typedData)]
8490
- });
8491
- }
8492
- connect = async () => {
8493
- this.logger.info("Connecting...");
8494
- await this.initializeProvider();
8495
- await this.initializeEvmSdk();
8496
- const provider = await this.getEvmProvider();
8497
- const evmAddresses = await provider.request({
8498
- method: "eth_requestAccounts"
8499
- });
8500
- if (evmAddresses.length === 0) {
8501
- this.logger.error("No accounts found!");
8502
- throw new Error("No accounts found!");
8503
- }
8504
- this.logger.info(`Connected to ${evmAddresses.length} EVM account(s)`);
8505
- const walletAccounts = await this.deriveAlgorandAccounts(evmAddresses);
8506
- const activeAccount = walletAccounts[0];
8507
- const walletState = {
8508
- accounts: walletAccounts,
8509
- activeAccount
8510
- };
8511
- addWallet(this.store, {
8512
- walletId: this.id,
8513
- wallet: walletState
8514
- });
8515
- this.logger.info("\u2705 Connected.", walletState);
8516
- this.notifyConnect(evmAddresses[0], activeAccount.address);
8517
- return walletAccounts;
8518
- };
8519
- disconnect = async () => {
8520
- this.logger.info("Disconnecting...");
8521
- if (this.metamaskSdk) {
8522
- await this.metamaskSdk.terminate();
8523
- }
8524
- this.provider = null;
8525
- this.evmAddressMap.clear();
8526
- this.onDisconnect();
8527
- this.logger.info("Disconnected");
8528
- };
8529
- resumeSession = async () => {
8530
- try {
8531
- const state = this.store.state;
8532
- const walletState = state.wallets[this.id];
8533
- if (!walletState) {
8534
- this.logger.info("No session to resume");
8535
- return;
8536
- }
8537
- this.logger.info("Resuming session...");
8538
- await this.initializeProvider();
8539
- await this.initializeEvmSdk();
8540
- const provider = await this.getEvmProvider();
8541
- const evmAddresses = await provider.request({
8542
- method: "eth_accounts"
8543
- });
8544
- if (evmAddresses.length === 0) {
8545
- this.logger.error("No accounts found!");
8546
- throw new Error("No accounts found!");
8547
- }
8548
- await this.resumeWithAccounts(evmAddresses, (accounts) => {
8549
- setAccounts(this.store, {
8550
- walletId: this.id,
8551
- accounts
8552
- });
8553
- });
8554
- } catch (error) {
8555
- this.logger.error("Error resuming session:", error.message);
8556
- this.onDisconnect();
8557
- throw error;
8558
- }
8559
- };
8560
- };
8561
-
8562
- // src/wallets/mnemonic.ts
8381
+ // src/wallets/pera.ts
8563
8382
  import algosdk10 from "algosdk";
8564
- var LOCAL_STORAGE_MNEMONIC_KEY = `${LOCAL_STORAGE_KEY}_mnemonic`;
8565
8383
  var ICON12 = `data:image/svg+xml;base64,${btoa(`
8566
- <svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
8567
- <rect fill="#525252" width="400" height="400" />
8568
- <path fill="#FFFFFF" d="M309.2,309.3H275l-22.2-82.7l-47.9,82.7h-38.3l73.9-128l-11.9-44.5l-99.6,172.6H90.8L217.1,90.6 h33.5l14.7,54.3h34.6l-23.6,41L309.2,309.3z" />
8569
- </svg>
8570
- `)}`;
8571
- var MnemonicWallet = class extends BaseWallet {
8572
- account = null;
8573
- options;
8574
- store;
8575
- constructor({
8576
- id,
8577
- store,
8578
- subscribe,
8579
- getAlgodClient,
8580
- options,
8581
- metadata = {}
8582
- }) {
8583
- super({ id, metadata, getAlgodClient, store, subscribe });
8584
- const {
8585
- persistToStorage = false,
8586
- promptForMnemonic = () => Promise.resolve(prompt("Enter 25-word mnemonic passphrase:"))
8587
- } = options || {};
8588
- this.options = { persistToStorage, promptForMnemonic };
8589
- this.store = store;
8590
- if (this.options.persistToStorage) {
8591
- this.logger.warn(
8592
- "Persisting mnemonics to storage is insecure. Any private key mnemonics used should never hold real Algos (i.e., on MainNet). Use with caution!"
8593
- );
8594
- }
8595
- }
8596
- static defaultMetadata = {
8597
- name: "Mnemonic",
8598
- icon: ICON12
8599
- };
8600
- loadMnemonicFromStorage() {
8601
- return StorageAdapter.getItem(LOCAL_STORAGE_MNEMONIC_KEY);
8602
- }
8603
- saveMnemonicToStorage(mnemonic) {
8604
- StorageAdapter.setItem(LOCAL_STORAGE_MNEMONIC_KEY, mnemonic);
8605
- }
8606
- removeMnemonicFromStorage() {
8607
- StorageAdapter.removeItem(LOCAL_STORAGE_MNEMONIC_KEY);
8608
- }
8609
- checkMainnet() {
8610
- try {
8611
- const network = this.activeNetworkConfig;
8612
- if (!network.isTestnet) {
8613
- this.logger.warn(
8614
- "The Mnemonic wallet provider is insecure and intended for testing only. Any private key mnemonics used should never hold real Algos (i.e., on MainNet)."
8615
- );
8616
- throw new Error("Production network detected. Aborting.");
8617
- }
8618
- } catch (error) {
8619
- this.disconnect();
8620
- throw error;
8621
- }
8622
- }
8623
- async initializeAccount() {
8624
- let mnemonic = this.loadMnemonicFromStorage();
8625
- if (!mnemonic) {
8626
- mnemonic = await this.options.promptForMnemonic();
8627
- if (!mnemonic) {
8628
- this.account = null;
8629
- this.logger.error("No mnemonic provided");
8630
- throw new Error("No mnemonic provided");
8631
- }
8632
- if (this.options.persistToStorage) {
8633
- this.logger.warn("Mnemonic saved to localStorage.");
8634
- this.saveMnemonicToStorage(mnemonic);
8635
- }
8636
- }
8637
- const account = algosdk10.mnemonicToSecretKey(mnemonic);
8638
- this.account = account;
8639
- return account;
8640
- }
8641
- connect = async () => {
8642
- this.checkMainnet();
8643
- this.logger.info("Connecting...");
8644
- const account = await this.initializeAccount();
8645
- const walletAccount = {
8646
- name: `${this.metadata.name} Account`,
8647
- address: account.addr.toString()
8648
- };
8649
- const walletState = {
8650
- accounts: [walletAccount],
8651
- activeAccount: walletAccount
8652
- };
8653
- addWallet(this.store, {
8654
- walletId: this.id,
8655
- wallet: walletState
8656
- });
8657
- this.logger.info("Connected successfully", walletState);
8658
- return [walletAccount];
8659
- };
8660
- disconnect = async () => {
8661
- this.logger.info("Disconnecting...");
8662
- this.onDisconnect();
8663
- this.account = null;
8664
- this.removeMnemonicFromStorage();
8665
- this.logger.info("Disconnected");
8666
- };
8667
- resumeSession = async () => {
8668
- this.checkMainnet();
8669
- const state = this.store.state;
8670
- const walletState = state.wallets[this.id];
8671
- if (!walletState) {
8672
- this.logger.info("No session to resume");
8673
- return;
8674
- }
8675
- this.logger.info("Resuming session...");
8676
- if (this.options.persistToStorage) {
8677
- try {
8678
- await this.initializeAccount();
8679
- this.logger.info("Session resumed successfully");
8680
- } catch (error) {
8681
- this.logger.error("Error resuming session:", error.message);
8682
- this.disconnect();
8683
- throw error;
8684
- }
8685
- } else {
8686
- this.logger.info("No session to resume, disconnecting...");
8687
- this.disconnect();
8688
- }
8689
- };
8690
- processTxns(txnGroup, indexesToSign) {
8691
- const txnsToSign = [];
8692
- txnGroup.forEach((txn, index) => {
8693
- const isIndexMatch = !indexesToSign || indexesToSign.includes(index);
8694
- const signer = txn.sender.toString();
8695
- const canSignTxn = signer === this.account.addr.toString();
8696
- if (isIndexMatch && canSignTxn) {
8697
- txnsToSign.push(txn);
8698
- }
8699
- });
8700
- return txnsToSign;
8701
- }
8702
- processEncodedTxns(txnGroup, indexesToSign) {
8703
- const txnsToSign = [];
8704
- txnGroup.forEach((txnBuffer, index) => {
8705
- const decodedObj = algosdk10.msgpackRawDecode(txnBuffer);
8706
- const isSigned = isSignedTxn(decodedObj);
8707
- const txn = isSigned ? algosdk10.decodeSignedTransaction(txnBuffer).txn : algosdk10.decodeUnsignedTransaction(txnBuffer);
8708
- const isIndexMatch = !indexesToSign || indexesToSign.includes(index);
8709
- const signer = txn.sender.toString();
8710
- const canSignTxn = !isSigned && signer === this.account.addr.toString();
8711
- if (isIndexMatch && canSignTxn) {
8712
- txnsToSign.push(txn);
8713
- }
8714
- });
8715
- return txnsToSign;
8716
- }
8717
- signTransactions = async (txnGroup, indexesToSign) => {
8718
- this.checkMainnet();
8719
- try {
8720
- this.logger.debug("Signing transactions...", { txnGroup, indexesToSign });
8721
- let txnsToSign = [];
8722
- if (isTransactionArray(txnGroup)) {
8723
- const flatTxns = flattenTxnGroup(txnGroup);
8724
- txnsToSign = this.processTxns(flatTxns, indexesToSign);
8725
- } else {
8726
- const flatTxns = flattenTxnGroup(txnGroup);
8727
- txnsToSign = this.processEncodedTxns(flatTxns, indexesToSign);
8728
- }
8729
- const signedTxns = txnsToSign.map((txn) => txn.signTxn(this.account.sk));
8730
- this.logger.debug("Transactions signed successfully", { signedTxns });
8731
- return signedTxns;
8732
- } catch (error) {
8733
- this.logger.error("Error signing transactions:", error.message);
8734
- throw error;
8735
- }
8736
- };
8737
- };
8738
-
8739
- // src/wallets/pera.ts
8740
- import algosdk11 from "algosdk";
8741
- var ICON13 = `data:image/svg+xml;base64,${btoa(`
8742
8384
  <svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
8743
8385
  <rect fill="#FFEE55" width="200" height="200" />
8744
8386
  <path fill="#1C1C1C" d="M106.1,64.3c2.2,9.1,1.5,17-1.7,17.8c-3.1,0.8-7.4-6-9.6-15c-2.2-9.1-1.5-17,1.7-17.8 C99.6,48.5,103.9,55.2,106.1,64.3z" />
@@ -8767,7 +8409,7 @@ var PeraWallet = class extends BaseWallet {
8767
8409
  }
8768
8410
  static defaultMetadata = {
8769
8411
  name: "Pera",
8770
- icon: ICON13
8412
+ icon: ICON12
8771
8413
  };
8772
8414
  async initializeClient() {
8773
8415
  this.logger.info("Initializing client...");
@@ -8873,18 +8515,180 @@ var PeraWallet = class extends BaseWallet {
8873
8515
  prev: walletState.accounts,
8874
8516
  current: walletAccounts
8875
8517
  });
8876
- setAccounts(this.store, {
8877
- walletId: this.id,
8878
- accounts: walletAccounts
8518
+ setAccounts(this.store, {
8519
+ walletId: this.id,
8520
+ accounts: walletAccounts
8521
+ });
8522
+ }
8523
+ this.logger.info("Session resumed successfully");
8524
+ } catch (error) {
8525
+ this.logger.error("Error resuming session:", error.message);
8526
+ this.onDisconnect();
8527
+ throw error;
8528
+ }
8529
+ };
8530
+ processTxns(txnGroup, indexesToSign) {
8531
+ const txnsToSign = [];
8532
+ txnGroup.forEach((txn, index) => {
8533
+ const isIndexMatch = !indexesToSign || indexesToSign.includes(index);
8534
+ const signer = txn.sender.toString();
8535
+ const canSignTxn = this.addresses.includes(signer);
8536
+ if (isIndexMatch && canSignTxn) {
8537
+ txnsToSign.push({ txn });
8538
+ } else {
8539
+ txnsToSign.push({ txn, signers: [] });
8540
+ }
8541
+ });
8542
+ return txnsToSign;
8543
+ }
8544
+ processEncodedTxns(txnGroup, indexesToSign) {
8545
+ const txnsToSign = [];
8546
+ txnGroup.forEach((txnBuffer, index) => {
8547
+ const decodedObj = algosdk10.msgpackRawDecode(txnBuffer);
8548
+ const isSigned = isSignedTxn(decodedObj);
8549
+ const txn = isSigned ? algosdk10.decodeSignedTransaction(txnBuffer).txn : algosdk10.decodeUnsignedTransaction(txnBuffer);
8550
+ const isIndexMatch = !indexesToSign || indexesToSign.includes(index);
8551
+ const signer = txn.sender.toString();
8552
+ const canSignTxn = !isSigned && this.addresses.includes(signer);
8553
+ if (isIndexMatch && canSignTxn) {
8554
+ txnsToSign.push({ txn });
8555
+ } else {
8556
+ txnsToSign.push({ txn, signers: [] });
8557
+ }
8558
+ });
8559
+ return txnsToSign;
8560
+ }
8561
+ signTransactions = async (txnGroup, indexesToSign) => {
8562
+ try {
8563
+ this.logger.debug("Signing transactions...", { txnGroup, indexesToSign });
8564
+ let txnsToSign = [];
8565
+ if (isTransactionArray(txnGroup)) {
8566
+ const flatTxns = flattenTxnGroup(txnGroup);
8567
+ txnsToSign = this.processTxns(flatTxns, indexesToSign);
8568
+ } else {
8569
+ const flatTxns = flattenTxnGroup(txnGroup);
8570
+ txnsToSign = this.processEncodedTxns(flatTxns, indexesToSign);
8571
+ }
8572
+ const client = this.client || await this.initializeClient();
8573
+ this.logger.debug("Sending processed transactions to wallet...", [txnsToSign]);
8574
+ const signedTxns = await client.signTransaction([txnsToSign]);
8575
+ this.logger.debug("Received signed transactions from wallet", signedTxns);
8576
+ const result = txnsToSign.reduce((acc, txn) => {
8577
+ if (txn.signers && txn.signers.length == 0) {
8578
+ acc.push(null);
8579
+ } else {
8580
+ const signedTxn = signedTxns.shift();
8581
+ if (signedTxn) {
8582
+ acc.push(signedTxn);
8583
+ }
8584
+ }
8585
+ return acc;
8586
+ }, []);
8587
+ this.logger.debug("Transactions signed successfully", result);
8588
+ return result;
8589
+ } catch (error) {
8590
+ this.logger.error("Error signing transactions:", error.message);
8591
+ throw error;
8592
+ }
8593
+ };
8594
+ };
8595
+
8596
+ // src/wallets/liquid-evm-base.ts
8597
+ import algosdk11 from "algosdk";
8598
+ var LiquidEvmBaseWallet = class extends BaseWallet {
8599
+ liquidEvmSdk = null;
8600
+ algorandClient = null;
8601
+ evmAddressMap = /* @__PURE__ */ new Map();
8602
+ // algorandAddress -> evmAddress
8603
+ options;
8604
+ store;
8605
+ constructor(params) {
8606
+ super(params);
8607
+ this.options = params.options || {};
8608
+ this.store = params.store;
8609
+ }
8610
+ /**
8611
+ * Default metadata for Liquid EVM wallets.
8612
+ * Subclasses MUST override this with their own metadata including isLiquid: "EVM"
8613
+ */
8614
+ static defaultMetadata;
8615
+ /**
8616
+ * Ensure the wallet is on the Algorand chain (4160).
8617
+ * Queries the current chain first, and only switches/adds if needed.
8618
+ */
8619
+ async ensureAlgorandChain() {
8620
+ const provider = await this.getEvmProvider();
8621
+ const { ALGORAND_CHAIN_ID_HEX, ALGORAND_EVM_CHAIN_CONFIG } = await import("liquid-accounts-evm");
8622
+ const currentChainId = await provider.request({ method: "eth_chainId" });
8623
+ if (currentChainId.toLowerCase() === ALGORAND_CHAIN_ID_HEX.toLowerCase()) {
8624
+ return;
8625
+ }
8626
+ this.logger.info(
8627
+ `Wrong chain (${currentChainId}), switching to Algorand (${ALGORAND_CHAIN_ID_HEX})...`
8628
+ );
8629
+ try {
8630
+ await provider.request({
8631
+ method: "wallet_switchEthereumChain",
8632
+ params: [{ chainId: ALGORAND_CHAIN_ID_HEX }]
8633
+ });
8634
+ } catch (switchError) {
8635
+ const chainUnknown = [4902, -32600, -32603].includes(switchError.code);
8636
+ if (chainUnknown) {
8637
+ this.logger.info("Algorand chain not found, adding it...");
8638
+ await provider.request({
8639
+ method: "wallet_addEthereumChain",
8640
+ params: [ALGORAND_EVM_CHAIN_CONFIG]
8641
+ });
8642
+ } else {
8643
+ throw switchError;
8644
+ }
8645
+ }
8646
+ }
8647
+ /**
8648
+ * Initialize the Liquid EVM SDK for deriving Algorand addresses
8649
+ */
8650
+ async initializeEvmSdk() {
8651
+ if (!this.liquidEvmSdk) {
8652
+ this.logger.info("Initializing Liquid EVM SDK...");
8653
+ if (!this.algorandClient) {
8654
+ const { AlgorandClient } = await import("@algorandfoundation/algokit-utils");
8655
+ const algodClient = this.getAlgodClient();
8656
+ this.algorandClient = AlgorandClient.fromClients({
8657
+ algod: algodClient
8879
8658
  });
8880
8659
  }
8881
- this.logger.info("Session resumed successfully");
8882
- } catch (error) {
8883
- this.logger.error("Error resuming session:", error.message);
8884
- this.onDisconnect();
8885
- throw error;
8660
+ const { LiquidEvmSdk } = await import("liquid-accounts-evm");
8661
+ this.liquidEvmSdk = new LiquidEvmSdk({ algorand: this.algorandClient });
8662
+ this.logger.info("Liquid EVM SDK initialized");
8886
8663
  }
8887
- };
8664
+ return this.liquidEvmSdk;
8665
+ }
8666
+ /**
8667
+ * Derive Algorand accounts from EVM addresses.
8668
+ * @param evmAddresses - EVM addresses to derive Algorand accounts from
8669
+ * @param connectorInfo - Optional connector name/icon to include in account metadata
8670
+ */
8671
+ async deriveAlgorandAccounts(evmAddresses, connectorInfo) {
8672
+ const liquidEvmSdk = await this.initializeEvmSdk();
8673
+ const walletAccounts = [];
8674
+ for (let i = 0; i < evmAddresses.length; i++) {
8675
+ const evmAddress = evmAddresses[i];
8676
+ const algorandAddress = await liquidEvmSdk.getAddress({ evmAddress });
8677
+ this.evmAddressMap.set(algorandAddress, evmAddress);
8678
+ const metadata = { evmAddress };
8679
+ if (connectorInfo?.name) metadata.connectorName = connectorInfo.name;
8680
+ if (connectorInfo?.icon) metadata.connectorIcon = connectorInfo.icon;
8681
+ walletAccounts.push({
8682
+ name: `${this.metadata.name} ${evmAddress}`,
8683
+ address: algorandAddress,
8684
+ metadata
8685
+ });
8686
+ }
8687
+ return walletAccounts;
8688
+ }
8689
+ /**
8690
+ * Process transaction group to extract transactions that need signing
8691
+ */
8888
8692
  processTxns(txnGroup, indexesToSign) {
8889
8693
  const txnsToSign = [];
8890
8694
  txnGroup.forEach((txn, index) => {
@@ -8916,9 +8720,13 @@ var PeraWallet = class extends BaseWallet {
8916
8720
  });
8917
8721
  return txnsToSign;
8918
8722
  }
8723
+ /**
8724
+ * Sign Algorand transactions using EVM wallet signatures
8725
+ */
8919
8726
  signTransactions = async (txnGroup, indexesToSign) => {
8920
8727
  try {
8921
8728
  this.logger.debug("Signing transactions...", { txnGroup, indexesToSign });
8729
+ const liquidEvmSdk = await this.initializeEvmSdk();
8922
8730
  let txnsToSign = [];
8923
8731
  if (isTransactionArray(txnGroup)) {
8924
8732
  const flatTxns = flattenTxnGroup(txnGroup);
@@ -8927,136 +8735,295 @@ var PeraWallet = class extends BaseWallet {
8927
8735
  const flatTxns = flattenTxnGroup(txnGroup);
8928
8736
  txnsToSign = this.processEncodedTxns(flatTxns, indexesToSign);
8929
8737
  }
8930
- const client = this.client || await this.initializeClient();
8931
- this.logger.debug("Sending processed transactions to wallet...", [txnsToSign]);
8932
- const signedTxns = await client.signTransaction([txnsToSign]);
8933
- this.logger.debug("Received signed transactions from wallet", signedTxns);
8934
- const result = txnsToSign.reduce((acc, txn) => {
8935
- if (txn.signers && txn.signers.length == 0) {
8936
- acc.push(null);
8937
- } else {
8938
- const signedTxn = signedTxns.shift();
8939
- if (signedTxn) {
8940
- acc.push(signedTxn);
8738
+ const firstTxn = txnsToSign[0];
8739
+ const algorandAddress = firstTxn.txn.sender.toString();
8740
+ let evmAddress = this.evmAddressMap.get(algorandAddress);
8741
+ if (!evmAddress) {
8742
+ const walletState = this.store.state.wallets[this.id];
8743
+ if (walletState) {
8744
+ for (const account of walletState.accounts) {
8745
+ const addr = account.metadata?.evmAddress;
8746
+ if (addr) {
8747
+ this.evmAddressMap.set(account.address, addr);
8748
+ }
8941
8749
  }
8750
+ evmAddress = this.evmAddressMap.get(algorandAddress);
8942
8751
  }
8752
+ }
8753
+ if (!evmAddress) {
8754
+ throw new Error(`No EVM address found for Algorand address: ${algorandAddress}`);
8755
+ }
8756
+ const onBeforeSign = this.options.uiHooks?.onBeforeSign ?? this.managerUIHooks?.onBeforeSign;
8757
+ if (onBeforeSign) {
8758
+ this.logger.debug("Running onBeforeSign hook", { txnGroup, indexesToSign });
8759
+ const txnsAsUint8 = txnsToSign.map(({ txn }) => algosdk11.encodeUnsignedTransaction(txn));
8760
+ await onBeforeSign(txnsAsUint8, indexesToSign);
8761
+ }
8762
+ await this.ensureAlgorandChain();
8763
+ const { signer: evmSigner } = await liquidEvmSdk.getSigner({
8764
+ evmAddress,
8765
+ signMessage: (typedData) => this.signWithProvider(typedData, evmAddress)
8766
+ });
8767
+ const allTxns = txnsToSign.map((t) => t.txn);
8768
+ const signIndexes = txnsToSign.reduce((acc, t, i) => {
8769
+ if (!("signers" in t)) acc.push(i);
8943
8770
  return acc;
8944
8771
  }, []);
8772
+ const signedBlobs = await evmSigner(allTxns, signIndexes);
8773
+ const onAfterSign = this.options.uiHooks?.onAfterSign ?? this.managerUIHooks?.onAfterSign;
8774
+ if (onAfterSign) {
8775
+ this.logger.debug("Running onAfterSign hook");
8776
+ try {
8777
+ onAfterSign(true);
8778
+ } catch (e) {
8779
+ }
8780
+ }
8781
+ let signedIdx = 0;
8782
+ const result = txnsToSign.map((_2, index) => {
8783
+ if (signIndexes.includes(index)) {
8784
+ return signedBlobs[signedIdx++];
8785
+ }
8786
+ return null;
8787
+ });
8945
8788
  this.logger.debug("Transactions signed successfully", result);
8946
8789
  return result;
8947
8790
  } catch (error) {
8791
+ try {
8792
+ const onAfterSignCleanup = this.options.uiHooks?.onAfterSign ?? this.managerUIHooks?.onAfterSign;
8793
+ onAfterSignCleanup?.(false, error.message);
8794
+ } catch (e) {
8795
+ }
8948
8796
  this.logger.error("Error signing transactions:", error.message);
8949
8797
  throw error;
8950
8798
  }
8951
8799
  };
8800
+ /**
8801
+ * Helper to compare and update accounts if needed during session resume
8802
+ */
8803
+ async resumeWithAccounts(evmAddresses, setAccountsFn, connectorInfo) {
8804
+ const state = this.store.state;
8805
+ const walletState = state.wallets[this.id];
8806
+ if (!walletState) {
8807
+ this.logger.info("No session to resume");
8808
+ return;
8809
+ }
8810
+ for (const account of walletState.accounts) {
8811
+ const evmAddr = account.metadata?.evmAddress;
8812
+ if (evmAddr) {
8813
+ this.evmAddressMap.set(account.address, evmAddr);
8814
+ }
8815
+ }
8816
+ const walletAccounts = await this.deriveAlgorandAccounts(evmAddresses, connectorInfo);
8817
+ const match = compareAccounts(walletAccounts, walletState.accounts);
8818
+ if (!match) {
8819
+ this.logger.warn("Session accounts mismatch, updating accounts", {
8820
+ prev: walletState.accounts,
8821
+ current: walletAccounts
8822
+ });
8823
+ }
8824
+ setAccountsFn(walletAccounts);
8825
+ this.logger.info("Session resumed");
8826
+ }
8827
+ notifyConnect(evmAddress, algorandAddress) {
8828
+ const onConnect = this.options.uiHooks?.onConnect ?? this.managerUIHooks?.onConnect;
8829
+ if (onConnect) {
8830
+ onConnect({ evmAddress, algorandAddress });
8831
+ }
8832
+ }
8952
8833
  };
8953
8834
 
8954
- // src/wallets/rainbow.ts
8955
- var ICON14 = `data:image/svg+xml;base64,${btoa(`
8835
+ // src/wallets/rainbowkit.ts
8836
+ import {
8837
+ ALGORAND_CHAIN_ID,
8838
+ algorandChain
8839
+ } from "liquid-accounts-evm";
8840
+ var ICON13 = `data:image/svg+xml;base64,${btoa(`
8956
8841
  <svg width="120" height="120" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
8957
- <rect width="120" height="120" rx="60" fill="url(#paint0_linear)"/>
8958
- <path d="M60 85C74.9117 85 87 72.9117 87 58C87 43.0883 74.9117 31 60 31C45.0883 31 33 43.0883 33 58C33 72.9117 45.0883 85 60 85Z" fill="white"/>
8959
- <path d="M60 77C70.4934 77 79 68.4934 79 58C79 47.5066 70.4934 39 60 39C49.5066 39 41 47.5066 41 58C41 68.4934 49.5066 77 60 77Z" fill="url(#paint1_linear)"/>
8842
+ <rect width="120" height="120" rx="24" fill="url(#rk_bg)"/>
8843
+ <path d="M24 86V76.8C24 55.9 40.9 39 61.8 39H66C70.418 39 74 42.582 74 47V86" stroke="#FF4000" stroke-width="8" stroke-linecap="round" fill="none"/>
8844
+ <path d="M36 86V76.8C36 62.3 47.7 50.6 62.2 50.6H64C67.314 50.6 70 53.286 70 56.6V86" stroke="#FF9500" stroke-width="8" stroke-linecap="round" fill="none"/>
8845
+ <path d="M48 86V76.8C48 68.8 54.5 62.3 62.5 62.3H62.7C65.461 62.3 67.7 64.539 67.7 67.3V86" stroke="#00C853" stroke-width="8" stroke-linecap="round" fill="none"/>
8846
+ <path d="M60 86V76.8C60 75 61.5 73.5 63.3 73.5C65.1 73.5 66.6 75 66.6 76.8V86" stroke="#2979FF" stroke-width="8" stroke-linecap="round" fill="none"/>
8960
8847
  <defs>
8961
- <linearGradient id="paint0_linear" x1="0" y1="0" x2="120" y2="120" gradientUnits="userSpaceOnUse">
8962
- <stop stop-color="#174299"/>
8963
- <stop offset="1" stop-color="#001E59"/>
8964
- </linearGradient>
8965
- <linearGradient id="paint1_linear" x1="41" y1="39" x2="79" y2="77" gradientUnits="userSpaceOnUse">
8966
- <stop stop-color="#FF4444"/>
8967
- <stop offset="0.2" stop-color="#FF8844"/>
8968
- <stop offset="0.4" stop-color="#FFDD00"/>
8969
- <stop offset="0.6" stop-color="#44FF44"/>
8970
- <stop offset="0.8" stop-color="#0088FF"/>
8971
- <stop offset="1" stop-color="#8844FF"/>
8848
+ <linearGradient id="rk_bg" x1="0" y1="0" x2="120" y2="120">
8849
+ <stop stop-color="#1A1B23"/>
8850
+ <stop offset="1" stop-color="#13141B"/>
8972
8851
  </linearGradient>
8973
8852
  </defs>
8974
8853
  </svg>
8975
8854
  `)}`;
8976
- var RainbowWallet = class extends LiquidEvmBaseWallet {
8977
- provider = null;
8855
+ var RainbowKitWallet = class _RainbowKitWallet extends LiquidEvmBaseWallet {
8978
8856
  options;
8857
+ _connecting = false;
8979
8858
  constructor(params) {
8980
8859
  super(params);
8981
8860
  this.options = params.options || {};
8861
+ if (!this.options.wagmiConfig) {
8862
+ throw new Error("RainbowKitWallet requires wagmiConfig in options");
8863
+ }
8864
+ this.ensureChainRegistered();
8982
8865
  }
8983
8866
  static defaultMetadata = {
8984
- name: "Rainbow",
8985
- icon: ICON14,
8867
+ name: "EVM Wallet",
8868
+ icon: ICON13,
8986
8869
  isLiquid: "EVM"
8987
8870
  };
8988
- async initializeProvider() {
8989
- if (typeof window === "undefined") {
8990
- throw new Error("Rainbow wallet only works in browser environment");
8991
- }
8992
- const windowEth = window.ethereum;
8993
- if (window.rainbow) {
8994
- this.logger.info("Found Rainbow browser extension at window.rainbow");
8995
- return;
8996
- }
8997
- if (windowEth?.providers && Array.isArray(windowEth.providers)) {
8998
- const rainbowProvider = windowEth.providers.find((p2) => p2.isRainbow);
8999
- if (rainbowProvider) {
9000
- this.logger.info("Found Rainbow in providers array");
9001
- return;
9002
- }
9003
- }
9004
- if (windowEth?.isRainbow) {
9005
- this.logger.info("Found Rainbow as the primary provider");
8871
+ /** True while connect() is running. Prevents re-entrancy from bridge components. */
8872
+ get isConnecting() {
8873
+ return this._connecting;
8874
+ }
8875
+ /**
8876
+ * Set the getEvmAccounts callback after construction.
8877
+ *
8878
+ * RainbowKit's connect modal can only be opened via React hooks (useConnectModal)
8879
+ * rendered inside <RainbowKitProvider>, but WalletManager is constructed before
8880
+ * any React tree renders. This method lets WalletUIProvider create the bridge
8881
+ * callback internally and inject it into the wallet on mount — before any
8882
+ * user-initiated connect() call.
8883
+ */
8884
+ setGetEvmAccounts(fn) {
8885
+ this.options.getEvmAccounts = fn;
8886
+ }
8887
+ get wagmiConfig() {
8888
+ return this.options.wagmiConfig;
8889
+ }
8890
+ /**
8891
+ * If the Algorand chain (4160) isn't already in the wagmi config, add it.
8892
+ * This is needed so wagmi's switchChain and signTypedData work without
8893
+ * falling back to raw provider calls.
8894
+ */
8895
+ ensureChainRegistered() {
8896
+ const chains = this.wagmiConfig.chains;
8897
+ if (chains.some((c) => c.id === ALGORAND_CHAIN_ID)) {
9006
8898
  return;
9007
8899
  }
9008
- this.logger.warn("Rainbow extension not detected. Note: Rainbow mobile app requires WalletConnect.");
8900
+ this.logger.info(`Registering Algorand chain (${ALGORAND_CHAIN_ID}) in wagmi config`);
8901
+ chains.push(algorandChain);
8902
+ }
8903
+ async initializeProvider() {
8904
+ this.logger.info("Using wagmi for EVM provider management");
8905
+ }
8906
+ /**
8907
+ * Get the raw EIP-1193 provider from the active wagmi connector.
8908
+ * Used by the base class's getEvmProvider and ensureAlgorandChain.
8909
+ */
8910
+ async getRawProvider() {
8911
+ const { getAccount } = await import("@wagmi/core");
8912
+ const account = getAccount(this.wagmiConfig);
8913
+ if (!account.connector) throw new Error("No EVM wallet connector available");
8914
+ return account.connector.getProvider();
9009
8915
  }
9010
8916
  async getEvmProvider() {
9011
- if (!this.provider) {
9012
- await this.initializeProvider();
9013
- const windowEth = window.ethereum;
9014
- if (window.rainbow) {
9015
- this.provider = window.rainbow;
9016
- } else if (windowEth?.providers && Array.isArray(windowEth.providers)) {
9017
- const rainbowProvider = windowEth.providers.find((p2) => p2.isRainbow);
9018
- if (rainbowProvider) {
9019
- this.provider = rainbowProvider;
8917
+ return this.getRawProvider();
8918
+ }
8919
+ /**
8920
+ * Sign EIP-712 typed data using wagmi's signTypedData.
8921
+ *
8922
+ * wagmi's signTypedData does NOT validate the domain's chainId against the
8923
+ * connected chain it simply forwards the typed data to the wallet via viem.
8924
+ * EIP-712 signing is chain-agnostic (the chain ID is in the typed data domain,
8925
+ * not in the RPC method), so this works regardless of which chain the wallet
8926
+ * reports being on.
8927
+ */
8928
+ async signWithProvider(typedData, evmAddress) {
8929
+ const { signTypedData } = await import("@wagmi/core");
8930
+ const { EIP712Domain: _2, ...types } = typedData.types;
8931
+ return signTypedData(this.wagmiConfig, {
8932
+ account: evmAddress,
8933
+ domain: typedData.domain,
8934
+ types,
8935
+ primaryType: typedData.primaryType,
8936
+ message: typedData.message
8937
+ });
8938
+ }
8939
+ /**
8940
+ * Build a connectorInfo object from a wagmi account, omitting undefined fields.
8941
+ * Uses `any` for the parameter to avoid exactOptionalPropertyTypes conflicts
8942
+ * with wagmi's discriminated union return types.
8943
+ */
8944
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8945
+ static extractConnectorInfo(account) {
8946
+ const info = {};
8947
+ const connector = account?.connector;
8948
+ if (typeof connector?.name === "string") info.name = connector.name;
8949
+ if (typeof connector?.icon === "string") info.icon = connector.icon;
8950
+ return info;
8951
+ }
8952
+ /**
8953
+ * Read connected EVM accounts from wagmi state.
8954
+ * If not connected, tries the getEvmAccounts callback, then falls back to
8955
+ * connecting with the first available connector.
8956
+ *
8957
+ * When getEvmAccounts is provided, it is always called (to show the wallet
8958
+ * selection UI). The callback is responsible for any disconnect/reconnect
8959
+ * needed to present a fresh selection.
8960
+ */
8961
+ async getConnectedEvmAddresses() {
8962
+ const { getAccount, connect: wagmiConnect } = await import("@wagmi/core");
8963
+ if (this.options.getEvmAccounts) {
8964
+ const addresses = await this.options.getEvmAccounts();
8965
+ if (addresses.length > 0) {
8966
+ const account2 = getAccount(this.wagmiConfig);
8967
+ const connectorInfo = _RainbowKitWallet.extractConnectorInfo(account2);
8968
+ if (account2.isConnected && account2.address) {
8969
+ return {
8970
+ addresses: account2.addresses ? [...account2.addresses] : [account2.address],
8971
+ connectorInfo
8972
+ };
9020
8973
  }
9021
- } else if (windowEth?.isRainbow) {
9022
- this.provider = windowEth;
8974
+ return { addresses, connectorInfo };
9023
8975
  }
9024
- if (!this.provider) {
9025
- throw new Error("Rainbow wallet not available. Please install Rainbow browser extension or use WalletConnect for mobile.");
8976
+ }
8977
+ const account = getAccount(this.wagmiConfig);
8978
+ if (account.isConnected && account.address) {
8979
+ return {
8980
+ addresses: account.addresses ? [...account.addresses] : [account.address],
8981
+ connectorInfo: _RainbowKitWallet.extractConnectorInfo(account)
8982
+ };
8983
+ }
8984
+ const connectors = this.wagmiConfig.connectors;
8985
+ if (connectors.length > 0) {
8986
+ this.logger.info("Attempting connection with first available connector...");
8987
+ try {
8988
+ const result = await wagmiConnect(this.wagmiConfig, { connector: connectors[0] });
8989
+ const updatedAccount = getAccount(this.wagmiConfig);
8990
+ return {
8991
+ addresses: [...result.accounts],
8992
+ connectorInfo: _RainbowKitWallet.extractConnectorInfo(updatedAccount)
8993
+ };
8994
+ } catch (error) {
8995
+ this.logger.warn("Auto-connect failed:", error.message);
9026
8996
  }
9027
8997
  }
9028
- return this.provider;
8998
+ throw new Error("No EVM wallet connected. Please connect an EVM wallet first.");
9029
8999
  }
9030
- async signWithProvider(typedData, evmAddress) {
9031
- const provider = await this.getEvmProvider();
9032
- try {
9033
- return await provider.request({
9034
- method: "eth_signTypedData_v4",
9035
- params: [evmAddress, JSON.stringify(typedData)]
9036
- });
9037
- } catch (error) {
9038
- if (error.code === 4001) {
9039
- throw new Error("User rejected the signing request");
9040
- }
9041
- throw error;
9000
+ /**
9001
+ * Apply connector info to wallet-level metadata so the UI displays
9002
+ * the actual wallet name/icon (e.g., "MetaMask") instead of the generic
9003
+ * "EVM Wallet". Falls back to defaults when connector info is unavailable.
9004
+ */
9005
+ applyConnectorMetadata(connectorInfo) {
9006
+ const updates = {};
9007
+ if (connectorInfo.name) updates.name = connectorInfo.name;
9008
+ if (connectorInfo.icon) updates.icon = connectorInfo.icon;
9009
+ if (updates.name || updates.icon) {
9010
+ this.updateMetadata(updates);
9011
+ this.logger.info(`Wallet metadata updated: ${updates.name ?? "(no name)"}`);
9042
9012
  }
9043
9013
  }
9044
9014
  connect = async () => {
9045
- this.logger.info("Connecting...");
9046
- await this.initializeProvider();
9047
- await this.initializeEvmSdk();
9048
- const provider = await this.getEvmProvider();
9015
+ if (this._connecting) {
9016
+ this.logger.info("connect() already in progress, ignoring duplicate call");
9017
+ return [];
9018
+ }
9019
+ this._connecting = true;
9049
9020
  try {
9050
- this.logger.info("Requesting Rainbow wallet connection...");
9051
- const evmAddresses = await provider.request({
9052
- method: "eth_requestAccounts"
9053
- });
9054
- if (evmAddresses.length === 0) {
9055
- this.logger.error("No accounts found!");
9056
- throw new Error("No accounts found!");
9057
- }
9021
+ this.logger.info("Connecting...");
9022
+ await this.initializeEvmSdk();
9023
+ const { addresses: evmAddresses, connectorInfo } = await this.getConnectedEvmAddresses();
9058
9024
  this.logger.info(`Connected to ${evmAddresses.length} EVM account(s)`);
9059
- const walletAccounts = await this.deriveAlgorandAccounts(evmAddresses);
9025
+ this.applyConnectorMetadata(connectorInfo);
9026
+ const walletAccounts = await this.deriveAlgorandAccounts(evmAddresses, connectorInfo);
9060
9027
  const activeAccount = walletAccounts[0];
9061
9028
  const walletState = {
9062
9029
  accounts: walletAccounts,
@@ -9066,51 +9033,70 @@ var RainbowWallet = class extends LiquidEvmBaseWallet {
9066
9033
  walletId: this.id,
9067
9034
  wallet: walletState
9068
9035
  });
9069
- this.logger.info("\u2705 Connected.", walletState);
9036
+ this.logger.info("Connected.", walletState);
9070
9037
  this.notifyConnect(evmAddresses[0], activeAccount.address);
9071
9038
  return walletAccounts;
9072
- } catch (error) {
9073
- this.logger.error("Error connecting:", error.message);
9074
- throw error;
9039
+ } finally {
9040
+ this._connecting = false;
9075
9041
  }
9076
9042
  };
9077
9043
  disconnect = async () => {
9078
9044
  this.logger.info("Disconnecting...");
9079
- this.provider = null;
9045
+ try {
9046
+ const { disconnect: wagmiDisconnect } = await import("@wagmi/core");
9047
+ await wagmiDisconnect(this.wagmiConfig);
9048
+ } catch (error) {
9049
+ this.logger.warn("wagmi disconnect error:", error.message);
9050
+ }
9080
9051
  this.evmAddressMap.clear();
9052
+ this.updateMetadata(_RainbowKitWallet.defaultMetadata);
9081
9053
  this.onDisconnect();
9082
9054
  this.logger.info("Disconnected");
9083
9055
  };
9084
9056
  resumeSession = async () => {
9057
+ const state = this.store.state;
9058
+ const walletState = state.wallets[this.id];
9059
+ if (!walletState) {
9060
+ return;
9061
+ }
9062
+ this.logger.info("Resuming session...");
9063
+ await this.initializeEvmSdk();
9064
+ const { getAccount, reconnect } = await import("@wagmi/core");
9085
9065
  try {
9086
- const state = this.store.state;
9087
- const walletState = state.wallets[this.id];
9088
- if (!walletState) {
9089
- this.logger.info("No session to resume");
9090
- return;
9091
- }
9092
- this.logger.info("Resuming session...");
9093
- await this.initializeProvider();
9094
- await this.initializeEvmSdk();
9095
- const provider = await this.getEvmProvider();
9096
- const evmAddresses = await provider.request({
9097
- method: "eth_accounts"
9098
- });
9066
+ await reconnect(this.wagmiConfig);
9067
+ } catch (err) {
9068
+ this.logger.warn("wagmi reconnect error (may be expected):", err.message);
9069
+ }
9070
+ const account = getAccount(this.wagmiConfig);
9071
+ let evmAddresses;
9072
+ let connectorInfo;
9073
+ if (account.isConnected && account.address) {
9074
+ evmAddresses = account.addresses ? [...account.addresses] : [account.address];
9075
+ connectorInfo = _RainbowKitWallet.extractConnectorInfo(account);
9076
+ } else {
9077
+ this.logger.warn("EVM wallet not yet connected, resuming from persisted state");
9078
+ evmAddresses = walletState.accounts.map((a) => a.metadata?.evmAddress).filter(Boolean);
9099
9079
  if (evmAddresses.length === 0) {
9100
- this.logger.error("No accounts found!");
9101
- throw new Error("No accounts found!");
9080
+ this.logger.warn("No persisted EVM addresses, cannot resume");
9081
+ this.onDisconnect();
9082
+ return;
9102
9083
  }
9103
- await this.resumeWithAccounts(evmAddresses, (accounts) => {
9104
- setAccounts(this.store, {
9105
- walletId: this.id,
9106
- accounts
9107
- });
9108
- });
9109
- } catch (error) {
9110
- this.logger.error("Error resuming session:", error.message);
9111
- this.onDisconnect();
9112
- throw error;
9084
+ connectorInfo = {};
9113
9085
  }
9086
+ if (!connectorInfo.name && walletState.accounts.length > 0) {
9087
+ const first = walletState.accounts[0];
9088
+ const persistedName = first.metadata?.connectorName;
9089
+ const persistedIcon = first.metadata?.connectorIcon;
9090
+ if (persistedName) connectorInfo.name = persistedName;
9091
+ if (persistedIcon) connectorInfo.icon = persistedIcon;
9092
+ }
9093
+ this.applyConnectorMetadata(connectorInfo);
9094
+ await this.resumeWithAccounts(evmAddresses, (accounts) => {
9095
+ setAccounts(this.store, {
9096
+ walletId: this.id,
9097
+ accounts
9098
+ });
9099
+ }, connectorInfo);
9114
9100
  };
9115
9101
  };
9116
9102
 
@@ -9227,7 +9213,7 @@ async function deriveAlgorandAccountFromEd25519(ed25519Seed) {
9227
9213
 
9228
9214
  // src/wallets/web3auth.ts
9229
9215
  var LOCAL_STORAGE_WEB3AUTH_KEY = `${LOCAL_STORAGE_KEY}:web3auth`;
9230
- var ICON15 = `data:image/svg+xml;base64,${btoa(`
9216
+ var ICON14 = `data:image/svg+xml;base64,${btoa(`
9231
9217
  <svg viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
9232
9218
  <rect fill="#0364FF" width="40" height="40" rx="8"/>
9233
9219
  <path fill="#FFFFFF" d="M20 8c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12S26.627 8 20 8zm0 21.6c-5.302 0-9.6-4.298-9.6-9.6S14.698 10.4 20 10.4s9.6 4.298 9.6 9.6-4.298 9.6-9.6 9.6zm0-16.8c-3.976 0-7.2 3.224-7.2 7.2s3.224 7.2 7.2 7.2 7.2-3.224 7.2-7.2-3.224-7.2-7.2-7.2zm0 12c-2.651 0-4.8-2.149-4.8-4.8s2.149-4.8 4.8-4.8 4.8 2.149 4.8 4.8-2.149 4.8-4.8 4.8z"/>
@@ -9284,7 +9270,7 @@ var Web3AuthWallet = class extends BaseWallet {
9284
9270
  }
9285
9271
  static defaultMetadata = {
9286
9272
  name: "Web3Auth",
9287
- icon: ICON15
9273
+ icon: ICON14
9288
9274
  };
9289
9275
  /**
9290
9276
  * Initialize the Web3Auth client
@@ -9793,7 +9779,7 @@ var Web3AuthWallet = class extends BaseWallet {
9793
9779
 
9794
9780
  // src/wallets/w3wallet.ts
9795
9781
  import algosdk13 from "algosdk";
9796
- var ICON16 = `data:image/svg+xml;base64,${btoa(`
9782
+ var ICON15 = `data:image/svg+xml;base64,${btoa(`
9797
9783
  <svg width="860" height="860" viewBox="0 0 860 860" fill="none" xmlns="http://www.w3.org/2000/svg">
9798
9784
  <rect width="860" height="860" rx="30" fill="#151923"/>
9799
9785
  <path d="M766 207L496.627 623.406C463.521 675.336 382.014 652.248 382.014 590.941V432.568L260.638 623.28C227.559 675.255 146 652.186 146 590.854V274.844H234.646V499.761L356.022 309.049C389.101 257.074 470.66 280.143 470.66 341.475V499.978L660.146 207L766 207Z" fill="#4BB7D1"/>
@@ -9814,7 +9800,7 @@ var W3Wallet = class extends BaseWallet {
9814
9800
  }
9815
9801
  static defaultMetadata = {
9816
9802
  name: "W3 Wallet",
9817
- icon: ICON16
9803
+ icon: ICON15
9818
9804
  };
9819
9805
  async initializeClient() {
9820
9806
  this.logger.info("Initializing client...");
@@ -9944,10 +9930,9 @@ function createWalletMap() {
9944
9930
  ["kmd" /* KMD */]: KmdWallet,
9945
9931
  ["lute" /* LUTE */]: LuteWallet,
9946
9932
  ["magic" /* MAGIC */]: MagicAuth,
9947
- ["metamask" /* METAMASK */]: MetaMaskWallet,
9948
9933
  ["mnemonic" /* MNEMONIC */]: MnemonicWallet,
9949
9934
  ["pera" /* PERA */]: PeraWallet,
9950
- ["rainbow" /* RAINBOW */]: RainbowWallet,
9935
+ ["rainbowkit" /* RAINBOWKIT */]: RainbowKitWallet,
9951
9936
  ["walletconnect" /* WALLETCONNECT */]: WalletConnect,
9952
9937
  ["web3auth" /* WEB3AUTH */]: Web3AuthWallet,
9953
9938
  ["w3-wallet" /* W3_WALLET */]: W3Wallet
@@ -10440,12 +10425,11 @@ export {
10440
10425
  LogLevel,
10441
10426
  LuteWallet,
10442
10427
  MagicAuth,
10443
- MetaMaskWallet,
10444
10428
  MnemonicWallet,
10445
10429
  NetworkConfigBuilder,
10446
10430
  NetworkId,
10447
10431
  PeraWallet,
10448
- RainbowWallet,
10432
+ RainbowKitWallet,
10449
10433
  ScopeType,
10450
10434
  SecureKeyContainer,
10451
10435
  SessionError,