Sikkerhetsselskapet PeckShield registrerte 50 større kryptohack i august 2026, det høyeste månedlige tallet så langt i år. Samlet tap endte på 136,3 millioner dollar, ifølge en analyse gjengitt av Investing.com i september, ned 49,5 prosent fra juli målt i dollar, men opp fra 37 hendelser måneden før. De fleste overskriftene handler om protokoller som blir plyndret gjennom smarte kontrakter. Det som drukner i støyen, er hvor sårbar en vanlig privatperson er når formuen ligger spredt over en kald Bitcoin-lommebok, en Ethereum-adresse og et par lag 2-nettverk som Base og Arbitrum.
Problemet er strukturelt, ikke bare et spørsmål om dårlige vaner. Bitcoin, Ethereum og lag 2-kjedene bruker helt ulike datakilder, og de fleste porteføljeapper viser deg bare et øyeblikksbilde når du selv åpner appen. Ingen varsler deg automatisk klokken tre om natten hvis en godkjenning du signerte for seks måneder siden plutselig blir brukt til å tømme en lommebok. I denne artikkelen bygger vi et eget verktøy for porteføljevarsling i Python som sjekker saldo og godkjenninger på tvers av fem kjeder, lagrer historikk lokalt og sender et Telegram-varsel i det øyeblikket noe avviker fra normalen. Du trenger ikke være proffutvikler for å følge med, men du bør kjenne til terminalen og ha skrevet litt Python før.
Verktøyet vi bygger er ikke en erstatning for en maskinvarelommebok eller god signeringshygiene. Det er et andre forsvarslag som fanger opp det du selv ikke rekker å sjekke manuelt hver dag. Sett av rundt 90 minutter til hele oppsettet, inkludert testing. Alt kjører lokalt eller på en liten server du selv kontrollerer, og ingen private nøkler forlater noensinne maskinen din, siden verktøyet kun leser offentlige data fra blokkjeden.
Hvorfor vanlige porteføljeapper ikke fanger opp tyveri
De fleste porteføljeapper er bygget for å vise deg hvor mye du eier akkurat nå, ikke for å varsle deg om at noe endret seg mens du sov. Åpner du appen en gang om dagen, kan et tyveri ha stått i flere timer før du oppdager det, og da er midlene som regel allerede byttet til et annet aktivum og flyttet videre gjennom en miksertjeneste. Push-varsler fra børser dekker som regel bare kontoen din der, ikke lommebøkene du selv har full kontroll over. Det er nettopp gapet mellom “jeg eier dette” og “jeg vet med en gang hvis noe forsvinner” som gjør selvstendig overvåking verdt bryet, selv for en portefølje som ikke er enorm.
Utfordringen forsterkes av at Bitcoin og Ethereum-familien er bygget på fullstendig ulike prinsipper. Bitcoin bruker et UTXO-system der en “adresse” bare er én av potensielt mange innganger til lommeboken din, mens Ethereum og lag 2-kjedene bruker kontobaserte adresser med én løpende saldo. Et verktøy som bare forstår den ene modellen, gir deg et blindt punkt på den andre kjeden. Det er nøyaktig dette vi løser ved å bygge separate, men koordinerte datainnhentere for hver kjedetype, og samle resultatet i én database og én varslingskanal.
Lag 2-nettverk som Base og Arbitrum gjør problemet enda tydeligere. De er billige og raske nok til at mange bruker dem til daglige transaksjoner, samtidig som en betydelig del av porteføljen fort flytter dit over tid. Resultatet er en formue spredt over fire eller fem separate saldi, ofte i samme lommebok-app, men uten noen samlet varsling som følger med automatisk. Å sjekke fire ulike blokkjede-utforskere manuelt hver dag er verken realistisk eller noe et menneske husker å gjøre konsekvent over tid, som er nøyaktig den typen jevn, kjedelig oppgave et lite skript løser bedre enn deg selv.
Forutsetninger: verktøy og versjoner du trenger
Skriptet bruker kun frie, offentlige API-er og et par velprøvde Python-biblioteker. Etherscan API V2 dekker Ethereum, Base og Arbitrum med én og samme nøkkel, mens Bitcoin-saldoen hentes fra mempool.space, som ikke krever registrering i det hele tatt. Sjekk versjonsnumrene mot PyPI før du installerer, siden bibliotekene oppdateres jevnlig.
| Verktøy | Versjon / krav | Formål |
|---|---|---|
| Python | 3.11 eller nyere | Kjøremiljø for skriptet |
| web3.py | 7.14.1 (stabil på PyPI) | Lese kontraktsdata og godkjenninger på EVM-kjeder |
| requests | 2.32.x eller nyere | HTTP-kall mot Etherscan, mempool.space og Telegram |
| Etherscan API V2-nøkkel | Gratis-nivå | Saldo og transaksjoner for Ethereum, Base, Arbitrum |
| mempool.space API | Ingen nøkkel kreves | Saldo og transaksjonshistorikk for Bitcoin |
| Telegram-bot | Opprettet via BotFather | Sende sanntidsvarsler til mobilen |
| SQLite | Innebygd i Python | Lagre øyeblikksbilder av saldo lokalt |
Hvilke adresser og kjeder bør du inkludere?
Start med adressene som faktisk har verdi i dag, ikke hver eneste adresse du noen gang har brukt. Et vanlig oppsett for en aktiv bruker er én kald Bitcoin-adresse i en maskinvarelommebok, én Ethereum-hovedadresse og de samme adressene speilet på Base og Arbitrum siden mange bruker samme nøkkelpar på tvers av EVM-kjeder. Legg til flere adresser senere ved å utvide konfigurasjonsfilen, du trenger ikke skrive om noe kode for det.
Vær bevisst på forskjellen mellom en enkeltadresse og en hel lommebok. En moderne Bitcoin-lommebok genererer som regel en ny mottaksadresse for hver transaksjon, noe som betyr at én enkelt adresse sjelden viser hele saldoen din over tid. Vi kommer tilbake til hvordan du løser dette med en samleoversikt (en såkalt xpub) lenger ned i artikkelen. På Ethereum-siden er derimot adressen din fast, så der holder det med adressen alene for å følge med på native ETH-saldoen.
Legg merke til hvor ulike datakildene er per kjedetype før du begynner å kode, det sparer deg for en del forvirring senere. Tabellen under oppsummerer forskjellene, samt en pekepinn for en fremtidig utvidelse til Solana hvis porteføljen din vokser i den retningen.
| Kjedetype | Datamodell | Datakilde i denne artikkelen | Krever nøkkel? |
|---|---|---|---|
| Bitcoin | UTXO, roterende adresser | mempool.space (adresse eller xpub) | Nei |
| Ethereum, Base, Arbitrum | Konto med fast adresse | Etherscan API V2 (chainid-parameter) | Ja, gratisnivå |
| Solana (utenfor denne artikkelen) | Konto med fast adresse, annen serialisering | Eget Solana RPC eller Solscan-API | Ja, ulikt oppsett |
Steg 1-2: Sett opp prosjektet og skaff API-nøkler
Opprett en egen mappe for prosjektet og et isolert virtuelt miljø, slik at avhengighetene ikke krasjer med andre Python-prosjekter på maskinen din. Installer deretter de to bibliotekene fra tabellen over.
mkdir portefolje-varsling && cd portefolje-varsling
python3 -m venv venv
source venv/bin/activate
pip install "web3==7.14.1" "requests>=2.32"
mkdir -p data
Gå til etherscan.io og opprett en gratis konto, deretter en API-nøkkel under “API Keys” i kontoinnstillingene. Etherscan API V2 bruker samme nøkkel til over 60 EVM-kjeder, du velger bare riktig kjede med parameteren chainid i hvert kall. Opprett så en Telegram-bot ved å skrive til @BotFather i Telegram-appen, gi den et navn, og lagre token-strengen du får tilbake. Send én melding til boten din, og hent deretter chat-ID-en din fra https://api.telegram.org/bot<TOKEN>/getUpdates. Lagre alt i en .env-fil som aldri sjekkes inn i noe versjonskontrollsystem.
# .env
ETHERSCAN_API_KEY=din_etherscan_nokkel
TELEGRAM_BOT_TOKEN=din_bot_token
TELEGRAM_CHAT_ID=din_chat_id
Steg 3-4: Konfigurer flere kjeder og hent EVM-saldo
Neste steg er å definere hvilke adresser og kjeder som skal overvåkes, i en enkel JSON-fil. Hold konfigurasjonen adskilt fra koden, slik at du kan legge til flere lommebøker uten å røre skriptet. Denne separasjonen mellom kode og data er ikke bare ryddig, den gjør det også trygt å dele selve skriptet med andre eller legge det i et offentlig kodearkiv, siden ingen adresser eller nøkler noensinne havner i selve kildekoden.
{
"evm_chains": [
{"name": "ethereum", "chainid": 1},
{"name": "base", "chainid": 8453},
{"name": "arbitrum", "chainid": 42161}
],
"evm_addresses": ["0xDinEthereumAdresseHer"],
"btc_addresses": ["bc1qDinBitcoinAdresseHer"],
"alert_threshold_pct": 8
}
Med konfigurasjonen på plass kan vi skrive funksjonen som henter saldo fra Etherscan API V2. Legg merke til at samme funksjon gjenbrukes for alle tre EVM-kjedene, du bytter bare chainid-verdien.
import os, requests
ETHERSCAN_KEY = os.environ["ETHERSCAN_API_KEY"]
def get_evm_balance(address: str, chainid: int) -> int:
url = "https://api.etherscan.io/v2/api"
params = {
"chainid": chainid,
"module": "account",
"action": "balance",
"address": address,
"tag": "latest",
"apikey": ETHERSCAN_KEY,
}
resp = requests.get(url, params=params, timeout=15)
resp.raise_for_status()
data = resp.json()
if data.get("status") != "1" and data.get("message") != "OK":
raise RuntimeError(f"Etherscan-feil: {data}")
return int(data["result"]) # saldo i wei
Etherscans gratisnivå tillater 3 kall i sekundet og opptil 100 000 kall per døgn, noe som er mer enn nok til å sjekke noen få adresser hvert 5. eller 10. minutt. Husk å dele wei-verdien på 10^18 når du skal vise beløpet i ETH i loggen eller varselet.
Det er verdt å regne på kvoten før du setter intervallet, spesielt hvis du senere legger til flere adresser eller tokens. Med tre EVM-kjeder, én adresse og to tokens per kjede blir det ni kall per runde. Kjører du runden hvert 5. minutt, gir det 288 runder i døgnet, altså 2 592 kall totalt, godt innenfor grensen på 100 000. Selv om du dobler antall adresser og halverer intervallet til hvert 2,5. minutt, lander du fortsatt godt under taket. Problemet oppstår først hvis du legger til svært mange adresser og samtidig henter full transaksjonshistorikk for godkjenningssjekken på hver runde, siden tokentx-kallet teller likt med et enkelt saldo-kall, men gjerne kjøres sjeldnere, for eksempel hver time i stedet for hvert 5. minutt.
Steg 5-6: Hent Bitcoin-saldo og lagre øyeblikksbilder
Bitcoin er ikke en EVM-kjede, så Etherscan kan ikke hjelpe deg her. mempool.space har et åpent REST-API som ikke krever nøkkel, og som gir deg både bekreftet og ubekreftet saldo for en gitt adresse.
def get_btc_balance(address: str) -> int:
url = f"https://mempool.space/api/address/{address}"
resp = requests.get(url, timeout=15)
resp.raise_for_status()
stats = resp.json()["chain_stats"]
funded = stats["funded_txo_sum"]
spent = stats["spent_txo_sum"]
return funded - spent # saldo i satoshi
For å oppdage avvik trenger vi et forrige øyeblikksbilde å sammenligne med. En lett SQLite-database er nok til formålet, den krever ingen ekstern server og passer fint på en liten VPS eller en Raspberry Pi.
import sqlite3, time
def init_db(path="data/snapshots.db"):
con = sqlite3.connect(path)
con.execute("""
CREATE TABLE IF NOT EXISTS snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
address TEXT, chain TEXT,
balance INTEGER, ts INTEGER
)
""")
con.commit()
return con
def save_snapshot(con, address, chain, balance):
con.execute(
"INSERT INTO snapshots (address, chain, balance, ts) VALUES (?,?,?,?)",
(address, chain, balance, int(time.time())),
)
con.commit()
def last_snapshot(con, address, chain):
row = con.execute(
"SELECT balance FROM snapshots WHERE address=? AND chain=? ORDER BY ts DESC LIMIT 1 OFFSET 1",
(address, chain),
).fetchone()
return row[0] if row else None
Utvid overvåkingen til ERC-20-tokens og stablecoins
De fleste porteføljer består ikke bare av native ETH. Stablecoins som USDC og USDT, samt andre ERC-20-tokens, utgjør ofte en større del av verdien enn selve gassvalutaen på kjeden. Etherscan API V2 har et eget endepunkt for tokensaldo, tokenbalance, som fungerer på nøyaktig samme måte som saldo-kallet vi allerede skrev, bare med en ekstra kontraktsadresse som parameter. Legg til de tokenene du faktisk holder i konfigurasjonsfilen, sammen med kontraktsadressen og antall desimaler, siden ERC-20-tokens ikke alle bruker 18 desimaler slik ETH gjør.
def get_token_balance(address: str, contract: str, chainid: int) -> int:
url = "https://api.etherscan.io/v2/api"
params = {
"chainid": chainid,
"module": "account",
"action": "tokenbalance",
"contractaddress": contract,
"address": address,
"tag": "latest",
"apikey": ETHERSCAN_KEY,
}
resp = requests.get(url, params=params, timeout=15)
resp.raise_for_status()
return int(resp.json()["result"])
# Eksempel: USDC pa Base
USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
Behandle hver token-saldo som en egen linje i snapshot-databasen, på samme måte som du gjør med native ETH og Bitcoin. Det gir deg mulighet til å sette ulike terskler for ulike aktiva, siden et fall i en liten spekulativ altcoin-posisjon kanskje ikke er like alarmerende som et tilsvarende fall i stablecoin-beholdningen din.
Steg 7-8: Bygg avviksregler og oppdag mistenkelige godkjenninger
Selve deteksjonslogikken bør holdes enkel i starten. Sammenlign gjeldende saldo mot forrige øyeblikksbilde, og flagg alt som faller mer enn terskelen du satte i konfigurasjonen (8 prosent er et fornuftig utgangspunkt for en portefølje som ikke flyttes ofte). Legg også inn en regel for uvanlig store enkeltuttak, siden en gradvis nedgang på 8 prosent over en uke ofte er normal bruk, mens et brått fall på 40 prosent på ti minutter sjelden er det.
def check_anomaly(chain, address, current, previous, threshold_pct):
if previous is None or previous == 0:
return None
drop_pct = ((previous - current) / previous) * 100
if drop_pct >= threshold_pct:
return (
f"Varsel: {chain} {address[:10]}... falt {drop_pct:.1f}% "
f"({previous} -> {current})"
)
return None
ERC-20-godkjenninger og Permit2-signaturer
Saldofall er bare halve bildet. Mange drainer-angrep starter med at offeret signerer en approve-transaksjon eller en Permit2-signatur som gir en fremmed adresse rett til å flytte tokens senere, uten at noe forsvinner med det samme. Skriptet kan hente kontoens siste transaksjoner via Etherscan sitt tokentx-endepunkt og se etter Approval-hendelser med uvanlig høy beløpsgrense eller en mottaker som ikke finnes i en egen liste over kjente, tiltrodde kontrakter. Merk at et rent kjede-basert overvåkingsverktøy aldri kan fange opp en signatur før den faktisk sendes til kjeden, det er en iboende begrensning du bør være ærlig med deg selv om.
def get_recent_approvals(address: str, chainid: int, trusted: set[str]):
url = "https://api.etherscan.io/v2/api"
params = {
"chainid": chainid, "module": "account",
"action": "tokentx", "address": address,
"sort": "desc", "apikey": ETHERSCAN_KEY,
}
resp = requests.get(url, params=params, timeout=15).json()
flagged = []
for tx in resp.get("result", [])[:20]:
spender = tx.get("to", "").lower()
if spender and spender not in trusted:
flagged.append(spender)
return flagged
Bitcoin xpub og roterende adresser
Siden en moderne Bitcoin-lommebok sjelden gjenbruker adresser, holder det ikke å overvåke bare én av dem hvis du vil ha et fullstendig bilde. Løsningen er å overvåke en utvidet offentlig nøkkel (xpub, ypub eller zpub, avhengig av adressetype), som lar deg beregne alle nåværende og fremtidige mottaksadresser uten å noen gang eksponere en privat nøkkel. mempool.space støtter oppslag direkte på en xpub gjennom samme type endepunkt vi allerede har brukt, den returnerer da en samlet saldo på tvers av alle utledede adresser.
def get_btc_xpub_balance(xpub: str) -> int:
url = f"https://mempool.space/api/v1/xpub/{xpub}"
resp = requests.get(url, timeout=20)
resp.raise_for_status()
stats = resp.json()["chain_stats"]
return stats["funded_txo_sum"] - stats["spent_txo_sum"]
De fleste maskinvarelommebøker lar deg eksportere en xpub uten å låse opp enheten, siden den offentlige nøkkelen i seg selv ikke kan brukes til å signere eller flytte midler. Sjekk likevel dokumentasjonen til din spesifikke lommebokprodusent, siden noen skiller mellom “kontovisning” og “signering” i menyene sine på litt ulike måter. Bruk xpub-varianten fremfor enkeltadresser i produksjon, det sparer deg for mye vedlikehold hver gang lommeboken genererer en ny adresse.
Vær samtidig oppmerksom på at en xpub i seg selv er informasjon du bør beskytte, selv om den ikke kan brukes til å flytte midler. Med en xpub kan hvem som helst se hele transaksjonshistorikken og alle fremtidige adresser i den lommeboken, noe som er et personvernproblem selv om det ikke er et direkte sikkerhetsproblem i streng forstand. Lagre den på samme måte som du lagrer API-nøklene dine, i .env-filen, ikke i et delt konfigurasjonsdokument eller i ren tekst i et sted andre kan lese.
Steg 9-10: Send Telegram-varsler og sett opp overvåkingsløkken
Du trenger ikke noe eget bibliotek for å sende Telegram-varsler, et enkelt HTTP-kall mot Telegrams Bot API holder. Legg til nytt-forsøk-logikk slik at et midlertidig nettverksproblem ikke stopper varselet helt. Dette er bevisst holdt enkelt, for en personlig porteføljevarsling er en rå HTTP-forespørsel både lettere å feilsøke og mindre sårbar for brutte avhengigheter enn et fullverdig bot-rammeverk du egentlig ikke trenger funksjonaliteten til.
def send_alert(text: str) -> None:
token = os.environ["TELEGRAM_BOT_TOKEN"]
chat_id = os.environ["TELEGRAM_CHAT_ID"]
url = f"https://api.telegram.org/bot{token}/sendMessage"
for forsok in range(3):
try:
r = requests.post(url, json={"chat_id": chat_id, "text": text}, timeout=15)
r.raise_for_status()
return
except requests.RequestException:
time.sleep(2 ** forsok)
print("Klarte ikke sende Telegram-varsel etter 3 forsok:", text)
Hovedløkken binder alt sammen: den leser konfigurasjonen, henter saldo fra hver kjede, sammenligner med forrige øyeblikksbilde, og sender varsel ved avvik. Kjør den som en cron-jobb hvert 5. eller 10. minutt, eller som en systemd-timer hvis du vil ha bedre logging og automatisk omstart ved feil.
def run_check():
con = init_db()
cfg = json.load(open("config.json"))
for addr in cfg["evm_addresses"]:
for chain in cfg["evm_chains"]:
bal = get_evm_balance(addr, chain["chainid"])
prev = last_snapshot(con, addr, chain["name"])
save_snapshot(con, addr, chain["name"], bal)
msg = check_anomaly(chain["name"], addr, bal, prev, cfg["alert_threshold_pct"])
if msg:
send_alert(msg)
for addr in cfg["btc_addresses"]:
bal = get_btc_balance(addr)
prev = last_snapshot(con, addr, "bitcoin")
save_snapshot(con, addr, "bitcoin", bal)
msg = check_anomaly("bitcoin", addr, bal, prev, cfg["alert_threshold_pct"])
if msg:
send_alert(msg)
if __name__ == "__main__":
run_check()
For systemd, lag en timer-enhet som kjører skriptet hvert 5. minutt fremfor å stole på cron alene, siden systemd logger feilmeldinger langt mer lesbart via journalctl.
# /etc/systemd/system/portefolje-varsling.timer
[Unit]
Description=Kjor porteføljevarsling hvert 5. minutt
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
[Install]
WantedBy=timers.target
Steg 11-12: Test systemet og sikre verktøyet selv
Test aldri en porteføljevarsling ved å vente på et ekte tyveri. Sett terskelen midlertidig til 0,5 prosent og flytt en liten sum mellom to av dine egne adresser, eller kjør skriptet manuelt to ganger etter at du har sendt en liten testbetaling. Du bør se et varsel i Telegram innen sekunder. Sett terskelen tilbake til et realistisk nivå etterpå, ellers drukner du i falske alarmer hver gang du bruker lommeboken normalt.
Test også godkjenningssjekken separat fra saldosjekken, siden de to dekker ulike hendelsestyper. Godkjenn en liten, ufarlig testtransaksjon til en kontraktsadresse som ikke står i din trusted-liste (en testkontrakt på en testnett-kjede fungerer utmerket til formålet), og bekreft at skriptet faktisk flagger den nye mottakeren neste gang det kjører. Kjør begge testene før du stoler på systemet i produksjon, en fungerende saldosjekk sier ingenting om hvorvidt godkjenningssjekken faktisk virker som tiltenkt.
Selve overvåkingsverktøyet blir også et mål hvis noen finner ut at det eksisterer. Bruk kun lesetilgang: Etherscan-nøkler og mempool.space krever aldri skriverettigheter, så det er ingenting å stjele fra API-siden. Det virkelige risikopunktet er .env-filen med Telegram-token, siden en angriper som får tak i den kan sende falske “alt er trygt”-meldinger eller stoppe varsler. Sett filrettigheter til kun eier kan lese, og vurder å kjøre verktøyet på en egen liten VPS eller Raspberry Pi adskilt fra maskinen du bruker til daglig nettlesing.
chmod 600 .env
chown $(whoami):$(whoami) .env
Et typisk varsel i Telegram ser slik ut når terskelen brytes:
Varsel: ethereum 0xA1b2C3D4... falt 41.7% (2450000000000000000 -> 1428500000000000000)
Og et vellykket, stille kjøreloggeksempel uten avvik ser slik ut i terminalen:
$ python main.py
[2026-09-24 07:05:01] ethereum 0xA1b2...: 2.45 ETH (uendret)
[2026-09-24 07:05:02] base 0xA1b2...: 0.31 ETH (uendret)
[2026-09-24 07:05:03] arbitrum 0xA1b2...: 0.08 ETH (uendret)
[2026-09-24 07:05:05] bitcoin bc1q...: 0.1842 BTC (uendret)
Ingen avvik funnet.
Vanlige teknikker bak lommebok-tyveri i 2026
Det hjelper å forstå hva du faktisk prøver å fange opp, siden det gjør det lettere å prioritere riktig regel først. Adresseforgiftning (address poisoning) er en av de mer utbredte teknikkene: angriperen sender en verdiløs transaksjon fra en adresse som ligner svært på en du selv bruker ofte, i håp om at du kopierer feil adresse fra transaksjonshistorikken din neste gang du skal sende midler. Skriptet vårt fanger ikke opp forsøket i seg selv, men det vil fange opp den påfølgende feilsendingen som et saldofall, forutsatt at terskelen er satt fornuftig.
En annen vanlig vektor er falske dApp-grensesnitt som ber deg signere en tilsynelatende ufarlig transaksjon, men som i realiteten er en setApprovalForAll-forespørsel eller en Permit2-signatur med ubegrenset beløpsgrense. Fordi signaturen i seg selv ikke flytter noe med det samme, kan det gå dager eller uker før angriperen faktisk bruker godkjenningen til å tømme lommeboken. Det er nøyaktig dette tidsvinduet godkjenningssjekken i steg 7-8 er designet for å dekke, ved å flagge nye, ukjente mottakere av godkjenninger så raskt som mulig etter at de dukker opp på kjeden, i stedet for å vente til midlene faktisk er borte.
En tredje vektor som rammer selvstendige investorer spesielt hardt, er kompromitterte utklippstavler. Skadevare som bytter ut en kryptoadresse du limer inn med angriperens egen adresse, krever ikke at du signerer noe som helst feil, den utnytter bare at de fleste sjelden dobbeltsjekker hvert eneste tegn i en lang adresse før de trykker send. Her hjelper det lite med kjedeovervåking i sanntid, men et saldofall som ikke matcher en transaksjon du selv husker å ha godkjent, er nettopp det signalet som bør trigge deg til å stoppe opp og undersøke nærmere før du gjør noe mer på den samme maskinen.
Vanlige fallgruver
De fleste som bygger dette selv, snubler i noen av de samme fellene. Her er seks du bør unngå fra start.
- For lav terskel: En grense på 1-2 prosent gir så mange falske alarmer at du etter en uke ignorerer alle varsler, inkludert det ene som faktisk betyr noe. Start heller høyt og senk terskelen gradvis etter hvert som du ser hvor mye porteføljen din normalt svinger.
- Ingen skille mellom kjeder: Å slå sammen saldo fra alle kjeder til ett tall skjuler hvilken adresse som ble tømt. Lagre og varsle per kjede og adresse, aldri som et samlet totaltall alene, ellers bruker du unødvendig lang tid på å finne ut hvor problemet faktisk oppstod.
- Hardkodede nøkler i koden: Å lime inn API-nøkler direkte i skriptet, i stedet for miljøvariabler, er den vanligste årsaken til at nøkler havner i en offentlig Git-historikk ved et uhell. Legg
.envtil i.gitignorefør du skriver en eneste linje kode. - Ingen håndtering av API-feil: Etherscan og mempool.space har begge korte driftsavbrudd fra tid til annen. Et skript som krasjer i stedet for å logge feilen og prøve igjen neste runde, kan stå stille i timevis uten at du merker det, siden ingen sender deg et varsel om at varslingsverktøyet selv har sluttet å virke.
- Å stole blindt på egen adresseliste: Hvis du glemmer å legge til en ny lag 2-adresse etter at du har begynt å bruke den, overvåker verktøyet ingenting der pengene faktisk ligger. Sett en fast rutine, for eksempel en gang i måneden, hvor du krysjekker konfigurasjonsfilen mot hvilke adresser du faktisk har brukt.
- Ingen test av selve varslingskjeden: Et skript som “ser riktig ut” i koden, men som aldri har sendt et faktisk Telegram-varsel, er verdiløst den dagen du trenger det. Kjør en manuell testalarm minst én gang i måneden, ikke bare den dagen du satte opp systemet.
- Å glemme token-desimaler: Ulike ERC-20-tokens bruker ulikt antall desimaler. Behandler du et token med 6 desimaler som om det hadde 18, vil verktøyet vise et tall som er milliarder ganger for lavt eller for høyt, og all avviksdeteksjon på den linjen blir meningsløs.
Feilsøking
De fleste feilene du støter på, har en kjent årsak og en rask løsning. Tabellen under dekker de åtte vanligste symptomene.
| Symptom | Sannsynlig årsak | Løsning |
|---|---|---|
| Etherscan svarer med “Max rate limit reached” | Du kaller API-et raskere enn 3 forespørsler i sekundet | Legg inn en kort pause mellom kallene, eller bruk exponentiell backoff |
| “Invalid API Key” fra Etherscan | Nøkkelen er ikke lest riktig fra .env, eller er ikke aktivert ennå | Sjekk at miljøvariabelen faktisk lastes, vent noen minutter etter opprettelse |
| Bitcoin-saldoen viser feil tall | Du glemte å trekke spent_txo_sum fra funded_txo_sum | Bruk differansen, ikke bare funded-summen alene |
| Ingen Telegram-melding kommer frem | Feil chat-ID, eller du har ikke sendt en første melding til boten | Send en melding manuelt til boten, hent chat-ID på nytt via getUpdates |
| SQLite-feilen “database is locked” | To instanser av skriptet kjører samtidig, ofte cron og manuell kjøring i konflikt | Bruk systemd-timer i stedet for cron, og pass på at forrige kjøring er ferdig |
| Falske alarmer ved normal bruk | Terskelen er satt for lavt i forhold til hvor ofte du selv flytter midler | Hev terskelen, eller legg til unntak for adresser du selv kontrollerer |
| Skriptet stopper helt uten feilmelding | En ufanget unntakstype i et av API-kallene | Fang requests.RequestException eksplisitt rundt hvert kall og logg feilen |
| Godkjenning-sjekken flagger kjente, trygge kontrakter | Listen over tiltrodde kontrakter er ikke oppdatert | Vedlikehold trusted-listen manuelt, og oppdater den når du bruker en ny tjeneste |
Avanserte tips for produksjonsklar overvåking
Når grunnoppsettet er stabilt, er det noen naturlige neste steg. Legg til en enkel webhook-mottaker slik at varsler også kan trigge en handling, som å midlertidig sperre en API-nøkkel på børsen din hvis du bruker en kombinert løsning. Vurder å kjøre skriptet fra to uavhengige nettverk (for eksempel hjemme og på en VPS) og sammenlign resultatene, siden det reduserer sjansen for at ett enkelt API-utfall gir deg falsk trygghet. Roter Etherscan-nøkkelen din hver tredje til sjette måned, og bruk en egen nøkkel kun til overvåking, aldri den samme nøkkelen som en handelsbot eller et annet skript bruker.
Utvid gjerne varslingsnivåene i stedet for å behandle alt som en enkelt alarmtype. Et lite avvik på 8-15 prosent kan sendes som en vanlig Telegram-melding, mens et brått fall over 40 prosent bør trigge noe mer påtrengende, som et repeterende varsel hvert minutt inntil du kvitterer manuelt i loggen. Skriver du skriptet slik at hver regel returnerer en alvorlighetsgrad i tillegg til meldingsteksten, blir det enkelt å rute alvorlige hendelser til en egen kanal eller et separat telefonnummer via en SMS-tjeneste, uten å bygge om resten av systemet.
Når bør du gå fra DIY-skript til kommersielt verktøy?
Et hjemmesnekret skript som dette dekker det viktigste for en privatperson eller en liten portefølje: saldoendringer og synlige godkjenninger på kjeden. Det har derimot en grense det ikke kommer forbi, siden det bare ser hva som allerede har skjedd på kjeden, ikke signaturer som ennå ikke er sendt inn. Selskaper som Blockaid, Forta og Hypernative tilbyr transaksjonssikkerhet og sanntidsdeteksjon som fanger opp mistenkelige signaturforespørsler før du trykker “godkjenn” i lommeboken, ikke bare etterpå. Hvis porteføljen din vokser forbi det du personlig er komfortabel med å tape, eller hvis du forvalter midler for andre, er det verdt å vurdere et slikt verktøy som et tillegg til, ikke en erstatning for, skriptet du nettopp bygde.
Vedlikehold og logging over tid
En porteføljevarsling som ingen ser på i seks måneder, er ikke stort bedre enn intet verktøy i det hele tatt. Skriv all aktivitet, ikke bare avvik, til en loggfil med tidsstempel, slik at du kan slå opp historikk hvis noe virker rart uken etter at det skjedde. Bruk Pythons innebygde logging-modul fremfor å spre print()-setninger rundt i koden, siden det gir deg riktig nivåstyring og enkel rotasjon av loggfiler.
import logging
from logging.handlers import RotatingFileHandler
handler = RotatingFileHandler("data/monitor.log", maxBytes=2_000_000, backupCount=5)
logging.basicConfig(level=logging.INFO, handlers=[handler],
format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("portefolje")
Sett også et kalenderpåminnelse hver tredje måned for å gå gjennom konfigurasjonsfilen, oppdatere listen over tiltrodde kontrakter, og bekrefte at Etherscan-nøkkelen fortsatt fungerer som forventet. API-er endrer seg over tid, selv gratis-tjenester som mempool.space har historisk justert svarformater, så en rask manuell sjekk med jevne mellomrom er billig forsikring mot at skriptet slutter å virke stille i bakgrunnen.
Det komplette prosjektet
Sett sammen blir prosjektet fem små filer som til sammen utgjør et fungerende, kjørbart varslingssystem. Ingen av delene er avhengig av en ekstern server utover de gratis API-ene vi allerede har brukt.
portefolje-varsling/
├── venv/
├── data/
│ └── snapshots.db
├── .env
├── config.json
├── chains.py # get_evm_balance, get_btc_balance
├── storage.py # init_db, save_snapshot, last_snapshot
├── rules.py # check_anomaly, get_recent_approvals
├── alerts.py # send_alert
└── main.py # run_check, hovedløkken
Last miljøvariablene fra .env ved oppstart (for eksempel med python-dotenv, eller ved å eksportere dem i systemd-enheten), koble sammen filene som vist i stegene over, og kjør python main.py manuelt én gang for å bekrefte at alt fungerer, før du legger inn timeren og lar systemet gå av seg selv.
Bruk denne sjekklisten før du stoler på at systemet kjører i produksjon uten tilsyn:
- Alle adresser og xpub-er i
config.jsoner hentet fra riktig lommebok, ikke fra en gammel eksport. .envhar rettigheter satt til kun eier, og er lagt til i.gitignore.- Både saldotesten og godkjenningstesten fra forrige steg er kjørt og bekreftet manuelt.
- Systemd-timeren (eller cron-jobben) er aktivert og har kjørt minst én gang uten feil i loggen.
- Terskelverdien er justert til et realistisk nivå etter testingen, ikke stående igjen på 0,5 prosent.
- Du vet hvor loggfilen ligger, og har sjekket at rotasjonen faktisk virker etter noen dager.
Ofte stilte spørsmål
Hvor mye koster det å kjøre dette døgnet rundt?
Praktisk talt ingenting. Etherscans gratisnivå og mempool.space koster ikke noe, og en liten VPS eller en Raspberry Pi du allerede eier holder mer enn nok til å sjekke saldo hvert 5. minutt. Den eneste reelle kostnaden er tiden du bruker på oppsettet og den sporadiske vedlikeholdssjekken hver tredje måned.
Kan jeg overvåke en maskinvarelommebok uten å eksponere seed-frasen?
Ja. Verktøyet leser kun offentlig saldo- og transaksjonsdata knyttet til adressen eller xpub-en din. Ingen privat nøkkel eller seed-frase går noensinne inn i skriptet, siden alt som trengs for å lese saldo allerede er offentlig informasjon på blokkjeden.
Fungerer dette for Solana eller andre ikke-EVM-kjeder utover Bitcoin?
Ikke uten videre. Etherscan API V2 dekker kun EVM-kompatible kjeder, og Solana krever et helt annet API med en annen datamodell. Du kan utvide prosjektet med en tredje modul senere ved å følge samme mønster som Bitcoin-modulen, men den konkrete implementasjonen ligger utenfor denne artikkelen.
Hvor raskt oppdager verktøyet et tyveri?
Det avhenger av hvor ofte du kjører sjekken. Hvert 5. minutt er et fornuftig utgangspunkt, det balanserer rask deteksjon mot å ikke sprenge den gratis API-kvoten din. Vil du ha raskere deteksjon, kan du sette systemd-timeren til hvert minutt, så lenge antall adresser og kjeder du overvåker holder deg godt under de 100 000 daglige kallene.
Er det trygt å lagre saldohistorikk lokalt i en SQLite-fil?
Ja, siden dataene kun er offentlig informasjon som allerede ligger åpent på blokkjeden. Filen inneholder ingen hemmeligheter, men det er likevel god praksis å begrense lesetilgang til egen bruker og ta jevnlig sikkerhetskopi av databasen sammen med resten av serveren din.
Bør jeg bruke samme Etherscan-nøkkel til overvåking som til andre prosjekter?
Nei. Opprett en egen nøkkel kun til dette formålet. Da kan du enkelt slette eller rotere den uten å påvirke andre verktøy du kjører, og du unngår at en feil i et helt annet prosjekt tapper kvoten som varslingsverktøyet ditt trenger.
Hva gjør jeg hvis verktøyet sender for mange falske alarmer?
Juster alert_threshold_pct i konfigurasjonen gradvis oppover til varslene matcher hvor ofte du faktisk flytter midler selv, og legg gjerne til et unntak for dine egne kjente mottakeradresser. Se også på loggfilen fra vedlikeholdsseksjonen for å finne mønsteret bak de falske alarmene før du justerer terskelen blindt.
Kan dette erstatte et kommersielt overvåkingsverktøy helt?
For en privat portefølje dekker det mesteparten av behovet. For større summer eller forvaltning på vegne av andre bør du legge til et signaturnivå-verktøy som et supplement, siden et kjede-basert skript aldri kan se en signatur før den er sendt inn. Tenk på dette prosjektet som grunnmuren, ikke som hele bygningen.
Må jeg kunne Python fra før for å følge denne guiden?
Du bør kunne lese enkel kode og vite hva en funksjon og en variabel er, men du trenger ikke være erfaren utvikler. Alle kodeblokkene i artikkelen er skrevet til å kopieres direkte inn i egne filer, og feilsøkingstabellen dekker de vanligste knutene du kan gå deg fast i underveis.




