Pular para o conteúdo principal

Wallet

Carteira, bonus, cashback, rollover e historico de transacoes no template.

:::danger @cactus-agents/wallet nao existe mais O pacote foi deletado (core 452450e, 2026-04-17) e unificado em @cactus-agents/accounts, junto com auth e user. Nenhum import … from "@cactus-agents/wallet" resolve.

  • Tipos e transforms (Wallet, RealWallet, BonusWallet, CashbackWallet, Rollover, Transaction, TransactionFilter) → @cactus-agents/accounts
  • Estado e hooks React (useAccountsStore, useWallet, useRollover, useTransactions, …) → @cactus-agents/accounts/react

Ver SDK — accounts. :::

Onde vive o estado

Nao existe app/store/wallet.ts, app/hooks/useWallet.ts nem app/utils/walletRefresh.client.ts no template. Todo o estado de carteira e owned pelo core, em useAccountsStore:

// campos relevantes de AccountsState (@cactus-agents/accounts/react)
interface AccountsState {
wallet: WalletData | null;
isLoading: boolean;
error: Error | null;

transactions: Transaction[];
transactionsRaw: TransactionResponseRaw | null;
transactionsMeta: TransactionsMeta | null;
transactionsLoading: boolean;
transactionsFilter: TransactionFilter;

// moedas (brands dual-currency)
activeCoinId: CoinId | null;
coinBalances: Record<CoinId, number> | null;

// actions
setWallet(data: WalletData | null): void;
refetchWallet(opts?: { force?: boolean }): Promise<void>;
fetchTransactions(filter?: TransactionFilter): Promise<void>;
transferBonus(): Promise<void>;
transferCashback(): Promise<void>;
}

O que o base possui e apenas o estado de UI dos modais:

// app/store/walletModal.ts
export type WalletModalType = "bonus-withdraw" | "bonus-rules" | "cashback-redeem" | null;

export const useWalletModalStore = create<WalletModalState>((set) => ({
walletModal: null,
openWalletModal: (modal) => set({ walletModal: modal }),
closeWalletModal: () => set({ walletModal: null }),
}));

Hooks (@cactus-agents/accounts/react)

HookRetorno
useWallet(){ wallet, isLoading, error, refetch }
useRealBalance() / useBonusBalance() / useCashbackBalance(){ balance, isLoading, error, refetch }balance ja formatado
useWalletFlags(){ hasReal, hasBonus, hasCashback, hasFirstDeposit }
useRollover(){ rollover, accomplished, isLoading }
useTransactions(){ transactions, meta, isLoading, filter, fetch, raw }
useTransferBonus() / useTransferCashback(){ transfer, isLoading, error }
useRefreshWallet()(opts?: { force?: boolean }) => Promise<void>
useWithdrawReceipt(withdrawId){ receipt, isLoading, error } — PDF base64, auto-fetch
useFormatMoney()(value: number) => string

Exemplo real, da propria pagina de carteira:

import {
useFormatMoney,
useRefreshWallet,
useRollover,
useTransactions,
useWallet,
} from "@cactus-agents/accounts/react";
import { useWalletModalStore } from "~/store/walletModal";

const { wallet, isLoading: walletLoading } = useWallet();
const { rollover, accomplished: rolloverAccomplished } = useRollover();
const formatMoney = useFormatMoney();
const updateWallet = useRefreshWallet();
const { transactions, filter: txFilter, fetch: fetchTx } = useTransactions();
const openWalletModal = useWalletModalStore((s) => s.openWalletModal);

:::tip Formate dinheiro so com useFormatMoney() Ele resolve locale + currency + displayDecimalDigits (0 pra NGN/CLP, 2 pras demais) a partir da config do accounts, e reage a troca de moeda nas brands dual-currency. Nao use Intl.NumberFormat({ style: "currency" }) a mao nem ~/utils/money em componentes React.

useTransactions() ja devolve amountFormatted (valor absoluto formatado) em cada transacao — use amount apenas pra logica de sinal. :::

useRefreshWallet() e o TTL

O refresh aplica memoize de 2s + dedup de in-flight. Chamadas passivas (botao de reload, listeners, hooks de mount) devem omitir opcoes, pra que gatilhos quase simultaneos colapsem numa unica chamada ao BFF.

Passe { force: true } depois de uma mutacao que mudou o estado da carteira: deposito, saque, aposta, transferencia de bonus/cashback.

API Routes

RotaDescricao
api/wallet/refreshRefresh completo (saldo + rollover)
api/wallet/transactionsTransacoes paginadas com filtros
api/wallet/actionTransfer bonus/cashback
api/wallet/receiptComprovante de saque

Os adapters HTTP default do accounts apontam pra essas rotas; a brand pode sobrescrever via AccountsConfig na inicializacao. Erros seguem proxyErrorResponse() + unauthorizedNoToken() — ver Services.

Paginas

ChavePath padraoDescricao
user.wallet/user/walletCards de saldo + rollover + atividade recente
user.history/user/historyHistorico unificado: transacoes e login, com filtro e paginacao

:::warning Nao existe /user/wallet/transactions O historico de transacoes foi unificado com o historico de login em user.history (routes/user/history.tsx). Os filtros disponiveis hoje sao deposit, withdraw, sports, casino, coupon, bonus, cashback, login e full. :::

A store de transacoes e unica e compartilhada entre /user/wallet e /user/history. A pagina de carteira compara o filter do ultimo fetch (exposto pelo hook) com a query dela mesma antes de renderizar o bloco de atividade recente — sem isso, remontar a pagina esconderia o bloco a cada navegacao.

Componentes

Em app/components/user/wallet/ — lista viva, consulte o diretorio.

Cards de saldo

  • RealWalletCard — saldo real com botao de deposito
  • BonusWalletCard — saldo bonus + progresso do rollover
  • CashbackWalletCard — saldo cashback com botao de resgate

Historico

  • WalletRecentActivity — bloco de atividade recente na pagina de carteira
  • TransactionCard / TransactionCardSkeleton (em app/components/user/) — item de transacao
  • Pagination — navegacao de paginas
  • LoginCardSkeleton (em app/components/user/) — skeleton do historico de login

Modais

  • BonusWithdrawModal — confirmacao de transferencia bonus → real
  • BonusRulesModal — regras do bonus ativo
  • CashbackRedeemModal — confirmacao de resgate de cashback
  • WithdrawDetailsModal — comprovante de saque (usa useWithdrawReceipt)

Outros

  • WalletInitializer (app/components/user/WalletInitializer.tsx) — busca a carteira uma vez quando o usuario esta autenticado (login ou reload). Nao renderiza nada
  • CountdownTimer — timer regressivo (ex.: expiracao de bonus)
  • ProgressBar — barra de progresso do rollover

WalletInitializer — implementacao

import { useAccountsStore, useRefreshWallet } from "@cactus-agents/accounts/react";

export function WalletInitializer() {
const hydrated = useAccountsStore((s) => s.authHydrated);
const isAuthenticated = useAccountsStore((s) => s.isAuthenticated);
const refreshWallet = useRefreshWallet();
// efeito dispara uma vez por sessao autenticada; reseta no logout
}

Ele e montado lazy pelo DefaultLayout e le auth da store, nao de loaderData — o doc SSR e auth-agnostico. Ver State Management.

Restricao de conta

A pagina de carteira consulta useAccountRestriction() (app/hooks/useAccountRestriction.ts, que empacota isAccountRestricted / getAccountRestriction de @cactus-agents/accounts) pra esconder acoes indisponiveis quando a conta esta restrita. Ver User Account.