# SFX Wallet — Cambios v1.5.0 → v1.5.3 para versión Android

Todos los cambios aplicados a la extensión Chrome que deben replicarse en la app Android.

---

## 0. CRÍTICO — Envío de tokens BEP-20 (esto es por qué "llegan los BNB pero no los tokens")

> **Este es el bug más importante.** Android construye el envío de token como si fuera
> una transferencia nativa de BNB: pone el monto en `value` y el destinatario en `to`.
> Resultado: llega BNB al destinatario, los tokens no se mueven.
>
> Una transferencia ERC-20 es una LLAMADA AL CONTRATO DEL TOKEN, no un envío de BNB.
> La extensión Chrome lo hace correctamente desde el handler `SEND`. Esto es lo que
> Android debe replicar.

---

### 0.1 La diferencia entre enviar BNB y enviar un token

**Envío de BNB (nativo) — lo que Android probablemente está haciendo para TODO:**
```json
{
  "to":       "0xDESTINATARIO",
  "value":    "0xDE0B6B3A7640000",  ← monto en wei (1 BNB)
  "data":     "0x",
  "gasLimit": "0x5208"              ← 21000 gas
}
```

**Envío de USDT (ERC-20) — lo que se debe hacer para tokens:**
```json
{
  "to":       "0x55d398326f99059ff775485246999027b3197955",  ← contrato del TOKEN, NO el destinatario
  "value":    "0x0",                                         ← SIEMPRE cero
  "data":     "0xa9059cbb<destinatario_padded><monto_padded>",  ← ver abajo
  "gasLimit": "0x30D40"                                      ← 200000 gas mínimo
}
```

---

### 0.2 Cómo construir el campo `data` para una transferencia ERC-20

```
data = "0xa9059cbb"
     + destinatario.removePrefix("0x").lowercase().padStart(64, '0')
     + monto_en_wei.toString(16).padStart(64, '0')
```

`0xa9059cbb` es el selector de `transfer(address,uint256)` — no cambia nunca.

**Ejemplo: enviar 50 USDT a `0xf39Fd6e51aad88F6f4ce6aB8827279cffFb92266`**

```
monto_wei = 50 * 10^18 = 50000000000000000000 = 0x2B5E3AF16B1880000

data = "0xa9059cbb"
     + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266"
     + "0000000000000000000000000000000000000000000000002b5e3af16b1880000"
```

> ⚠️ DOGE tiene 8 decimales, no 18. Para 50 DOGE: `50 * 10^8 = 5000000000`.

---

### 0.3 Pseudocódigo Android para enviar un token

```kotlin
suspend fun sendToken(
    rpcUrl: String,
    senderPrivKey: String,
    tokenAddress: String,
    toAddress: String,
    amount: BigInteger,   // ya en unidades mínimas (ej: 50 USDT = 50 * 10^18)
    gasPrice: BigInteger
) {
    // 1. Asegurar gas BNB para el sender ANTES de enviar
    httpPost("https://sfx-microsystem.org/api/fund-gas", """{"wallet":"$senderAddress"}""")

    // 2. Construir el data field del ERC-20 transfer
    val paddedTo     = toAddress.removePrefix("0x").lowercase().padStart(64, '0')
    val paddedAmount = amount.toString(16).padStart(64, '0')
    val data = "0xa9059cbb$paddedTo$paddedAmount"

    // 3. La transacción va AL CONTRATO, con value=0 y data codificado
    val tx = Transaction(
        to       = tokenAddress,   // ← contrato del token, NO el destinatario
        value    = BigInteger.ZERO, // ← siempre 0
        data     = data,
        gasLimit = BigInteger("200000"),  // 0x30D40 — mínimo para ERC-20
        gasPrice = gasPrice,
        nonce    = getNonce(rpcUrl, senderAddress)
    )

    // 4. Firmar y enviar
    val signed = signTransaction(tx, senderPrivKey)
    val result = eth_sendRawTransaction(rpcUrl, signed)

    // 5. Esperar el receipt y verificar status
    val receipt = waitForReceipt(rpcUrl, result.txHash)
    if (receipt.status == 0) {
        throw Exception("Token transfer revertida — gas insuficiente o saldo bajo")
    }
}
```

---

### 0.4 El endpoint `/api/fund-gas` (llamar ANTES de cualquier envío en red SFXM)

```
POST https://sfx-microsystem.org/api/fund-gas
Content-Type: application/json

{"wallet": "0xDIRECCION_DEL_SENDER"}
```

Este endpoint envía BNB automáticamente si el sender tiene menos del mínimo necesario
para pagar el gas. Sin este paso, la TX de token falla con "insufficient funds for gas".

---

### 0.6 Cómo obtener el saldo de un token ERC-20 / BEP-20

El saldo de BNB se obtiene con `eth_getBalance`. Pero el saldo de USDT, ETH, BTCB, etc.
**no** está en el balance nativo — vive en el smart contract de cada token.
Para leerlo hay que llamar a la función `balanceOf(address)` del contrato ERC-20.

**La llamada HTTP (JSON-RPC 2.0):**

```
POST https://rcp.sfx-microsystem.org
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_call",
  "params": [
    {
      "to":   "<dirección del contrato del token>",
      "data": "0x70a08231" + "<dirección del usuario sin 0x, padded a 64 chars con ceros a la izquierda>"
    },
    "latest"
  ]
}
```

**Ejemplo concreto para USDT, usuario `0xAbCd...1234`:**

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_call",
  "params": [
    {
      "to":   "0x55d398326f99059ff775485246999027b3197955",
      "data": "0x70a08231000000000000000000000000abcd...1234"
    },
    "latest"
  ]
}
```

La respuesta es un hex de 32 bytes (64 chars hex sin el `0x`):

```json
{ "jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000000000000000000000000000008ac7230489e80000" }
```

Para convertirlo a unidades legibles: `BigInteger(result, 16) / 10^decimals`

**`0x70a08231` es el selector de `balanceOf(address)` — no cambia nunca.**

---

### 0.7 Lista de tokens a consultar (red SFXM = ChainID 56)

Los contratos en la red SFXM usan las **mismas direcciones** que BSC mainnet:

```
Símbolo | Dirección del contrato                       | Decimales
--------|----------------------------------------------|----------
USDT    | 0x55d398326f99059fF775485246999027B3197955   | 18
USDC    | 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d   | 18
DAI     | 0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3   | 18
ETH     | 0x2170Ed0880ac9A755fd29B2688956BD959F933F8   | 18
BTCB    | 0x7130d2A12B9BCbFAe4f2634d864A1Ee1Ce3Ead9c   | 18
BUSD    | 0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56   | 18
CAKE    | 0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82   | 18
ADA     | 0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47   | 18
XRP     | 0x1D2F0da169ceB9fC7B3144628dB156f3F6c60dBe   | 18
DOGE    | 0xbA2aE424d960c26247Dd6c32edC70B295c744C43   | 8   ← diferente!
SOL     | 0x570A5D26f7765Ecb712C0924E4De545B89fD43dF   | 18
```

---

### 0.8 Cómo codificar el parámetro `data` (para balanceOf)

```
data = "0x70a08231" + address.replace("0x","").toLowerCase().padStart(64, "0")
```

Ejemplo para `0xf39Fd6e51aad88F6f4ce6aB8827279cffFb92266`:
```
data = "0x70a08231" + "000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266"
```

---

### 0.9 Cómo leer balances en Android (pseudocódigo)

```kotlin
// Para cada token en la lista:
suspend fun getTokenBalance(rpcUrl: String, contractAddress: String, userAddress: String): BigInteger {
    val paddedAddr = userAddress.removePrefix("0x").lowercase().padStart(64, '0')
    val data = "0x70a08231$paddedAddr"

    val body = """{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"$contractAddress","data":"$data"},"latest"]}"""

    val response = httpPost(rpcUrl, body) // POST con Content-Type: application/json
    val result = response["result"] as String // "0x0000...0064"

    return if (result == "0x" || result.isNullOrEmpty()) BigInteger.ZERO
           else BigInteger(result.removePrefix("0x"), 16)
    // Dividir por 10^decimals para mostrar al usuario
}

// Llamar en paralelo para todos los tokens:
val rpc = "https://rcp.sfx-microsystem.org"
val userAddress = wallet.activeAddress

val bnbBalance = eth_getBalance(rpc, userAddress)      // balance nativo (ya implementado)

val tokenBalances = TOKENS.map { token ->
    token.symbol to getTokenBalance(rpc, token.address, userAddress) // ESTO faltaba
}
```

---

### 0.10 Mostrar el balance en la UI

El resultado de `eth_call` es el balance en la unidad mínima del token (equivalente a Wei).
Para convertir a unidades de usuario:

```kotlin
val rawBalance: BigInteger = getTokenBalance(...)
val decimals: Int = 18  // (u 8 para DOGE)
val displayBalance: Double = rawBalance.toBigDecimal()
    .divide(BigDecimal.TEN.pow(decimals))
    .toDouble()
```

---

## 1. background.js

### 1.1 Quitar `autoFundSFXM` del handler `GET_BALANCE`

El handler solo debe leer el saldo real de la cadena, sin fondear automáticamente.

**Antes:**
```javascript
if (type === 'GET_BALANCE') {
  const st = await getState();
  if (!st.activeAddress) return { ok: false, error: 'Sin dirección' };
  if (st.activeChain === SFXM_CHAIN) autoFundSFXM(st.activeAddress); // ← QUITAR esto
  try {
    const bal = await rpcCall(st.activeChain, 'eth_getBalance', [st.activeAddress, 'latest']);
    return { ok: true, balance: normHex(bal), address: st.activeAddress };
  } catch (e) { return { ok: false, error: e.message }; }
}
```

**Después:**
```javascript
if (type === 'GET_BALANCE') {
  const st = await getState();
  if (!st.activeAddress) return { ok: false, error: 'Sin dirección' };
  try {
    const bal = await rpcCall(st.activeChain, 'eth_getBalance', [st.activeAddress, 'latest']);
    return { ok: true, balance: normHex(bal), address: st.activeAddress };
  } catch (e) { return { ok: false, error: e.message }; }
}
```

---

### 1.2 Agregar `autoFundSFXM` + gas mínimo en `APPROVE_TX`

Cuando el usuario aprueba una transacción de una DApp en la red SFXM, hay que
asegurar que tenga gas antes de enviar, y fijar el gas limit para transfers de token.

Dentro del handler `APPROVE_TX`, justo antes de `wallet.sendTransaction(...)`:

```javascript
// Fondear si está en red SFXM y el saldo de gas es bajo
if (st.activeChain === SFXM_CHAIN) await autoFundSFXM(wallet.address);

// Fijar gas limit: usar el de la tx o, si es transfer ERC-20, 200 000
const isTokenTransfer = tx.data && tx.data.startsWith('0xa9059cbb');
const gasToUse = tx.gas || tx.gasLimit || (isTokenTransfer ? '0x30D40' : undefined);

const txResp = await wallet.sendTransaction({
  to:       tx.to,
  value:    tx.value    || '0x0',
  data:     tx.data     || '0x',
  gasLimit: gasToUse,
  gasPrice: tx.gasPrice,
});
```

> `autoFundSFXM` debe permanecer también en el handler `SEND` (no tocarlo).

---

### 1.3 Sistema de auto-update

Agregar antes del listener de mensajes principal.

**Funciones de soporte:**
```javascript
const SFXM_SERVER = 'https://sfx-microsystem.org'; // si no existe ya

function cmpVer(a, b) {
  const pa = a.split('.').map(Number), pb = b.split('.').map(Number);
  for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
    const d = (pa[i] || 0) - (pb[i] || 0);
    if (d !== 0) return d;
  }
  return 0;
}

async function checkForUpdates() {
  try {
    const current = '1.5.3'; // reemplazar con la versión actual de la app
    const r = await fetch(SFXM_SERVER + '/api/wallet-version', { cache: 'no-store' });
    if (!r.ok) return;
    const { version: latest } = await r.json();
    if (cmpVer(latest, current) > 0) {
      await setState({ pendingUpdate: { current, latest } });
    } else {
      const st = await getState();
      if (st.pendingUpdate) await setState({ pendingUpdate: null });
    }
  } catch (_) {}
}

// Revisar al arrancar y cada 4 horas
checkForUpdates();
setInterval(checkForUpdates, 4 * 60 * 60 * 1000);
```

**Handler `DISMISS_UPDATE`** — agregar en el dispatch junto a los otros handlers:
```javascript
if (type === 'DISMISS_UPDATE') {
  await setState({ pendingUpdate: null });
  return { ok: true };
}
```

**Exponer en `GET_STATE`** — incluir en el objeto que retorna:
```javascript
pendingUpdate: st.pendingUpdate || null,
```

---

## 2. popup.js (lógica de la UI)

### 2.1 Contador de generación en `refreshMain()` — elimina race condition

Sin esto, si el usuario cambia de cuenta mientras la cuenta anterior aún está
cargando precios de Binance, los datos viejos sobreescriben los nuevos.

**Agregar variable global** (junto a las demás declaraciones `let` del módulo):
```javascript
let _refreshGen = 0;
```

**Modificar `refreshMain()`** — primera línea de la función y 3 checks después de cada await:
```javascript
async function refreshMain() {
  const gen = ++_refreshGen;          // ← nueva primera línea
  const st = _st;
  if (!st.activeAddress) return;
  // ... resto del código igual ...

  const balR = await bg('GET_BALANCE');
  if (gen !== _refreshGen) return;    // ← después de GET_BALANCE
  if (balR.ok) _natBal = normHex(balR.balance);

  const tokBalR = await bg('GET_TOKEN_BALANCES');
  if (gen !== _refreshGen) return;    // ← después de GET_TOKEN_BALANCES
  if (tokBalR.ok) { /* procesar balances */ }

  // ... construir lista de tokens ...

  const priceR = await bg('GET_PRICES', { ... });
  if (gen !== _refreshGen) return;    // ← después de GET_PRICES
  if (priceR.ok) { _prices = priceR.prices; _changes = priceR.changes || {}; }

  updateBalanceDisplay();
  renderTokenList(st, net, customToks, defToks);
}
```

---

### 2.2 Limpiar display al cambiar de cuenta

En el click handler de cada fila de cuenta (en `renderAccounts()`), después de
llamar a `SWITCH_ACCOUNT` y antes de llamar a `initMain()`:

```javascript
await bg('SWITCH_ACCOUNT', { address: addr });
_st = await bg('GET_STATE');

// Limpiar datos de la cuenta anterior inmediatamente
_natBal = '0x0'; _tokBals = {}; _prices = {}; _changes = {};
// Mostrar placeholder mientras carga la nueva cuenta
// (en Android: limpiar el TextView/componente de saldo y mostrar "...")
```

---

### 2.3 Poll detecta cambio de dirección y refresca

El intervalo de polling (cada ~1.5 s) debe detectar si la cuenta activa cambió
y disparar un refresh automáticamente.

```javascript
function startPoll() {
  stopPoll();
  let _lastPollAddr = _st?.activeAddress;

  _pollTimer = setInterval(async () => {
    const s = await bg('GET_STATE');
    if (!s.ok) return;
    const addrChanged = s.activeAddress !== _lastPollAddr;
    _lastPollAddr = s.activeAddress;
    _st = s;

    if (enPantallaMain) {
      if (s.pendingConn) { /* mostrar conn */ }
      else if (s.pendingTx) { /* mostrar tx */ }
      else if (addrChanged) await refreshMain(); // ← nuevo
    }
  }, 1500);

  // Refresh completo de saldo cada 30 s
  _balRefreshTimer = setInterval(async () => {
    if (enPantallaMain) await refreshMain();
  }, 30000);
}
```

---

### 2.4 Botón "volver" de cuentas refresca la pantalla principal

```javascript
// Listener del botón back en la pantalla de cuentas:
botonVolver.addEventListener('click', async () => {
  mostrarPantalla('main');
  await refreshMain(); // ← agregar esto
});
```

---

### 2.5 Banner de actualización disponible

Cuando `_st.pendingUpdate` está definido, mostrar un banner en la parte
superior de la pantalla principal.

```javascript
function renderUpdateBanner(upd) {
  const banner = document.getElementById('update-banner'); // o el View de Android
  if (!upd) { /* ocultar banner */ return; }
  // Mostrar: "Nueva versión v{upd.latest} disponible"  [Actualizar] [×]
  banner.style.display = 'flex';
  labelVersion.textContent = 'v' + upd.latest;
}

// Botón "Actualizar":
btnActualizar.addEventListener('click', async () => {
  const upd = _st.pendingUpdate;
  if (!upd) return;
  // Lanzar descarga / abrir Play Store / abrir URL de descarga
  abrirDescarga('https://sfx-microsystem.org/sfx-wallet.zip?v=' + upd.latest);
  await bg('DISMISS_UPDATE');
  _st = await bg('GET_STATE');
  renderUpdateBanner(null);
});

// Botón "×" (descartar):
btnCerrar.addEventListener('click', async () => {
  await bg('DISMISS_UPDATE');
  _st = await bg('GET_STATE');
  renderUpdateBanner(null);
});

// En initMain(), después de mostrar la pantalla principal:
renderUpdateBanner(_st.pendingUpdate);
```

---

## 3. popup.html (estructura)

### 3.1 Restaurar botón "Crear wallet con nueva semilla"

Asegurarse de que el botón para crear una wallet con semilla nueva esté **visible**
(no oculto con `display:none`). Sirve para demostrar cuentas vacías, ya que una
semilla nueva genera direcciones que nunca han sido fondeadas.

```html
<button id="btn-new-hd-wallet">
  Crear wallet con nueva semilla
</button>
```

### 3.2 Agregar banner de actualización en la pantalla principal

Colocarlo justo antes de la tarjeta de saldo, oculto por defecto:

```html
<!-- Oculto por defecto; se muestra desde JS cuando hay update disponible -->
<div id="update-banner" style="display:none; /* flex cuando activo */
     align-items:center; gap:8px; padding:7px 12px;
     background:rgba(0,229,255,.08); border-bottom:1px solid rgba(0,229,255,.2)">

  <!-- Ícono de descarga -->
  <span>Nueva versión <b id="upd-latest"></b> disponible</span>
  <button id="btn-do-update">Actualizar</button>
  <button id="btn-update-later">×</button>
</div>
```

---

## 4. Resumen de prioridades

| # | Cambio | Impacto |
|---|--------|---------|
| **0.1–0.5** | **Envío de tokens: construir data ERC-20 correctamente** | **🔴 CRÍTICO** — sin esto, enviar USDT manda BNB en su lugar |
| **0.6–0.10** | **Leer saldo de tokens con eth_call balanceOf** | **🔴 CRÍTICO** — sin esto, la app solo muestra saldo BNB |
| 1.1 | Quitar autoFund de GET_BALANCE | **Crítico** — todas las cuentas muestran saldo distinto |
| 2.1 | Contador de generación refreshMain | **Crítico** — race condition que muestra saldo equivocado |
| 1.2 | autoFund + gas en APPROVE_TX | **Alto** — transfers desde DApps fallan sin gas |
| 2.2 | Limpiar display al cambiar cuenta | **Alto** — UX confusa al cambiar de cuenta |
| 2.3 | Poll detecta cambio de dirección | **Medio** — refresh automático al cambiar cuenta |
| 2.4 | Back button refresca main | **Medio** — saldo no se actualiza al volver |
| 1.3 + 2.5 | Sistema de auto-update | **Medio** — notificación de nuevas versiones |
| 3.1 | Restaurar botón nueva semilla | **Bajo** — necesario para demo de cuenta vacía |

---

*Generado el 2026-06-27 — SFX Wallet v1.5.3*
