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.cjs CHANGED
@@ -80,8 +80,12 @@ function createRegistry(emitter) {
80
80
  };
81
81
  if (incoming.chain === "evm") {
82
82
  account.evmProvider = incoming.provider;
83
- } else {
83
+ } else if (incoming.chain === "solana") {
84
84
  account.solanaProvider = incoming.provider;
85
+ } else if (incoming.chain === "tron") {
86
+ account.tronProvider = incoming.provider;
87
+ } else if (incoming.chain === "bitcoin") {
88
+ account.bitcoinProvider = incoming.provider;
85
89
  }
86
90
  return account;
87
91
  }
@@ -92,12 +96,10 @@ function createRegistry(emitter) {
92
96
  incoming.chain
93
97
  ].sort();
94
98
  }
95
- if (incoming.chain === "evm") {
96
- existing.evmProvider = incoming.provider;
97
- }
98
- if (incoming.chain === "solana") {
99
- existing.solanaProvider = incoming.provider;
100
- }
99
+ if (incoming.chain === "evm") existing.evmProvider = incoming.provider;
100
+ if (incoming.chain === "solana") existing.solanaProvider = incoming.provider;
101
+ if (incoming.chain === "tron") existing.tronProvider = incoming.provider;
102
+ if (incoming.chain === "bitcoin") existing.bitcoinProvider = incoming.provider;
101
103
  if (!existing.icon && incoming.icon) existing.icon = incoming.icon;
102
104
  }
103
105
  function registerOrMerge(incoming) {
@@ -144,9 +146,9 @@ function createRegistry(emitter) {
144
146
  all: () => [...wallets],
145
147
  evm: () => wallets.filter((w) => w.chainSupport.includes("evm")),
146
148
  solana: () => wallets.filter((w) => w.chainSupport.includes("solana")),
147
- multiChain: () => wallets.filter(
148
- (w) => w.chainSupport.includes("evm") && w.chainSupport.includes("solana")
149
- ),
149
+ tron: () => wallets.filter((w) => w.chainSupport.includes("tron")),
150
+ bitcoin: () => wallets.filter((w) => w.chainSupport.includes("bitcoin")),
151
+ multiChain: () => wallets.filter((w) => w.chainSupport.length >= 2),
150
152
  byId: (id) => wallets.find((w) => w.id === id)
151
153
  }
152
154
  };
@@ -729,11 +731,37 @@ var BUILT_IN_CHAINS = [
729
731
  name: "Solana Devnet",
730
732
  nativeCurrency: SOL,
731
733
  blockExplorerUrl: "https://solscan.io/?cluster=devnet"
734
+ },
735
+ // ── Tron ─────────────────────────────────────────────────────────────────────
736
+ {
737
+ id: "tron:mainnet",
738
+ type: "tron",
739
+ name: "Tron",
740
+ nativeCurrency: { name: "Tronix", symbol: "TRX", decimals: 6 },
741
+ blockExplorerUrl: "https://tronscan.org"
742
+ },
743
+ {
744
+ id: "tron:shasta",
745
+ type: "tron",
746
+ name: "Tron Shasta Testnet",
747
+ nativeCurrency: { name: "Tronix", symbol: "TRX", decimals: 6 },
748
+ blockExplorerUrl: "https://shasta.tronscan.org",
749
+ isCustom: true
750
+ },
751
+ // ── Bitcoin ───────────────────────────────────────────────────────────────────
752
+ {
753
+ id: "bip122:mainnet",
754
+ type: "bitcoin",
755
+ name: "Bitcoin",
756
+ nativeCurrency: { name: "Bitcoin", symbol: "BTC", decimals: 8 },
757
+ blockExplorerUrl: "https://blockstream.info"
732
758
  }
733
759
  ];
734
760
  function getChainType(caip2Id) {
735
761
  if (caip2Id.startsWith("eip155:")) return "evm";
736
762
  if (caip2Id.startsWith("solana:")) return "solana";
763
+ if (caip2Id.startsWith("tron:")) return "tron";
764
+ if (caip2Id.startsWith("bip122:")) return "bitcoin";
737
765
  throw new Error(`Cannot determine chain type from: ${caip2Id}`);
738
766
  }
739
767
  function toCAIP10(chainId, address) {
@@ -749,6 +777,14 @@ var SOLANA_METHODS = /* @__PURE__ */ new Set([
749
777
  "wallet_signMessage"
750
778
  // wallet_signTypedData is EVM-only — never included here
751
779
  ]);
780
+ var TRON_METHODS = /* @__PURE__ */ new Set([
781
+ "wallet_sendTransaction",
782
+ "wallet_signMessage"
783
+ ]);
784
+ var BITCOIN_METHODS = /* @__PURE__ */ new Set([
785
+ "wallet_sendTransaction",
786
+ "wallet_signMessage"
787
+ ]);
752
788
  var CHAINS = {
753
789
  // Tier-1
754
790
  ETHEREUM: "eip155:1",
@@ -814,13 +850,17 @@ var CHAINS = {
814
850
  LOCALHOST: "eip155:31337",
815
851
  // Solana
816
852
  SOLANA: "solana:mainnet",
817
- SOLANA_DEVNET: "solana:devnet"
853
+ SOLANA_DEVNET: "solana:devnet",
854
+ // Tron
855
+ TRON: "tron:mainnet",
856
+ // Bitcoin
857
+ BITCOIN: "bip122:mainnet"
818
858
  };
819
859
  function validateCustomChain(chain) {
820
860
  if (!chain || typeof chain !== "object") return false;
821
861
  const c = chain;
822
862
  if (typeof c.id !== "string" || !c.id) return false;
823
- if (c.type !== "evm" && c.type !== "solana") return false;
863
+ if (c.type !== "evm" && c.type !== "solana" && c.type !== "tron" && c.type !== "bitcoin") return false;
824
864
  if (typeof c.name !== "string" || !c.name) return false;
825
865
  if (!c.nativeCurrency || typeof c.nativeCurrency !== "object") return false;
826
866
  if (c.type === "evm" && (typeof c.chainId !== "number" || !c.id.startsWith("eip155:")))
@@ -1039,7 +1079,12 @@ async function walletStandardRequest(wallet, method, payload, account) {
1039
1079
  );
1040
1080
  }
1041
1081
  }
1082
+ function isSolanaAccount(a) {
1083
+ return !a.chains?.length || a.chains.some((c) => c.startsWith("solana:"));
1084
+ }
1042
1085
  async function getSolanaAddress(wallet) {
1086
+ const pre = wallet.accounts.find(isSolanaAccount);
1087
+ if (pre) return import_base.base58.encode(pre.publicKey);
1043
1088
  const connectFeature = wallet.features["standard:connect"];
1044
1089
  if (!connectFeature) {
1045
1090
  throw new DecaneError(
@@ -1048,13 +1093,157 @@ async function getSolanaAddress(wallet) {
1048
1093
  );
1049
1094
  }
1050
1095
  const { accounts } = await connectFeature.connect();
1051
- if (!accounts?.[0]) {
1096
+ const solanaAccount = accounts.find(isSolanaAccount);
1097
+ if (!solanaAccount) {
1052
1098
  throw new DecaneError(
1053
1099
  4001 /* USER_REJECTED */,
1054
1100
  "No Solana accounts returned"
1055
1101
  );
1056
1102
  }
1057
- return import_base.base58.encode(accounts[0].publicKey);
1103
+ return import_base.base58.encode(solanaAccount.publicKey);
1104
+ }
1105
+
1106
+ // src/tron/adapter.ts
1107
+ function getTronWeb() {
1108
+ if (typeof window === "undefined") return void 0;
1109
+ const win = window;
1110
+ return win.tronLink?.tronWeb ?? win.tronWeb;
1111
+ }
1112
+ async function getTronAddress(provider) {
1113
+ const addr = getTronWeb()?.defaultAddress?.base58;
1114
+ if (addr) return addr;
1115
+ try {
1116
+ const result = await provider.request({ method: "tron_requestAccounts" });
1117
+ const accounts = result;
1118
+ if (accounts?.[0]) return accounts[0];
1119
+ } catch {
1120
+ }
1121
+ const result2 = await provider.request({ method: "tron_accounts" }).catch(() => null);
1122
+ const accounts2 = result2;
1123
+ if (accounts2?.[0]) return accounts2[0];
1124
+ throw new DecaneError(4001 /* USER_REJECTED */, "No Tron accounts returned");
1125
+ }
1126
+ async function tronRequest(provider, method, payload, account) {
1127
+ switch (method) {
1128
+ case "wallet_signMessage":
1129
+ return provider.request({
1130
+ method: "tron_signMessage",
1131
+ params: { message: payload }
1132
+ });
1133
+ case "wallet_sendTransaction": {
1134
+ const tronWeb = getTronWeb();
1135
+ const p = payload;
1136
+ const toAddress = p.to;
1137
+ const sunAmount = typeof p.value === "string" ? parseInt(p.value, 10) : p.value ?? 0;
1138
+ let unsignedTx;
1139
+ if (tronWeb?.transactionBuilder?.sendTrx) {
1140
+ if (p.data && p.data !== "0x") {
1141
+ const res = await tronWeb.transactionBuilder.triggerSmartContract(
1142
+ toAddress,
1143
+ "transfer(address,uint256)",
1144
+ { callValue: 0, feeLimit: 1e8 },
1145
+ [],
1146
+ account
1147
+ );
1148
+ unsignedTx = res.transaction;
1149
+ } else {
1150
+ unsignedTx = await tronWeb.transactionBuilder.sendTrx(toAddress, sunAmount, account);
1151
+ }
1152
+ } else {
1153
+ const host = tronWeb?.fullNode?.host ?? "https://api.trongrid.io";
1154
+ const endpoint = p.data && p.data !== "0x" ? `${host}/wallet/triggersmartcontract` : `${host}/wallet/createtransaction`;
1155
+ const body = p.data && p.data !== "0x" ? {
1156
+ owner_address: account,
1157
+ contract_address: toAddress,
1158
+ function_selector: "transfer(address,uint256)",
1159
+ fee_limit: 1e8,
1160
+ call_value: 0,
1161
+ visible: true
1162
+ } : {
1163
+ owner_address: account,
1164
+ to_address: toAddress,
1165
+ amount: sunAmount,
1166
+ visible: true
1167
+ };
1168
+ const res = await fetch(endpoint, {
1169
+ method: "POST",
1170
+ headers: { "Content-Type": "application/json" },
1171
+ body: JSON.stringify(body)
1172
+ });
1173
+ if (!res.ok) {
1174
+ throw new DecaneError(
1175
+ 5022 /* PROVIDER_ERROR */,
1176
+ `Failed to build Tron transaction: ${res.statusText}`
1177
+ );
1178
+ }
1179
+ const json = await res.json();
1180
+ unsignedTx = json.transaction ?? json;
1181
+ }
1182
+ let signedTx;
1183
+ if (tronWeb?.trx?.sign) {
1184
+ signedTx = await tronWeb.trx.sign(unsignedTx);
1185
+ } else {
1186
+ signedTx = await provider.request({
1187
+ method: "tron_signTransaction",
1188
+ params: unsignedTx
1189
+ });
1190
+ }
1191
+ let txid;
1192
+ if (tronWeb?.trx?.sendRawTransaction) {
1193
+ const result = await tronWeb.trx.sendRawTransaction(signedTx);
1194
+ txid = result?.txid;
1195
+ } else {
1196
+ const result = await provider.request({
1197
+ method: "tron_sendRawTransaction",
1198
+ params: signedTx
1199
+ });
1200
+ txid = result?.txid;
1201
+ }
1202
+ if (!txid) {
1203
+ throw new DecaneError(5022 /* PROVIDER_ERROR */, "Tron transaction broadcast failed");
1204
+ }
1205
+ return txid;
1206
+ }
1207
+ default:
1208
+ throw new DecaneError(
1209
+ 5030 /* METHOD_NOT_SUPPORTED */,
1210
+ `Tron adapter: unsupported method ${method}`
1211
+ );
1212
+ }
1213
+ }
1214
+
1215
+ // src/bitcoin/adapter.ts
1216
+ async function getBitcoinAddress(provider) {
1217
+ const result = await provider.requestAccounts();
1218
+ if (!result || result.length === 0) {
1219
+ throw new DecaneError(4001 /* USER_REJECTED */, "No Bitcoin accounts returned");
1220
+ }
1221
+ const first = result[0];
1222
+ return typeof first === "string" ? first : first.address;
1223
+ }
1224
+ async function bitcoinRequest(provider, method, payload, account) {
1225
+ switch (method) {
1226
+ case "wallet_signMessage":
1227
+ return provider.signMessage(payload);
1228
+ case "wallet_sendTransaction": {
1229
+ const { to, value } = payload;
1230
+ if (provider.sendBitcoin) {
1231
+ return provider.sendBitcoin(to, value);
1232
+ }
1233
+ if (provider.send) {
1234
+ return provider.send({ to, value, from: account });
1235
+ }
1236
+ throw new DecaneError(
1237
+ 5030 /* METHOD_NOT_SUPPORTED */,
1238
+ "Bitcoin wallet does not support sendBitcoin"
1239
+ );
1240
+ }
1241
+ default:
1242
+ throw new DecaneError(
1243
+ 5030 /* METHOD_NOT_SUPPORTED */,
1244
+ `Bitcoin adapter: unsupported method ${method}`
1245
+ );
1246
+ }
1058
1247
  }
1059
1248
 
1060
1249
  // src/core/router.ts
@@ -1105,20 +1294,23 @@ function createRouter(opts) {
1105
1294
  const chainType = getChainType(chainId);
1106
1295
  if (chainType === "evm" && !connectedWallet.evmProvider) continue;
1107
1296
  if (chainType === "solana" && !connectedWallet.solanaProvider) continue;
1297
+ if (chainType === "tron" && !connectedWallet.tronProvider) continue;
1298
+ if (chainType === "bitcoin" && !connectedWallet.bitcoinProvider) continue;
1108
1299
  let address;
1109
1300
  if (chainType === "evm") {
1110
1301
  address = await getEvmAddress(connectedWallet.evmProvider);
1111
1302
  if (chain.isCustom) {
1112
- await addEthereumChain(
1113
- connectedWallet.evmProvider,
1114
- chain
1115
- ).catch(() => {
1303
+ await addEthereumChain(connectedWallet.evmProvider, chain).catch(() => {
1116
1304
  });
1117
1305
  }
1118
- } else {
1306
+ } else if (chainType === "solana") {
1119
1307
  address = await getSolanaAddress(connectedWallet.solanaProvider);
1308
+ } else if (chainType === "tron") {
1309
+ address = await getTronAddress(connectedWallet.tronProvider);
1310
+ } else {
1311
+ address = await getBitcoinAddress(connectedWallet.bitcoinProvider);
1120
1312
  }
1121
- const methodSet = chainType === "evm" ? EVM_METHODS : SOLANA_METHODS;
1313
+ const methodSet = chainType === "evm" ? EVM_METHODS : chainType === "solana" ? SOLANA_METHODS : chainType === "tron" ? TRON_METHODS : BITCOIN_METHODS;
1122
1314
  const grantedMethods = (scope?.methods ?? []).filter(
1123
1315
  (m) => methodSet.has(m)
1124
1316
  );
@@ -1197,19 +1389,13 @@ function createRouter(opts) {
1197
1389
  );
1198
1390
  }
1199
1391
  if (chainType === "evm") {
1200
- return eip1193Request(
1201
- wallet.evmProvider,
1202
- method,
1203
- payload,
1204
- account
1205
- );
1392
+ return eip1193Request(wallet.evmProvider, method, payload, account);
1393
+ } else if (chainType === "solana") {
1394
+ return walletStandardRequest(wallet.solanaProvider, method, payload, account);
1395
+ } else if (chainType === "tron") {
1396
+ return tronRequest(wallet.tronProvider, method, payload, account);
1206
1397
  } else {
1207
- return walletStandardRequest(
1208
- wallet.solanaProvider,
1209
- method,
1210
- payload,
1211
- account
1212
- );
1398
+ return bitcoinRequest(wallet.bitcoinProvider, method, payload, account);
1213
1399
  }
1214
1400
  }
1215
1401
  return { request };
@@ -1264,6 +1450,12 @@ function createProvider(opts) {
1264
1450
  get multiChainWallets() {
1265
1451
  return registry.get.multiChain();
1266
1452
  },
1453
+ get tronWallets() {
1454
+ return registry.get.tron();
1455
+ },
1456
+ get bitcoinWallets() {
1457
+ return registry.get.bitcoin();
1458
+ },
1267
1459
  // ── Connection ──────────────────────────────────────────────────────────
1268
1460
  async connect(wallet, preferredChain) {
1269
1461
  if (session.get()) {
@@ -1272,13 +1464,19 @@ function createProvider(opts) {
1272
1464
  }
1273
1465
  _connectedWallet = wallet;
1274
1466
  const support = wallet.chainSupport;
1275
- if (support.includes("evm") && preferredChain !== "solana") {
1467
+ const hasEvm = support.includes("evm") && preferredChain !== "solana" && preferredChain !== "tron" && preferredChain !== "bitcoin";
1468
+ const hasSol = support.includes("solana") && preferredChain !== "evm" && preferredChain !== "tron" && preferredChain !== "bitcoin";
1469
+ const hasTron = support.includes("tron") && preferredChain !== "evm" && preferredChain !== "solana" && preferredChain !== "bitcoin";
1470
+ const hasBitcoin = support.includes("bitcoin") && preferredChain !== "evm" && preferredChain !== "solana" && preferredChain !== "tron";
1471
+ if (hasEvm) {
1276
1472
  _activeChain = config.defaultChain?.startsWith("eip155:") ? config.defaultChain : "eip155:1";
1277
- } else {
1473
+ } else if (hasSol) {
1278
1474
  _activeChain = "solana:mainnet";
1475
+ } else if (hasTron) {
1476
+ _activeChain = "tron:mainnet";
1477
+ } else if (hasBitcoin) {
1478
+ _activeChain = "bip122:mainnet";
1279
1479
  }
1280
- const hasEvm = support.includes("evm") && preferredChain !== "solana";
1281
- const hasSol = support.includes("solana") && preferredChain !== "evm";
1282
1480
  const required = {};
1283
1481
  const optional = {};
1284
1482
  if (hasEvm) {
@@ -1302,6 +1500,34 @@ function createProvider(opts) {
1302
1500
  _activeChain = solChain;
1303
1501
  }
1304
1502
  }
1503
+ if (hasTron) {
1504
+ const tronChain = "tron:mainnet";
1505
+ const scope = {
1506
+ methods: ["wallet_sendTransaction", "wallet_signMessage"],
1507
+ accounts: [],
1508
+ events: []
1509
+ };
1510
+ if (hasEvm || hasSol) {
1511
+ optional[tronChain] = scope;
1512
+ } else {
1513
+ required[tronChain] = scope;
1514
+ _activeChain = tronChain;
1515
+ }
1516
+ }
1517
+ if (hasBitcoin) {
1518
+ const btcChain = "bip122:mainnet";
1519
+ const scope = {
1520
+ methods: ["wallet_sendTransaction", "wallet_signMessage"],
1521
+ accounts: [],
1522
+ events: []
1523
+ };
1524
+ if (hasEvm || hasSol || hasTron) {
1525
+ optional[btcChain] = scope;
1526
+ } else {
1527
+ required[btcChain] = scope;
1528
+ _activeChain = btcChain;
1529
+ }
1530
+ }
1305
1531
  await router.request({
1306
1532
  method: "wallet_requestPermissions",
1307
1533
  params: [{ requiredScopes: required, optionalScopes: optional }, wallet.id]
@@ -1369,6 +1595,58 @@ function createProvider(opts) {
1369
1595
  return provider;
1370
1596
  }
1371
1597
 
1598
+ // src/evm/bitcoinSnap.ts
1599
+ var BITCOIN_SNAP_IDS = [
1600
+ "npm:@metamask/bitcoin-snap",
1601
+ "npm:@consensys/btc-snap"
1602
+ ];
1603
+ async function detectBitcoinSnap(provider) {
1604
+ try {
1605
+ const snaps = await provider.request({ method: "wallet_getSnaps" });
1606
+ if (!snaps) return null;
1607
+ for (const id of BITCOIN_SNAP_IDS) {
1608
+ const snap = snaps[id];
1609
+ if (snap && snap.blocked !== true) return id;
1610
+ }
1611
+ return null;
1612
+ } catch {
1613
+ return null;
1614
+ }
1615
+ }
1616
+ function makeBitcoinSnapProvider(snapId, evmProvider) {
1617
+ function invoke(method, params) {
1618
+ return evmProvider.request({
1619
+ method: "wallet_invokeSnap",
1620
+ params: [{ snapId, request: { method, params } }]
1621
+ });
1622
+ }
1623
+ return {
1624
+ async requestAccounts() {
1625
+ const result = await invoke("btc_getAddress").catch(() => invoke("btc_getPublicKey"));
1626
+ if (typeof result === "string") return [result];
1627
+ if (Array.isArray(result)) return result;
1628
+ return [];
1629
+ },
1630
+ async signMessage(message) {
1631
+ return invoke("btc_signMessage", [message]);
1632
+ },
1633
+ async sendBitcoin(to, value) {
1634
+ return invoke("btc_sendBitcoin", [{ to, satoshis: value }]);
1635
+ }
1636
+ };
1637
+ }
1638
+ async function tryRegisterBitcoinSnap(registry, name, icon, rdns, evmProvider) {
1639
+ const snapId = await detectBitcoinSnap(evmProvider);
1640
+ if (!snapId) return;
1641
+ registry.registerOrMerge({
1642
+ chain: "bitcoin",
1643
+ name,
1644
+ icon,
1645
+ rdns,
1646
+ provider: makeBitcoinSnapProvider(snapId, evmProvider)
1647
+ });
1648
+ }
1649
+
1372
1650
  // src/evm/discovery.ts
1373
1651
  function startEvmDiscovery(registry) {
1374
1652
  if (typeof window === "undefined") return;
@@ -1388,6 +1666,14 @@ function startEvmDiscovery(registry) {
1388
1666
  uuid: info.uuid,
1389
1667
  provider
1390
1668
  });
1669
+ tryRegisterBitcoinSnap(
1670
+ registry,
1671
+ info.name,
1672
+ info.icon,
1673
+ info.rdns,
1674
+ provider
1675
+ ).catch(() => {
1676
+ });
1391
1677
  }
1392
1678
  );
1393
1679
  window.dispatchEvent(new CustomEvent("eip6963:requestProvider"));
@@ -1428,6 +1714,53 @@ function startSolanaDiscovery(registry) {
1428
1714
  );
1429
1715
  }
1430
1716
 
1717
+ // src/tron/discovery.ts
1718
+ var TRONLINK_RDNS = "link.tron.tronlink";
1719
+ var TRONLINK_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTYiIGZpbGw9IiNGRjAwMTMiLz48cG9seWdvbiBwb2ludHM9IjEyLDkgMjMsMTYgMTIsMjMiIGZpbGw9IndoaXRlIi8+PC9zdmc+";
1720
+ function startTronDiscovery(registry) {
1721
+ let registered = false;
1722
+ function tryRegister() {
1723
+ if (registered || !window.tronLink) return;
1724
+ registered = true;
1725
+ registry.registerOrMerge({
1726
+ chain: "tron",
1727
+ name: "TronLink",
1728
+ icon: TRONLINK_ICON,
1729
+ rdns: TRONLINK_RDNS,
1730
+ provider: window.tronLink
1731
+ });
1732
+ }
1733
+ tryRegister();
1734
+ window.addEventListener("tronLink#initialized", tryRegister, { once: true });
1735
+ const start = Date.now();
1736
+ const poll = setInterval(() => {
1737
+ tryRegister();
1738
+ if (registered || Date.now() - start > 3e3) clearInterval(poll);
1739
+ }, 200);
1740
+ }
1741
+
1742
+ // src/bitcoin/discovery.ts
1743
+ var BTC_ICON = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiI+PGNpcmNsZSBjeD0iMTYiIGN5PSIxNiIgcj0iMTYiIGZpbGw9IiNGNzkzMUEiLz48dGV4dCB4PSIxNi41IiB5PSIyMiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1zaXplPSIxNiIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0id2hpdGUiIGZvbnQtZmFtaWx5PSJBcmlhbCxzeXN0ZW0tdWkiPuKmojwvdGV4dD48L3N2Zz4=";
1744
+ function startBitcoinDiscovery(registry) {
1745
+ setTimeout(() => {
1746
+ const candidates = [
1747
+ { name: "Unisat", rdns: "io.unisat.wallet", provider: window.unisat },
1748
+ // OKX shares rdns with its EVM provider — merges into the EVM entry automatically
1749
+ { name: "OKX Wallet", rdns: "com.okex.okxwallet", provider: window.okxwallet?.bitcoin }
1750
+ ];
1751
+ for (const { name, rdns, provider } of candidates) {
1752
+ if (!provider) continue;
1753
+ registry.registerOrMerge({
1754
+ chain: "bitcoin",
1755
+ name,
1756
+ icon: BTC_ICON,
1757
+ rdns,
1758
+ provider
1759
+ });
1760
+ }
1761
+ }, 150);
1762
+ }
1763
+
1431
1764
  // src/factory.ts
1432
1765
  function createDecane(config = {}) {
1433
1766
  const emitter = createEmitter();
@@ -1441,6 +1774,8 @@ function createDecane(config = {}) {
1441
1774
  const router = createRouter({ registry, session, chainStore });
1442
1775
  startEvmDiscovery(registry);
1443
1776
  startSolanaDiscovery(registry);
1777
+ startTronDiscovery(registry);
1778
+ startBitcoinDiscovery(registry);
1444
1779
  return createProvider({ config, registry, session, chainStore, router, emitter });
1445
1780
  }
1446
1781
 
@@ -1608,6 +1943,67 @@ function WalletIcon({ src, name, size = 36 }) {
1608
1943
  initial
1609
1944
  );
1610
1945
  }
1946
+ function EthCircle({ size }) {
1947
+ const s = size;
1948
+ return /* @__PURE__ */ import_react.default.createElement("svg", { width: s, height: s, viewBox: "0 0 16 16", fill: "none" }, /* @__PURE__ */ import_react.default.createElement("circle", { cx: "8", cy: "8", r: "8", fill: "#627EEA" }), /* @__PURE__ */ import_react.default.createElement("polygon", { points: "8,2.5 11.5,8 8,9.8 4.5,8", fill: "white", fillOpacity: "0.9" }), /* @__PURE__ */ import_react.default.createElement("polygon", { points: "8,9.8 11.5,8 8,13.5", fill: "white", fillOpacity: "0.6" }), /* @__PURE__ */ import_react.default.createElement("polygon", { points: "8,9.8 4.5,8 8,13.5", fill: "white", fillOpacity: "0.8" }), /* @__PURE__ */ import_react.default.createElement("polygon", { points: "8,2.5 8,9.8 4.5,8", fill: "white", fillOpacity: "0.6" }));
1949
+ }
1950
+ function SolCircle({ size }) {
1951
+ const s = size;
1952
+ return /* @__PURE__ */ import_react.default.createElement("svg", { width: s, height: s, viewBox: "0 0 16 16", fill: "none" }, /* @__PURE__ */ import_react.default.createElement("circle", { cx: "8", cy: "8", r: "8", fill: "#9945FF" }), /* @__PURE__ */ import_react.default.createElement("rect", { x: "4", y: "4.5", width: "7", height: "1.5", rx: "0.75", fill: "white", fillOpacity: "0.95" }), /* @__PURE__ */ import_react.default.createElement("rect", { x: "4", y: "7.25", width: "7", height: "1.5", rx: "0.75", fill: "white", fillOpacity: "0.95" }), /* @__PURE__ */ import_react.default.createElement("rect", { x: "4", y: "10", width: "5.5", height: "1.5", rx: "0.75", fill: "white", fillOpacity: "0.95" }));
1953
+ }
1954
+ function TronCircle({ size }) {
1955
+ const s = size;
1956
+ return /* @__PURE__ */ import_react.default.createElement("svg", { width: s, height: s, viewBox: "0 0 16 16", fill: "none" }, /* @__PURE__ */ import_react.default.createElement("circle", { cx: "8", cy: "8", r: "8", fill: "#FF0013" }), /* @__PURE__ */ import_react.default.createElement("polygon", { points: "6,4.5 12,8 6,11.5", fill: "white", fillOpacity: "0.95" }));
1957
+ }
1958
+ function BitcoinCircle({ size }) {
1959
+ const s = size;
1960
+ return /* @__PURE__ */ import_react.default.createElement("svg", { width: s, height: s, viewBox: "0 0 16 16", fill: "none" }, /* @__PURE__ */ import_react.default.createElement("circle", { cx: "8", cy: "8", r: "8", fill: "#F7931A" }), /* @__PURE__ */ import_react.default.createElement(
1961
+ "text",
1962
+ {
1963
+ x: "8.3",
1964
+ y: "11.5",
1965
+ textAnchor: "middle",
1966
+ fontSize: "8",
1967
+ fontWeight: "700",
1968
+ fill: "white",
1969
+ fontFamily: "Arial, system-ui"
1970
+ },
1971
+ "\u20BF"
1972
+ ));
1973
+ }
1974
+ function ChainCircleIcon({ chain, size }) {
1975
+ switch (chain) {
1976
+ case "evm":
1977
+ return /* @__PURE__ */ import_react.default.createElement(EthCircle, { size });
1978
+ case "solana":
1979
+ return /* @__PURE__ */ import_react.default.createElement(SolCircle, { size });
1980
+ case "tron":
1981
+ return /* @__PURE__ */ import_react.default.createElement(TronCircle, { size });
1982
+ case "bitcoin":
1983
+ return /* @__PURE__ */ import_react.default.createElement(BitcoinCircle, { size });
1984
+ }
1985
+ }
1986
+ var CHAIN_ORDER = ["evm", "solana", "tron", "bitcoin"];
1987
+ function ChainIconStack({ support, size = 16 }) {
1988
+ const ordered = CHAIN_ORDER.filter((c) => support.includes(c));
1989
+ if (ordered.length === 0) return null;
1990
+ const overlap = Math.round(size * 0.35);
1991
+ return /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", alignItems: "center", flexShrink: 0 } }, ordered.map((chain, i) => /* @__PURE__ */ import_react.default.createElement(
1992
+ "div",
1993
+ {
1994
+ key: chain,
1995
+ style: {
1996
+ marginLeft: i === 0 ? 0 : `-${overlap}px`,
1997
+ zIndex: ordered.length - i,
1998
+ position: "relative",
1999
+ borderRadius: "50%",
2000
+ lineHeight: 0,
2001
+ boxShadow: i > 0 ? `0 0 0 1.5px var(--dc-bg, #fff)` : void 0
2002
+ }
2003
+ },
2004
+ /* @__PURE__ */ import_react.default.createElement(ChainCircleIcon, { chain, size })
2005
+ )));
2006
+ }
1611
2007
 
1612
2008
  // src/ui/WalletSelector.tsx
1613
2009
  var FONT_URL = "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap";
@@ -1634,21 +2030,6 @@ function injectFont() {
1634
2030
  link.href = FONT_URL;
1635
2031
  document.head.appendChild(link);
1636
2032
  }
1637
- var badgeBase = {
1638
- display: "inline-flex",
1639
- alignItems: "center",
1640
- padding: "3px 8px",
1641
- borderRadius: 20,
1642
- fontSize: 10,
1643
- fontWeight: 700,
1644
- letterSpacing: "0.04em",
1645
- textTransform: "uppercase"
1646
- };
1647
- function ChainBadges({ chainSupport }) {
1648
- const hasEvm = chainSupport.includes("evm");
1649
- const hasSol = chainSupport.includes("solana");
1650
- return /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", gap: 4 } }, hasEvm && /* @__PURE__ */ import_react2.default.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-evm)", color: "var(--dc-badge-evm-text)" } }, "EVM"), hasSol && /* @__PURE__ */ import_react2.default.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-sol)", color: "var(--dc-badge-sol-text)", border: "1px solid var(--dc-border)" } }, "SVM"));
1651
- }
1652
2033
  function WalletRow({ wallet, onClick }) {
1653
2034
  const [hovered, setHovered] = (0, import_react2.useState)(false);
1654
2035
  const [focused, setFocused] = (0, import_react2.useState)(false);
@@ -1678,7 +2059,7 @@ function WalletRow({ wallet, onClick }) {
1678
2059
  },
1679
2060
  /* @__PURE__ */ import_react2.default.createElement(WalletIcon, { src: wallet.icon || void 0, name: wallet.name, size: 38 }),
1680
2061
  /* @__PURE__ */ import_react2.default.createElement("span", { style: { flex: 1, fontSize: 14, fontWeight: 600, color: "var(--dc-text)", letterSpacing: "-0.01em" } }, wallet.name),
1681
- /* @__PURE__ */ import_react2.default.createElement(ChainBadges, { chainSupport: wallet.chainSupport }),
2062
+ /* @__PURE__ */ import_react2.default.createElement(ChainIconStack, { support: wallet.chainSupport, size: 16 }),
1682
2063
  /* @__PURE__ */ import_react2.default.createElement(
1683
2064
  "svg",
1684
2065
  {
@@ -1805,17 +2186,19 @@ function WalletSelector({
1805
2186
  const cs = w.chainSupport;
1806
2187
  if (networks === "evm") return cs.includes("evm");
1807
2188
  if (networks === "solana") return cs.includes("solana");
2189
+ if (networks === "tron") return cs.includes("tron");
2190
+ if (networks === "bitcoin") return cs.includes("bitcoin");
1808
2191
  return true;
1809
2192
  });
1810
- const baseWallets = filter === "evm" ? networkWallets.filter((w) => w.chainSupport.includes("evm")) : filter === "solana" ? networkWallets.filter((w) => w.chainSupport.includes("solana")) : networkWallets;
2193
+ 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;
1811
2194
  const filtered = q ? baseWallets.filter((w) => w.name.toLowerCase().includes(q)) : baseWallets;
1812
2195
  function handleWalletClick(wallet) {
1813
2196
  const cs = wallet.chainSupport;
1814
- const isMulti = cs.includes("evm") && cs.includes("solana");
2197
+ const isMulti = cs.length >= 2;
1815
2198
  if (isMulti && networks === "all") {
1816
2199
  setPendingWallet(wallet);
1817
2200
  } else {
1818
- const preference = networks === "evm" ? "evm" : networks === "solana" ? "solana" : void 0;
2201
+ const preference = networks === "evm" ? "evm" : networks === "solana" ? "solana" : networks === "tron" ? "tron" : networks === "bitcoin" ? "bitcoin" : void 0;
1819
2202
  onConnect(wallet, preference);
1820
2203
  onClose();
1821
2204
  }
@@ -1950,28 +2333,44 @@ function WalletSelector({
1950
2333
  },
1951
2334
  /* @__PURE__ */ import_react2.default.createElement(WalletIcon, { src: pendingWallet.icon || void 0, name: pendingWallet.name, size: 36 }),
1952
2335
  /* @__PURE__ */ import_react2.default.createElement("div", null, /* @__PURE__ */ import_react2.default.createElement("div", { style: { fontSize: 14, fontWeight: 700, color: "var(--dc-text)" } }, pendingWallet.name), /* @__PURE__ */ import_react2.default.createElement("div", { style: { fontSize: 11, color: "var(--dc-text-muted)", marginTop: 2 } }, "Multi-chain wallet detected"))
1953
- ), /* @__PURE__ */ import_react2.default.createElement("div", { style: { padding: "0 12px 20px", display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ import_react2.default.createElement(
2336
+ ), /* @__PURE__ */ import_react2.default.createElement("div", { style: { padding: "0 12px 20px", display: "flex", flexDirection: "column", gap: 8 } }, pendingWallet.chainSupport.includes("evm") && /* @__PURE__ */ import_react2.default.createElement(
1954
2337
  ChainOption,
1955
2338
  {
1956
2339
  label: "EVM networks",
1957
2340
  description: "Connect your Ethereum-compatible address only",
1958
- badges: /* @__PURE__ */ import_react2.default.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-evm)", color: "var(--dc-badge-evm-text)" } }, "EVM"),
2341
+ badges: /* @__PURE__ */ import_react2.default.createElement(ChainIconStack, { support: ["evm"], size: 18 }),
1959
2342
  onClick: () => handleChainChoice("evm")
1960
2343
  }
1961
- ), /* @__PURE__ */ import_react2.default.createElement(
2344
+ ), pendingWallet.chainSupport.includes("solana") && /* @__PURE__ */ import_react2.default.createElement(
1962
2345
  ChainOption,
1963
2346
  {
1964
2347
  label: "Solana network",
1965
2348
  description: "Connect your Solana address only",
1966
- badges: /* @__PURE__ */ import_react2.default.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-sol)", color: "var(--dc-badge-sol-text)", border: "1px solid var(--dc-border)" } }, "SVM"),
2349
+ badges: /* @__PURE__ */ import_react2.default.createElement(ChainIconStack, { support: ["solana"], size: 18 }),
1967
2350
  onClick: () => handleChainChoice("solana")
1968
2351
  }
2352
+ ), pendingWallet.chainSupport.includes("tron") && /* @__PURE__ */ import_react2.default.createElement(
2353
+ ChainOption,
2354
+ {
2355
+ label: "Tron network",
2356
+ description: "Connect your Tron address only",
2357
+ badges: /* @__PURE__ */ import_react2.default.createElement(ChainIconStack, { support: ["tron"], size: 18 }),
2358
+ onClick: () => handleChainChoice("tron")
2359
+ }
2360
+ ), pendingWallet.chainSupport.includes("bitcoin") && /* @__PURE__ */ import_react2.default.createElement(
2361
+ ChainOption,
2362
+ {
2363
+ label: "Bitcoin network",
2364
+ description: "Connect your Bitcoin address only",
2365
+ badges: /* @__PURE__ */ import_react2.default.createElement(ChainIconStack, { support: ["bitcoin"], size: 18 }),
2366
+ onClick: () => handleChainChoice("bitcoin")
2367
+ }
1969
2368
  ), /* @__PURE__ */ import_react2.default.createElement(
1970
2369
  ChainOption,
1971
2370
  {
1972
- label: "Both networks",
1973
- description: "Connect EVM and Solana addresses together",
1974
- badges: /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", gap: 4 } }, /* @__PURE__ */ import_react2.default.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-evm)", color: "var(--dc-badge-evm-text)" } }, "EVM"), /* @__PURE__ */ import_react2.default.createElement("span", { style: { ...badgeBase, background: "var(--dc-badge-sol)", color: "var(--dc-badge-sol-text)", border: "1px solid var(--dc-border)" } }, "SVM")),
2371
+ label: "All networks",
2372
+ description: "Connect all supported addresses together",
2373
+ badges: /* @__PURE__ */ import_react2.default.createElement(ChainIconStack, { support: pendingWallet.chainSupport, size: 18 }),
1975
2374
  onClick: () => handleChainChoice(void 0)
1976
2375
  }
1977
2376
  )))
@@ -2046,7 +2445,7 @@ function WalletSelector({
2046
2445
  e.currentTarget.style.borderColor = "var(--dc-border)";
2047
2446
  }
2048
2447
  }
2049
- )), networks === "all" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 10 } }, /* @__PURE__ */ import_react2.default.createElement(FilterTab, { label: "All", active: filter === "all", onClick: () => setFilter("all") }), /* @__PURE__ */ import_react2.default.createElement(FilterTab, { label: "EVM", active: filter === "evm", onClick: () => setFilter("evm") }), /* @__PURE__ */ import_react2.default.createElement(FilterTab, { label: "Solana", active: filter === "solana", onClick: () => setFilter("solana") }))), /* @__PURE__ */ import_react2.default.createElement("div", { style: { overflowY: "auto", flex: 1, padding: "0 12px 20px" } }, filtered.length > 0 ? /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((w) => /* @__PURE__ */ import_react2.default.createElement(WalletRow, { key: w.id, wallet: w, onClick: () => handleWalletClick(w) }))) : /* @__PURE__ */ import_react2.default.createElement("div", { style: { textAlign: "center", padding: "32px 16px", color: "var(--dc-text-muted)", fontSize: 13 } }, q ? `No wallets matching "${q}"` : "No wallets detected")), /* @__PURE__ */ import_react2.default.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"))
2448
+ )), networks === "all" && /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", gap: 4, marginBottom: 10 } }, /* @__PURE__ */ import_react2.default.createElement(FilterTab, { label: "All", active: filter === "all", onClick: () => setFilter("all") }), /* @__PURE__ */ import_react2.default.createElement(FilterTab, { label: "EVM", active: filter === "evm", onClick: () => setFilter("evm") }), /* @__PURE__ */ import_react2.default.createElement(FilterTab, { label: "Solana", active: filter === "solana", onClick: () => setFilter("solana") }), provider.tronWallets && provider.tronWallets.length > 0 && /* @__PURE__ */ import_react2.default.createElement(FilterTab, { label: "Tron", active: filter === "tron", onClick: () => setFilter("tron") }), provider.bitcoinWallets && provider.bitcoinWallets.length > 0 && /* @__PURE__ */ import_react2.default.createElement(FilterTab, { label: "Bitcoin", active: filter === "bitcoin", onClick: () => setFilter("bitcoin") }))), /* @__PURE__ */ import_react2.default.createElement("div", { style: { overflowY: "auto", flex: 1, padding: "0 12px 20px" } }, filtered.length > 0 ? /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((w) => /* @__PURE__ */ import_react2.default.createElement(WalletRow, { key: w.id, wallet: w, onClick: () => handleWalletClick(w) }))) : /* @__PURE__ */ import_react2.default.createElement("div", { style: { textAlign: "center", padding: "32px 16px", color: "var(--dc-text-muted)", fontSize: 13 } }, q ? `No wallets matching "${q}"` : "No wallets detected")), /* @__PURE__ */ import_react2.default.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"))
2050
2449
  )
2051
2450
  )
2052
2451
  );
@@ -2102,6 +2501,11 @@ function useDecaneContext() {
2102
2501
 
2103
2502
  // src/ui/hooks.ts
2104
2503
  var import_react4 = require("react");
2504
+ function toDecaneError(err) {
2505
+ if (err instanceof DecaneError) return err;
2506
+ const msg = err instanceof Error ? err.message : err?.message != null ? String(err.message) : String(err);
2507
+ return new DecaneError(5022 /* PROVIDER_ERROR */, msg);
2508
+ }
2105
2509
  function useDecane() {
2106
2510
  const { provider, theme, setTheme } = useDecaneContext();
2107
2511
  const [, forceUpdate] = (0, import_react4.useState)(0);
@@ -2179,7 +2583,7 @@ function useChainSwitch() {
2179
2583
  });
2180
2584
  forceUpdate((n) => n + 1);
2181
2585
  } catch (err) {
2182
- const e = err instanceof DecaneError ? err : new DecaneError(5022 /* PROVIDER_ERROR */, String(err));
2586
+ const e = toDecaneError(err);
2183
2587
  setError(e);
2184
2588
  throw err;
2185
2589
  } finally {
@@ -2230,7 +2634,7 @@ function useSignMessage() {
2230
2634
  setSignature(sig);
2231
2635
  return sig;
2232
2636
  } catch (err) {
2233
- const e = err instanceof DecaneError ? err : new DecaneError(5022 /* PROVIDER_ERROR */, String(err));
2637
+ const e = toDecaneError(err);
2234
2638
  setError(e);
2235
2639
  throw err;
2236
2640
  } finally {
@@ -2268,7 +2672,7 @@ function useSendTransaction() {
2268
2672
  setTxHash(hash);
2269
2673
  return hash;
2270
2674
  } catch (err) {
2271
- const e = err instanceof DecaneError ? err : new DecaneError(5022 /* PROVIDER_ERROR */, String(err));
2675
+ const e = toDecaneError(err);
2272
2676
  setError(e);
2273
2677
  throw err;
2274
2678
  } finally {
@@ -2305,7 +2709,7 @@ function useSignTypedData() {
2305
2709
  setSignature(sig);
2306
2710
  return sig;
2307
2711
  } catch (err) {
2308
- const e = err instanceof DecaneError ? err : new DecaneError(5022 /* PROVIDER_ERROR */, String(err));
2712
+ const e = toDecaneError(err);
2309
2713
  setError(e);
2310
2714
  throw err;
2311
2715
  } finally {