decane-connect-kit 0.0.1 → 1.0.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,8 +32,12 @@ function createRegistry(emitter) {
32
32
  };
33
33
  if (incoming.chain === "evm") {
34
34
  account.evmProvider = incoming.provider;
35
- } else {
35
+ } else if (incoming.chain === "solana") {
36
36
  account.solanaProvider = incoming.provider;
37
+ } else if (incoming.chain === "tron") {
38
+ account.tronProvider = incoming.provider;
39
+ } else if (incoming.chain === "bitcoin") {
40
+ account.bitcoinProvider = incoming.provider;
37
41
  }
38
42
  return account;
39
43
  }
@@ -44,12 +48,10 @@ function createRegistry(emitter) {
44
48
  incoming.chain
45
49
  ].sort();
46
50
  }
47
- if (incoming.chain === "evm") {
48
- existing.evmProvider = incoming.provider;
49
- }
50
- if (incoming.chain === "solana") {
51
- existing.solanaProvider = incoming.provider;
52
- }
51
+ if (incoming.chain === "evm") existing.evmProvider = incoming.provider;
52
+ if (incoming.chain === "solana") existing.solanaProvider = incoming.provider;
53
+ if (incoming.chain === "tron") existing.tronProvider = incoming.provider;
54
+ if (incoming.chain === "bitcoin") existing.bitcoinProvider = incoming.provider;
53
55
  if (!existing.icon && incoming.icon) existing.icon = incoming.icon;
54
56
  }
55
57
  function registerOrMerge(incoming) {
@@ -96,9 +98,9 @@ function createRegistry(emitter) {
96
98
  all: () => [...wallets],
97
99
  evm: () => wallets.filter((w) => w.chainSupport.includes("evm")),
98
100
  solana: () => wallets.filter((w) => w.chainSupport.includes("solana")),
99
- multiChain: () => wallets.filter(
100
- (w) => w.chainSupport.includes("evm") && w.chainSupport.includes("solana")
101
- ),
101
+ tron: () => wallets.filter((w) => w.chainSupport.includes("tron")),
102
+ bitcoin: () => wallets.filter((w) => w.chainSupport.includes("bitcoin")),
103
+ multiChain: () => wallets.filter((w) => w.chainSupport.length >= 2),
102
104
  byId: (id) => wallets.find((w) => w.id === id)
103
105
  }
104
106
  };
@@ -681,11 +683,37 @@ var BUILT_IN_CHAINS = [
681
683
  name: "Solana Devnet",
682
684
  nativeCurrency: SOL,
683
685
  blockExplorerUrl: "https://solscan.io/?cluster=devnet"
686
+ },
687
+ // ── Tron ─────────────────────────────────────────────────────────────────────
688
+ {
689
+ id: "tron:mainnet",
690
+ type: "tron",
691
+ name: "Tron",
692
+ nativeCurrency: { name: "Tronix", symbol: "TRX", decimals: 6 },
693
+ blockExplorerUrl: "https://tronscan.org"
694
+ },
695
+ {
696
+ id: "tron:shasta",
697
+ type: "tron",
698
+ name: "Tron Shasta Testnet",
699
+ nativeCurrency: { name: "Tronix", symbol: "TRX", decimals: 6 },
700
+ blockExplorerUrl: "https://shasta.tronscan.org",
701
+ isCustom: true
702
+ },
703
+ // ── Bitcoin ───────────────────────────────────────────────────────────────────
704
+ {
705
+ id: "bip122:mainnet",
706
+ type: "bitcoin",
707
+ name: "Bitcoin",
708
+ nativeCurrency: { name: "Bitcoin", symbol: "BTC", decimals: 8 },
709
+ blockExplorerUrl: "https://blockstream.info"
684
710
  }
685
711
  ];
686
712
  function getChainType(caip2Id) {
687
713
  if (caip2Id.startsWith("eip155:")) return "evm";
688
714
  if (caip2Id.startsWith("solana:")) return "solana";
715
+ if (caip2Id.startsWith("tron:")) return "tron";
716
+ if (caip2Id.startsWith("bip122:")) return "bitcoin";
689
717
  throw new Error(`Cannot determine chain type from: ${caip2Id}`);
690
718
  }
691
719
  function toCAIP10(chainId, address) {
@@ -701,6 +729,14 @@ var SOLANA_METHODS = /* @__PURE__ */ new Set([
701
729
  "wallet_signMessage"
702
730
  // wallet_signTypedData is EVM-only — never included here
703
731
  ]);
732
+ var TRON_METHODS = /* @__PURE__ */ new Set([
733
+ "wallet_sendTransaction",
734
+ "wallet_signMessage"
735
+ ]);
736
+ var BITCOIN_METHODS = /* @__PURE__ */ new Set([
737
+ "wallet_sendTransaction",
738
+ "wallet_signMessage"
739
+ ]);
704
740
  var CHAINS = {
705
741
  // Tier-1
706
742
  ETHEREUM: "eip155:1",
@@ -766,13 +802,17 @@ var CHAINS = {
766
802
  LOCALHOST: "eip155:31337",
767
803
  // Solana
768
804
  SOLANA: "solana:mainnet",
769
- SOLANA_DEVNET: "solana:devnet"
805
+ SOLANA_DEVNET: "solana:devnet",
806
+ // Tron
807
+ TRON: "tron:mainnet",
808
+ // Bitcoin
809
+ BITCOIN: "bip122:mainnet"
770
810
  };
771
811
  function validateCustomChain(chain) {
772
812
  if (!chain || typeof chain !== "object") return false;
773
813
  const c = chain;
774
814
  if (typeof c.id !== "string" || !c.id) return false;
775
- if (c.type !== "evm" && c.type !== "solana") return false;
815
+ if (c.type !== "evm" && c.type !== "solana" && c.type !== "tron" && c.type !== "bitcoin") return false;
776
816
  if (typeof c.name !== "string" || !c.name) return false;
777
817
  if (!c.nativeCurrency || typeof c.nativeCurrency !== "object") return false;
778
818
  if (c.type === "evm" && (typeof c.chainId !== "number" || !c.id.startsWith("eip155:")))
@@ -991,7 +1031,12 @@ async function walletStandardRequest(wallet, method, payload, account) {
991
1031
  );
992
1032
  }
993
1033
  }
1034
+ function isSolanaAccount(a) {
1035
+ return !a.chains?.length || a.chains.some((c) => c.startsWith("solana:"));
1036
+ }
994
1037
  async function getSolanaAddress(wallet) {
1038
+ const pre = wallet.accounts.find(isSolanaAccount);
1039
+ if (pre) return base58.encode(pre.publicKey);
995
1040
  const connectFeature = wallet.features["standard:connect"];
996
1041
  if (!connectFeature) {
997
1042
  throw new DecaneError(
@@ -1000,13 +1045,157 @@ async function getSolanaAddress(wallet) {
1000
1045
  );
1001
1046
  }
1002
1047
  const { accounts } = await connectFeature.connect();
1003
- if (!accounts?.[0]) {
1048
+ const solanaAccount = accounts.find(isSolanaAccount);
1049
+ if (!solanaAccount) {
1004
1050
  throw new DecaneError(
1005
1051
  4001 /* USER_REJECTED */,
1006
1052
  "No Solana accounts returned"
1007
1053
  );
1008
1054
  }
1009
- return base58.encode(accounts[0].publicKey);
1055
+ return base58.encode(solanaAccount.publicKey);
1056
+ }
1057
+
1058
+ // src/tron/adapter.ts
1059
+ function getTronWeb() {
1060
+ if (typeof window === "undefined") return void 0;
1061
+ const win = window;
1062
+ return win.tronLink?.tronWeb ?? win.tronWeb;
1063
+ }
1064
+ async function getTronAddress(provider) {
1065
+ const addr = getTronWeb()?.defaultAddress?.base58;
1066
+ if (addr) return addr;
1067
+ try {
1068
+ const result = await provider.request({ method: "tron_requestAccounts" });
1069
+ const accounts = result;
1070
+ if (accounts?.[0]) return accounts[0];
1071
+ } catch {
1072
+ }
1073
+ const result2 = await provider.request({ method: "tron_accounts" }).catch(() => null);
1074
+ const accounts2 = result2;
1075
+ if (accounts2?.[0]) return accounts2[0];
1076
+ throw new DecaneError(4001 /* USER_REJECTED */, "No Tron accounts returned");
1077
+ }
1078
+ async function tronRequest(provider, method, payload, account) {
1079
+ switch (method) {
1080
+ case "wallet_signMessage":
1081
+ return provider.request({
1082
+ method: "tron_signMessage",
1083
+ params: { message: payload }
1084
+ });
1085
+ case "wallet_sendTransaction": {
1086
+ const tronWeb = getTronWeb();
1087
+ const p = payload;
1088
+ const toAddress = p.to;
1089
+ const sunAmount = typeof p.value === "string" ? parseInt(p.value, 10) : p.value ?? 0;
1090
+ let unsignedTx;
1091
+ if (tronWeb?.transactionBuilder?.sendTrx) {
1092
+ if (p.data && p.data !== "0x") {
1093
+ const res = await tronWeb.transactionBuilder.triggerSmartContract(
1094
+ toAddress,
1095
+ "transfer(address,uint256)",
1096
+ { callValue: 0, feeLimit: 1e8 },
1097
+ [],
1098
+ account
1099
+ );
1100
+ unsignedTx = res.transaction;
1101
+ } else {
1102
+ unsignedTx = await tronWeb.transactionBuilder.sendTrx(toAddress, sunAmount, account);
1103
+ }
1104
+ } else {
1105
+ const host = tronWeb?.fullNode?.host ?? "https://api.trongrid.io";
1106
+ const endpoint = p.data && p.data !== "0x" ? `${host}/wallet/triggersmartcontract` : `${host}/wallet/createtransaction`;
1107
+ const body = p.data && p.data !== "0x" ? {
1108
+ owner_address: account,
1109
+ contract_address: toAddress,
1110
+ function_selector: "transfer(address,uint256)",
1111
+ fee_limit: 1e8,
1112
+ call_value: 0,
1113
+ visible: true
1114
+ } : {
1115
+ owner_address: account,
1116
+ to_address: toAddress,
1117
+ amount: sunAmount,
1118
+ visible: true
1119
+ };
1120
+ const res = await fetch(endpoint, {
1121
+ method: "POST",
1122
+ headers: { "Content-Type": "application/json" },
1123
+ body: JSON.stringify(body)
1124
+ });
1125
+ if (!res.ok) {
1126
+ throw new DecaneError(
1127
+ 5022 /* PROVIDER_ERROR */,
1128
+ `Failed to build Tron transaction: ${res.statusText}`
1129
+ );
1130
+ }
1131
+ const json = await res.json();
1132
+ unsignedTx = json.transaction ?? json;
1133
+ }
1134
+ let signedTx;
1135
+ if (tronWeb?.trx?.sign) {
1136
+ signedTx = await tronWeb.trx.sign(unsignedTx);
1137
+ } else {
1138
+ signedTx = await provider.request({
1139
+ method: "tron_signTransaction",
1140
+ params: unsignedTx
1141
+ });
1142
+ }
1143
+ let txid;
1144
+ if (tronWeb?.trx?.sendRawTransaction) {
1145
+ const result = await tronWeb.trx.sendRawTransaction(signedTx);
1146
+ txid = result?.txid;
1147
+ } else {
1148
+ const result = await provider.request({
1149
+ method: "tron_sendRawTransaction",
1150
+ params: signedTx
1151
+ });
1152
+ txid = result?.txid;
1153
+ }
1154
+ if (!txid) {
1155
+ throw new DecaneError(5022 /* PROVIDER_ERROR */, "Tron transaction broadcast failed");
1156
+ }
1157
+ return txid;
1158
+ }
1159
+ default:
1160
+ throw new DecaneError(
1161
+ 5030 /* METHOD_NOT_SUPPORTED */,
1162
+ `Tron adapter: unsupported method ${method}`
1163
+ );
1164
+ }
1165
+ }
1166
+
1167
+ // src/bitcoin/adapter.ts
1168
+ async function getBitcoinAddress(provider) {
1169
+ const result = await provider.requestAccounts();
1170
+ if (!result || result.length === 0) {
1171
+ throw new DecaneError(4001 /* USER_REJECTED */, "No Bitcoin accounts returned");
1172
+ }
1173
+ const first = result[0];
1174
+ return typeof first === "string" ? first : first.address;
1175
+ }
1176
+ async function bitcoinRequest(provider, method, payload, account) {
1177
+ switch (method) {
1178
+ case "wallet_signMessage":
1179
+ return provider.signMessage(payload);
1180
+ case "wallet_sendTransaction": {
1181
+ const { to, value } = payload;
1182
+ if (provider.sendBitcoin) {
1183
+ return provider.sendBitcoin(to, value);
1184
+ }
1185
+ if (provider.send) {
1186
+ return provider.send({ to, value, from: account });
1187
+ }
1188
+ throw new DecaneError(
1189
+ 5030 /* METHOD_NOT_SUPPORTED */,
1190
+ "Bitcoin wallet does not support sendBitcoin"
1191
+ );
1192
+ }
1193
+ default:
1194
+ throw new DecaneError(
1195
+ 5030 /* METHOD_NOT_SUPPORTED */,
1196
+ `Bitcoin adapter: unsupported method ${method}`
1197
+ );
1198
+ }
1010
1199
  }
1011
1200
 
1012
1201
  // src/core/router.ts
@@ -1057,20 +1246,23 @@ function createRouter(opts) {
1057
1246
  const chainType = getChainType(chainId);
1058
1247
  if (chainType === "evm" && !connectedWallet.evmProvider) continue;
1059
1248
  if (chainType === "solana" && !connectedWallet.solanaProvider) continue;
1249
+ if (chainType === "tron" && !connectedWallet.tronProvider) continue;
1250
+ if (chainType === "bitcoin" && !connectedWallet.bitcoinProvider) continue;
1060
1251
  let address;
1061
1252
  if (chainType === "evm") {
1062
1253
  address = await getEvmAddress(connectedWallet.evmProvider);
1063
1254
  if (chain.isCustom) {
1064
- await addEthereumChain(
1065
- connectedWallet.evmProvider,
1066
- chain
1067
- ).catch(() => {
1255
+ await addEthereumChain(connectedWallet.evmProvider, chain).catch(() => {
1068
1256
  });
1069
1257
  }
1070
- } else {
1258
+ } else if (chainType === "solana") {
1071
1259
  address = await getSolanaAddress(connectedWallet.solanaProvider);
1260
+ } else if (chainType === "tron") {
1261
+ address = await getTronAddress(connectedWallet.tronProvider);
1262
+ } else {
1263
+ address = await getBitcoinAddress(connectedWallet.bitcoinProvider);
1072
1264
  }
1073
- const methodSet = chainType === "evm" ? EVM_METHODS : SOLANA_METHODS;
1265
+ const methodSet = chainType === "evm" ? EVM_METHODS : chainType === "solana" ? SOLANA_METHODS : chainType === "tron" ? TRON_METHODS : BITCOIN_METHODS;
1074
1266
  const grantedMethods = (scope?.methods ?? []).filter(
1075
1267
  (m) => methodSet.has(m)
1076
1268
  );
@@ -1149,19 +1341,13 @@ function createRouter(opts) {
1149
1341
  );
1150
1342
  }
1151
1343
  if (chainType === "evm") {
1152
- return eip1193Request(
1153
- wallet.evmProvider,
1154
- method,
1155
- payload,
1156
- account
1157
- );
1344
+ return eip1193Request(wallet.evmProvider, method, payload, account);
1345
+ } else if (chainType === "solana") {
1346
+ return walletStandardRequest(wallet.solanaProvider, method, payload, account);
1347
+ } else if (chainType === "tron") {
1348
+ return tronRequest(wallet.tronProvider, method, payload, account);
1158
1349
  } else {
1159
- return walletStandardRequest(
1160
- wallet.solanaProvider,
1161
- method,
1162
- payload,
1163
- account
1164
- );
1350
+ return bitcoinRequest(wallet.bitcoinProvider, method, payload, account);
1165
1351
  }
1166
1352
  }
1167
1353
  return { request };
@@ -1216,6 +1402,12 @@ function createProvider(opts) {
1216
1402
  get multiChainWallets() {
1217
1403
  return registry.get.multiChain();
1218
1404
  },
1405
+ get tronWallets() {
1406
+ return registry.get.tron();
1407
+ },
1408
+ get bitcoinWallets() {
1409
+ return registry.get.bitcoin();
1410
+ },
1219
1411
  // ── Connection ──────────────────────────────────────────────────────────
1220
1412
  async connect(wallet, preferredChain) {
1221
1413
  if (session.get()) {
@@ -1224,13 +1416,19 @@ function createProvider(opts) {
1224
1416
  }
1225
1417
  _connectedWallet = wallet;
1226
1418
  const support = wallet.chainSupport;
1227
- if (support.includes("evm") && preferredChain !== "solana") {
1419
+ const hasEvm = support.includes("evm") && preferredChain !== "solana" && preferredChain !== "tron" && preferredChain !== "bitcoin";
1420
+ const hasSol = support.includes("solana") && preferredChain !== "evm" && preferredChain !== "tron" && preferredChain !== "bitcoin";
1421
+ const hasTron = support.includes("tron") && preferredChain !== "evm" && preferredChain !== "solana" && preferredChain !== "bitcoin";
1422
+ const hasBitcoin = support.includes("bitcoin") && preferredChain !== "evm" && preferredChain !== "solana" && preferredChain !== "tron";
1423
+ if (hasEvm) {
1228
1424
  _activeChain = config.defaultChain?.startsWith("eip155:") ? config.defaultChain : "eip155:1";
1229
- } else {
1425
+ } else if (hasSol) {
1230
1426
  _activeChain = "solana:mainnet";
1427
+ } else if (hasTron) {
1428
+ _activeChain = "tron:mainnet";
1429
+ } else if (hasBitcoin) {
1430
+ _activeChain = "bip122:mainnet";
1231
1431
  }
1232
- const hasEvm = support.includes("evm") && preferredChain !== "solana";
1233
- const hasSol = support.includes("solana") && preferredChain !== "evm";
1234
1432
  const required = {};
1235
1433
  const optional = {};
1236
1434
  if (hasEvm) {
@@ -1254,6 +1452,34 @@ function createProvider(opts) {
1254
1452
  _activeChain = solChain;
1255
1453
  }
1256
1454
  }
1455
+ if (hasTron) {
1456
+ const tronChain = "tron:mainnet";
1457
+ const scope = {
1458
+ methods: ["wallet_sendTransaction", "wallet_signMessage"],
1459
+ accounts: [],
1460
+ events: []
1461
+ };
1462
+ if (hasEvm || hasSol) {
1463
+ optional[tronChain] = scope;
1464
+ } else {
1465
+ required[tronChain] = scope;
1466
+ _activeChain = tronChain;
1467
+ }
1468
+ }
1469
+ if (hasBitcoin) {
1470
+ const btcChain = "bip122:mainnet";
1471
+ const scope = {
1472
+ methods: ["wallet_sendTransaction", "wallet_signMessage"],
1473
+ accounts: [],
1474
+ events: []
1475
+ };
1476
+ if (hasEvm || hasSol || hasTron) {
1477
+ optional[btcChain] = scope;
1478
+ } else {
1479
+ required[btcChain] = scope;
1480
+ _activeChain = btcChain;
1481
+ }
1482
+ }
1257
1483
  await router.request({
1258
1484
  method: "wallet_requestPermissions",
1259
1485
  params: [{ requiredScopes: required, optionalScopes: optional }, wallet.id]
@@ -1321,6 +1547,58 @@ function createProvider(opts) {
1321
1547
  return provider;
1322
1548
  }
1323
1549
 
1550
+ // src/evm/bitcoinSnap.ts
1551
+ var BITCOIN_SNAP_IDS = [
1552
+ "npm:@metamask/bitcoin-snap",
1553
+ "npm:@consensys/btc-snap"
1554
+ ];
1555
+ async function detectBitcoinSnap(provider) {
1556
+ try {
1557
+ const snaps = await provider.request({ method: "wallet_getSnaps" });
1558
+ if (!snaps) return null;
1559
+ for (const id of BITCOIN_SNAP_IDS) {
1560
+ const snap = snaps[id];
1561
+ if (snap && snap.blocked !== true) return id;
1562
+ }
1563
+ return null;
1564
+ } catch {
1565
+ return null;
1566
+ }
1567
+ }
1568
+ function makeBitcoinSnapProvider(snapId, evmProvider) {
1569
+ function invoke(method, params) {
1570
+ return evmProvider.request({
1571
+ method: "wallet_invokeSnap",
1572
+ params: [{ snapId, request: { method, params } }]
1573
+ });
1574
+ }
1575
+ return {
1576
+ async requestAccounts() {
1577
+ const result = await invoke("btc_getAddress").catch(() => invoke("btc_getPublicKey"));
1578
+ if (typeof result === "string") return [result];
1579
+ if (Array.isArray(result)) return result;
1580
+ return [];
1581
+ },
1582
+ async signMessage(message) {
1583
+ return invoke("btc_signMessage", [message]);
1584
+ },
1585
+ async sendBitcoin(to, value) {
1586
+ return invoke("btc_sendBitcoin", [{ to, satoshis: value }]);
1587
+ }
1588
+ };
1589
+ }
1590
+ async function tryRegisterBitcoinSnap(registry, name, icon, rdns, evmProvider) {
1591
+ const snapId = await detectBitcoinSnap(evmProvider);
1592
+ if (!snapId) return;
1593
+ registry.registerOrMerge({
1594
+ chain: "bitcoin",
1595
+ name,
1596
+ icon,
1597
+ rdns,
1598
+ provider: makeBitcoinSnapProvider(snapId, evmProvider)
1599
+ });
1600
+ }
1601
+
1324
1602
  // src/evm/discovery.ts
1325
1603
  function startEvmDiscovery(registry) {
1326
1604
  if (typeof window === "undefined") return;
@@ -1340,6 +1618,14 @@ function startEvmDiscovery(registry) {
1340
1618
  uuid: info.uuid,
1341
1619
  provider
1342
1620
  });
1621
+ tryRegisterBitcoinSnap(
1622
+ registry,
1623
+ info.name,
1624
+ info.icon,
1625
+ info.rdns,
1626
+ provider
1627
+ ).catch(() => {
1628
+ });
1343
1629
  }
1344
1630
  );
1345
1631
  window.dispatchEvent(new CustomEvent("eip6963:requestProvider"));
@@ -1380,6 +1666,53 @@ function startSolanaDiscovery(registry) {
1380
1666
  );
1381
1667
  }
1382
1668
 
1669
+ // src/tron/discovery.ts
1670
+ var TRONLINK_RDNS = "link.tron.tronlink";
1671
+ var TRONLINK_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTYiIGZpbGw9IiNGRjAwMTMiLz48cG9seWdvbiBwb2ludHM9IjEyLDkgMjMsMTYgMTIsMjMiIGZpbGw9IndoaXRlIi8+PC9zdmc+";
1672
+ function startTronDiscovery(registry) {
1673
+ let registered = false;
1674
+ function tryRegister() {
1675
+ if (registered || !window.tronLink) return;
1676
+ registered = true;
1677
+ registry.registerOrMerge({
1678
+ chain: "tron",
1679
+ name: "TronLink",
1680
+ icon: TRONLINK_ICON,
1681
+ rdns: TRONLINK_RDNS,
1682
+ provider: window.tronLink
1683
+ });
1684
+ }
1685
+ tryRegister();
1686
+ window.addEventListener("tronLink#initialized", tryRegister, { once: true });
1687
+ const start = Date.now();
1688
+ const poll = setInterval(() => {
1689
+ tryRegister();
1690
+ if (registered || Date.now() - start > 3e3) clearInterval(poll);
1691
+ }, 200);
1692
+ }
1693
+
1694
+ // src/bitcoin/discovery.ts
1695
+ var BTC_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTYiIGZpbGw9IiNGNzkzMUEiLz48dGV4dCB4PSIxNi41IiB5PSIyMiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxNiIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0id2hpdGUiIGZvbnQtZmFtaWx5PSJBcmlhbCxzeXN0ZW0tdWkiPuKmojwvdGV4dD48L3N2Zz4=";
1696
+ function startBitcoinDiscovery(registry) {
1697
+ setTimeout(() => {
1698
+ const candidates = [
1699
+ { name: "Unisat", rdns: "io.unisat.wallet", provider: window.unisat },
1700
+ // OKX shares rdns with its EVM provider — merges into the EVM entry automatically
1701
+ { name: "OKX Wallet", rdns: "com.okex.okxwallet", provider: window.okxwallet?.bitcoin }
1702
+ ];
1703
+ for (const { name, rdns, provider } of candidates) {
1704
+ if (!provider) continue;
1705
+ registry.registerOrMerge({
1706
+ chain: "bitcoin",
1707
+ name,
1708
+ icon: BTC_ICON,
1709
+ rdns,
1710
+ provider
1711
+ });
1712
+ }
1713
+ }, 150);
1714
+ }
1715
+
1383
1716
  // src/factory.ts
1384
1717
  function createDecane(config = {}) {
1385
1718
  const emitter = createEmitter();
@@ -1393,6 +1726,8 @@ function createDecane(config = {}) {
1393
1726
  const router = createRouter({ registry, session, chainStore });
1394
1727
  startEvmDiscovery(registry);
1395
1728
  startSolanaDiscovery(registry);
1729
+ startTronDiscovery(registry);
1730
+ startBitcoinDiscovery(registry);
1396
1731
  return createProvider({ config, registry, session, chainStore, router, emitter });
1397
1732
  }
1398
1733
 
@@ -1560,6 +1895,67 @@ function WalletIcon({ src, name, size = 36 }) {
1560
1895
  initial
1561
1896
  );
1562
1897
  }
1898
+ function EthCircle({ size }) {
1899
+ const s = size;
1900
+ return /* @__PURE__ */ React.createElement("svg", { width: s, height: s, viewBox: "0 0 16 16", fill: "none" }, /* @__PURE__ */ React.createElement("circle", { cx: "8", cy: "8", r: "8", fill: "#627EEA" }), /* @__PURE__ */ React.createElement("polygon", { points: "8,2.5 11.5,8 8,9.8 4.5,8", fill: "white", fillOpacity: "0.9" }), /* @__PURE__ */ React.createElement("polygon", { points: "8,9.8 11.5,8 8,13.5", fill: "white", fillOpacity: "0.6" }), /* @__PURE__ */ React.createElement("polygon", { points: "8,9.8 4.5,8 8,13.5", fill: "white", fillOpacity: "0.8" }), /* @__PURE__ */ React.createElement("polygon", { points: "8,2.5 8,9.8 4.5,8", fill: "white", fillOpacity: "0.6" }));
1901
+ }
1902
+ function SolCircle({ size }) {
1903
+ const s = size;
1904
+ return /* @__PURE__ */ React.createElement("svg", { width: s, height: s, viewBox: "0 0 16 16", fill: "none" }, /* @__PURE__ */ React.createElement("circle", { cx: "8", cy: "8", r: "8", fill: "#9945FF" }), /* @__PURE__ */ React.createElement("rect", { x: "4", y: "4.5", width: "7", height: "1.5", rx: "0.75", fill: "white", fillOpacity: "0.95" }), /* @__PURE__ */ React.createElement("rect", { x: "4", y: "7.25", width: "7", height: "1.5", rx: "0.75", fill: "white", fillOpacity: "0.95" }), /* @__PURE__ */ React.createElement("rect", { x: "4", y: "10", width: "5.5", height: "1.5", rx: "0.75", fill: "white", fillOpacity: "0.95" }));
1905
+ }
1906
+ function TronCircle({ size }) {
1907
+ const s = size;
1908
+ return /* @__PURE__ */ React.createElement("svg", { width: s, height: s, viewBox: "0 0 16 16", fill: "none" }, /* @__PURE__ */ React.createElement("circle", { cx: "8", cy: "8", r: "8", fill: "#FF0013" }), /* @__PURE__ */ React.createElement("polygon", { points: "6,4.5 12,8 6,11.5", fill: "white", fillOpacity: "0.95" }));
1909
+ }
1910
+ function BitcoinCircle({ size }) {
1911
+ const s = size;
1912
+ return /* @__PURE__ */ React.createElement("svg", { width: s, height: s, viewBox: "0 0 16 16", fill: "none" }, /* @__PURE__ */ React.createElement("circle", { cx: "8", cy: "8", r: "8", fill: "#F7931A" }), /* @__PURE__ */ React.createElement(
1913
+ "text",
1914
+ {
1915
+ x: "8.3",
1916
+ y: "11.5",
1917
+ textAnchor: "middle",
1918
+ fontSize: "8",
1919
+ fontWeight: "700",
1920
+ fill: "white",
1921
+ fontFamily: "Arial, system-ui"
1922
+ },
1923
+ "\u20BF"
1924
+ ));
1925
+ }
1926
+ function ChainCircleIcon({ chain, size }) {
1927
+ switch (chain) {
1928
+ case "evm":
1929
+ return /* @__PURE__ */ React.createElement(EthCircle, { size });
1930
+ case "solana":
1931
+ return /* @__PURE__ */ React.createElement(SolCircle, { size });
1932
+ case "tron":
1933
+ return /* @__PURE__ */ React.createElement(TronCircle, { size });
1934
+ case "bitcoin":
1935
+ return /* @__PURE__ */ React.createElement(BitcoinCircle, { size });
1936
+ }
1937
+ }
1938
+ var CHAIN_ORDER = ["evm", "solana", "tron", "bitcoin"];
1939
+ function ChainIconStack({ support, size = 16 }) {
1940
+ const ordered = CHAIN_ORDER.filter((c) => support.includes(c));
1941
+ if (ordered.length === 0) return null;
1942
+ const overlap = Math.round(size * 0.35);
1943
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", flexShrink: 0 } }, ordered.map((chain, i) => /* @__PURE__ */ React.createElement(
1944
+ "div",
1945
+ {
1946
+ key: chain,
1947
+ style: {
1948
+ marginLeft: i === 0 ? 0 : `-${overlap}px`,
1949
+ zIndex: ordered.length - i,
1950
+ position: "relative",
1951
+ borderRadius: "50%",
1952
+ lineHeight: 0,
1953
+ boxShadow: i > 0 ? `0 0 0 1.5px var(--dc-bg, #fff)` : void 0
1954
+ }
1955
+ },
1956
+ /* @__PURE__ */ React.createElement(ChainCircleIcon, { chain, size })
1957
+ )));
1958
+ }
1563
1959
 
1564
1960
  // src/ui/WalletSelector.tsx
1565
1961
  var FONT_URL = "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap";
@@ -1586,21 +1982,6 @@ function injectFont() {
1586
1982
  link.href = FONT_URL;
1587
1983
  document.head.appendChild(link);
1588
1984
  }
1589
- var badgeBase = {
1590
- display: "inline-flex",
1591
- alignItems: "center",
1592
- padding: "3px 8px",
1593
- borderRadius: 20,
1594
- fontSize: 10,
1595
- fontWeight: 700,
1596
- letterSpacing: "0.04em",
1597
- textTransform: "uppercase"
1598
- };
1599
- function ChainBadges({ chainSupport }) {
1600
- const hasEvm = chainSupport.includes("evm");
1601
- const hasSol = chainSupport.includes("solana");
1602
- return /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: 4 } }, hasEvm && /* @__PURE__ */ React2.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-evm)", color: "var(--dc-badge-evm-text)" } }, "EVM"), hasSol && /* @__PURE__ */ React2.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-sol)", color: "var(--dc-badge-sol-text)", border: "1px solid var(--dc-border)" } }, "SVM"));
1603
- }
1604
1985
  function WalletRow({ wallet, onClick }) {
1605
1986
  const [hovered, setHovered] = useState(false);
1606
1987
  const [focused, setFocused] = useState(false);
@@ -1630,7 +2011,7 @@ function WalletRow({ wallet, onClick }) {
1630
2011
  },
1631
2012
  /* @__PURE__ */ React2.createElement(WalletIcon, { src: wallet.icon || void 0, name: wallet.name, size: 38 }),
1632
2013
  /* @__PURE__ */ React2.createElement("span", { style: { flex: 1, fontSize: 14, fontWeight: 600, color: "var(--dc-text)", letterSpacing: "-0.01em" } }, wallet.name),
1633
- /* @__PURE__ */ React2.createElement(ChainBadges, { chainSupport: wallet.chainSupport }),
2014
+ /* @__PURE__ */ React2.createElement(ChainIconStack, { support: wallet.chainSupport, size: 16 }),
1634
2015
  /* @__PURE__ */ React2.createElement(
1635
2016
  "svg",
1636
2017
  {
@@ -1757,17 +2138,19 @@ function WalletSelector({
1757
2138
  const cs = w.chainSupport;
1758
2139
  if (networks === "evm") return cs.includes("evm");
1759
2140
  if (networks === "solana") return cs.includes("solana");
2141
+ if (networks === "tron") return cs.includes("tron");
2142
+ if (networks === "bitcoin") return cs.includes("bitcoin");
1760
2143
  return true;
1761
2144
  });
1762
- const baseWallets = filter === "evm" ? networkWallets.filter((w) => w.chainSupport.includes("evm")) : filter === "solana" ? networkWallets.filter((w) => w.chainSupport.includes("solana")) : networkWallets;
2145
+ const baseWallets = filter === "evm" ? networkWallets.filter((w) => w.chainSupport.includes("evm")) : filter === "solana" ? networkWallets.filter((w) => w.chainSupport.includes("solana")) : filter === "tron" ? networkWallets.filter((w) => w.chainSupport.includes("tron")) : filter === "bitcoin" ? networkWallets.filter((w) => w.chainSupport.includes("bitcoin")) : networkWallets;
1763
2146
  const filtered = q ? baseWallets.filter((w) => w.name.toLowerCase().includes(q)) : baseWallets;
1764
2147
  function handleWalletClick(wallet) {
1765
2148
  const cs = wallet.chainSupport;
1766
- const isMulti = cs.includes("evm") && cs.includes("solana");
2149
+ const isMulti = cs.length >= 2;
1767
2150
  if (isMulti && networks === "all") {
1768
2151
  setPendingWallet(wallet);
1769
2152
  } else {
1770
- const preference = networks === "evm" ? "evm" : networks === "solana" ? "solana" : void 0;
2153
+ const preference = networks === "evm" ? "evm" : networks === "solana" ? "solana" : networks === "tron" ? "tron" : networks === "bitcoin" ? "bitcoin" : void 0;
1771
2154
  onConnect(wallet, preference);
1772
2155
  onClose();
1773
2156
  }
@@ -1902,28 +2285,44 @@ function WalletSelector({
1902
2285
  },
1903
2286
  /* @__PURE__ */ React2.createElement(WalletIcon, { src: pendingWallet.icon || void 0, name: pendingWallet.name, size: 36 }),
1904
2287
  /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("div", { style: { fontSize: 14, fontWeight: 700, color: "var(--dc-text)" } }, pendingWallet.name), /* @__PURE__ */ React2.createElement("div", { style: { fontSize: 11, color: "var(--dc-text-muted)", marginTop: 2 } }, "Multi-chain wallet detected"))
1905
- ), /* @__PURE__ */ React2.createElement("div", { style: { padding: "0 12px 20px", display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React2.createElement(
2288
+ ), /* @__PURE__ */ React2.createElement("div", { style: { padding: "0 12px 20px", display: "flex", flexDirection: "column", gap: 8 } }, pendingWallet.chainSupport.includes("evm") && /* @__PURE__ */ React2.createElement(
1906
2289
  ChainOption,
1907
2290
  {
1908
2291
  label: "EVM networks",
1909
2292
  description: "Connect your Ethereum-compatible address only",
1910
- badges: /* @__PURE__ */ React2.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-evm)", color: "var(--dc-badge-evm-text)" } }, "EVM"),
2293
+ badges: /* @__PURE__ */ React2.createElement(ChainIconStack, { support: ["evm"], size: 18 }),
1911
2294
  onClick: () => handleChainChoice("evm")
1912
2295
  }
1913
- ), /* @__PURE__ */ React2.createElement(
2296
+ ), pendingWallet.chainSupport.includes("solana") && /* @__PURE__ */ React2.createElement(
1914
2297
  ChainOption,
1915
2298
  {
1916
2299
  label: "Solana network",
1917
2300
  description: "Connect your Solana address only",
1918
- badges: /* @__PURE__ */ React2.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-sol)", color: "var(--dc-badge-sol-text)", border: "1px solid var(--dc-border)" } }, "SVM"),
2301
+ badges: /* @__PURE__ */ React2.createElement(ChainIconStack, { support: ["solana"], size: 18 }),
1919
2302
  onClick: () => handleChainChoice("solana")
1920
2303
  }
2304
+ ), pendingWallet.chainSupport.includes("tron") && /* @__PURE__ */ React2.createElement(
2305
+ ChainOption,
2306
+ {
2307
+ label: "Tron network",
2308
+ description: "Connect your Tron address only",
2309
+ badges: /* @__PURE__ */ React2.createElement(ChainIconStack, { support: ["tron"], size: 18 }),
2310
+ onClick: () => handleChainChoice("tron")
2311
+ }
2312
+ ), pendingWallet.chainSupport.includes("bitcoin") && /* @__PURE__ */ React2.createElement(
2313
+ ChainOption,
2314
+ {
2315
+ label: "Bitcoin network",
2316
+ description: "Connect your Bitcoin address only",
2317
+ badges: /* @__PURE__ */ React2.createElement(ChainIconStack, { support: ["bitcoin"], size: 18 }),
2318
+ onClick: () => handleChainChoice("bitcoin")
2319
+ }
1921
2320
  ), /* @__PURE__ */ React2.createElement(
1922
2321
  ChainOption,
1923
2322
  {
1924
- label: "Both networks",
1925
- description: "Connect EVM and Solana addresses together",
1926
- badges: /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: 4 } }, /* @__PURE__ */ React2.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-evm)", color: "var(--dc-badge-evm-text)" } }, "EVM"), /* @__PURE__ */ React2.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-sol)", color: "var(--dc-badge-sol-text)", border: "1px solid var(--dc-border)" } }, "SVM")),
2323
+ label: "All networks",
2324
+ description: "Connect all supported addresses together",
2325
+ badges: /* @__PURE__ */ React2.createElement(ChainIconStack, { support: pendingWallet.chainSupport, size: 18 }),
1927
2326
  onClick: () => handleChainChoice(void 0)
1928
2327
  }
1929
2328
  )))
@@ -1998,7 +2397,7 @@ function WalletSelector({
1998
2397
  e.currentTarget.style.borderColor = "var(--dc-border)";
1999
2398
  }
2000
2399
  }
2001
- )), networks === "all" && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 10 } }, /* @__PURE__ */ React2.createElement(FilterTab, { label: "All", active: filter === "all", onClick: () => setFilter("all") }), /* @__PURE__ */ React2.createElement(FilterTab, { label: "EVM", active: filter === "evm", onClick: () => setFilter("evm") }), /* @__PURE__ */ React2.createElement(FilterTab, { label: "Solana", active: filter === "solana", onClick: () => setFilter("solana") }))), /* @__PURE__ */ React2.createElement("div", { style: { overflowY: "auto", flex: 1, padding: "0 12px 20px" } }, filtered.length > 0 ? /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((w) => /* @__PURE__ */ React2.createElement(WalletRow, { key: w.id, wallet: w, onClick: () => handleWalletClick(w) }))) : /* @__PURE__ */ React2.createElement("div", { style: { textAlign: "center", padding: "32px 16px", color: "var(--dc-text-muted)", fontSize: 13 } }, q ? `No wallets matching "${q}"` : "No wallets detected")), /* @__PURE__ */ React2.createElement("div", { style: { padding: "10px 20px 14px", borderTop: "1px solid var(--dc-border)", textAlign: "center", fontSize: 11, color: "var(--dc-text-muted)" } }, "Don't see your wallet? Make sure it's installed and unlocked"))
2400
+ )), networks === "all" && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 10 } }, /* @__PURE__ */ React2.createElement(FilterTab, { label: "All", active: filter === "all", onClick: () => setFilter("all") }), /* @__PURE__ */ React2.createElement(FilterTab, { label: "EVM", active: filter === "evm", onClick: () => setFilter("evm") }), /* @__PURE__ */ React2.createElement(FilterTab, { label: "Solana", active: filter === "solana", onClick: () => setFilter("solana") }), provider.tronWallets && provider.tronWallets.length > 0 && /* @__PURE__ */ React2.createElement(FilterTab, { label: "Tron", active: filter === "tron", onClick: () => setFilter("tron") }), provider.bitcoinWallets && provider.bitcoinWallets.length > 0 && /* @__PURE__ */ React2.createElement(FilterTab, { label: "Bitcoin", active: filter === "bitcoin", onClick: () => setFilter("bitcoin") }))), /* @__PURE__ */ React2.createElement("div", { style: { overflowY: "auto", flex: 1, padding: "0 12px 20px" } }, filtered.length > 0 ? /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((w) => /* @__PURE__ */ React2.createElement(WalletRow, { key: w.id, wallet: w, onClick: () => handleWalletClick(w) }))) : /* @__PURE__ */ React2.createElement("div", { style: { textAlign: "center", padding: "32px 16px", color: "var(--dc-text-muted)", fontSize: 13 } }, q ? `No wallets matching "${q}"` : "No wallets detected")), /* @__PURE__ */ React2.createElement("div", { style: { padding: "10px 20px 14px", borderTop: "1px solid var(--dc-border)", textAlign: "center", fontSize: 11, color: "var(--dc-text-muted)" } }, "Don't see your wallet? Make sure it's installed and unlocked"))
2002
2401
  )
2003
2402
  )
2004
2403
  );
@@ -2060,6 +2459,11 @@ function useDecaneContext() {
2060
2459
 
2061
2460
  // src/ui/hooks.ts
2062
2461
  import { useState as useState3, useEffect as useEffect2, useCallback as useCallback3 } from "react";
2462
+ function toDecaneError(err) {
2463
+ if (err instanceof DecaneError) return err;
2464
+ const msg = err instanceof Error ? err.message : err?.message != null ? String(err.message) : String(err);
2465
+ return new DecaneError(5022 /* PROVIDER_ERROR */, msg);
2466
+ }
2063
2467
  function useDecane() {
2064
2468
  const { provider, theme, setTheme } = useDecaneContext();
2065
2469
  const [, forceUpdate] = useState3(0);
@@ -2137,7 +2541,7 @@ function useChainSwitch() {
2137
2541
  });
2138
2542
  forceUpdate((n) => n + 1);
2139
2543
  } catch (err) {
2140
- const e = err instanceof DecaneError ? err : new DecaneError(5022 /* PROVIDER_ERROR */, String(err));
2544
+ const e = toDecaneError(err);
2141
2545
  setError(e);
2142
2546
  throw err;
2143
2547
  } finally {
@@ -2188,7 +2592,7 @@ function useSignMessage() {
2188
2592
  setSignature(sig);
2189
2593
  return sig;
2190
2594
  } catch (err) {
2191
- const e = err instanceof DecaneError ? err : new DecaneError(5022 /* PROVIDER_ERROR */, String(err));
2595
+ const e = toDecaneError(err);
2192
2596
  setError(e);
2193
2597
  throw err;
2194
2598
  } finally {
@@ -2226,7 +2630,7 @@ function useSendTransaction() {
2226
2630
  setTxHash(hash);
2227
2631
  return hash;
2228
2632
  } catch (err) {
2229
- const e = err instanceof DecaneError ? err : new DecaneError(5022 /* PROVIDER_ERROR */, String(err));
2633
+ const e = toDecaneError(err);
2230
2634
  setError(e);
2231
2635
  throw err;
2232
2636
  } finally {
@@ -2263,7 +2667,7 @@ function useSignTypedData() {
2263
2667
  setSignature(sig);
2264
2668
  return sig;
2265
2669
  } catch (err) {
2266
- const e = err instanceof DecaneError ? err : new DecaneError(5022 /* PROVIDER_ERROR */, String(err));
2670
+ const e = toDecaneError(err);
2267
2671
  setError(e);
2268
2672
  throw err;
2269
2673
  } finally {