Einige Tricks von Betrugs-Token und wie man sie erkennt
In diesem Tutorial analysieren wir einen Betrugs-Token (opens in a new tab), um einige der Tricks von Betrügern zu untersuchen und zu sehen, wie sie diese implementieren. Am Ende des Tutorials werden Sie ein umfassenderes Verständnis von ERC-20-Token-Verträgen und deren Fähigkeiten haben und wissen, warum Skepsis geboten ist. Anschließend betrachten wir die von diesem Betrugs-Token ausgegebenen Ereignisse und zeigen, wie wir automatisch erkennen können, dass er nicht legitim ist.
Betrugs-Token – was sie sind, warum Leute sie erstellen und wie man sie vermeidet
Eine der häufigsten Anwendungen für Ethereum ist die Erstellung eines handelbaren Tokens durch eine Gruppe, gewissermaßen als eigene Währung. Wo es jedoch legitime Anwendungsfälle gibt, die Wert schaffen, gibt es auch Kriminelle, die versuchen, diesen Wert für sich selbst zu stehlen.
Sie können mehr über dieses Thema aus der Nutzerperspektive an anderer Stelle auf ethereum.org lesen. Dieses Tutorial konzentriert sich auf die Analyse eines Betrugs-Tokens, um zu sehen, wie es gemacht wird und wie es erkannt werden kann.
Woher weiß ich, dass wARB ein Betrug ist?
Der Token, den wir analysieren, ist wARB (opens in a new tab), der vorgibt, dem legitimen ARB-Token (opens in a new tab) gleichwertig zu sein.
Der einfachste Weg, um herauszufinden, welcher der legitime Token ist, besteht darin, sich die Ursprungsorganisation Arbitrum (opens in a new tab) anzusehen. Die legitimen Adressen sind in ihrer Dokumentation (opens in a new tab) angegeben.
Warum ist der Quellcode verfügbar?
Normalerweise würden wir erwarten, dass Leute, die versuchen, andere zu betrügen, geheimnisvoll tun, und tatsächlich ist der Code vieler Betrugs-Token nicht verfügbar (zum Beispiel dieser (opens in a new tab) und dieser (opens in a new tab)).
Da legitime Token jedoch in der Regel ihren Quellcode veröffentlichen, tun die Autoren von Betrugs-Token manchmal dasselbe, um legitim zu erscheinen. wARB (opens in a new tab) ist einer dieser Token mit verfügbarem Quellcode, was es einfacher macht, ihn zu verstehen.
Während die Bereitsteller von Verträgen wählen können, ob sie den Quellcode veröffentlichen oder nicht, können sie nicht den falschen Quellcode veröffentlichen. Die Blocksuchmaschine kompiliert den bereitgestellten Quellcode unabhängig, und wenn sie nicht genau denselben Bytecode erhält, lehnt sie diesen Quellcode ab. Sie können mehr darüber auf der Etherscan-Website lesen (opens in a new tab).
Vergleich mit legitimen ERC-20-Token
Wir werden diesen Token mit legitimen ERC-20-Token vergleichen. Wenn Sie nicht damit vertraut sind, wie legitime ERC-20-Token typischerweise geschrieben werden, sehen Sie sich dieses Tutorial an.
Konstanten für privilegierte Adressen
Verträge benötigen manchmal privilegierte Adressen. Verträge, die für eine langfristige Nutzung ausgelegt sind, ermöglichen es einer privilegierten Adresse, diese Adressen zu ändern, beispielsweise um die Nutzung eines neuen Mehrfachsignatur-Vertrags zu ermöglichen. Es gibt mehrere Möglichkeiten, dies zu tun.
Der HOP-Token-Vertrag (opens in a new tab) verwendet das Ownable (opens in a new tab)-Muster. Die privilegierte Adresse wird im Speicher in einem Feld namens _owner aufbewahrt (siehe die dritte Datei, Ownable.sol).
1abstract contract Ownable is Context {2 address private _owner;3 .4 .5 .6}Der ARB-Token-Vertrag (opens in a new tab) hat nicht direkt eine privilegierte Adresse. Er benötigt jedoch auch keine. Er befindet sich hinter einem proxy (opens in a new tab) unter der Adresse 0xb50721bcf8d664c30412cfbc6cf7a15145234ad1 (opens in a new tab). Dieser Vertrag hat eine privilegierte Adresse (siehe die vierte Datei, ERC1967Upgrade.sol), die für Upgrades verwendet werden kann.
1 /* *2 * @dev Speichert eine neue Adresse im EIP1967-Admin-Slot. */3 456 function _setAdmin(address newAdmin) private {7 require(newAdmin != address(0), "ERC1967: new admin is the zero address");8 StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;9 }Alle anzeigenIm Gegensatz dazu hat der wARB-Vertrag einen fest codierten contract_owner.
1contract WrappedArbitrum is Context, IERC20 {2 .3 .4 .5 address deployer = 0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1;6 address public contract_owner = 0xb40dE7b1beE84Ff2dc22B70a049A07A13a411A33;7 .8 .9 .10}Alle anzeigenDieser Vertragsbesitzer (opens in a new tab) ist kein Vertrag, der zu verschiedenen Zeiten von verschiedenen Konten kontrolliert werden könnte, sondern ein Extern verwaltetes Konto. Dies bedeutet, dass es wahrscheinlich eher für die kurzfristige Nutzung durch eine Einzelperson konzipiert ist, als als langfristige Lösung zur Kontrolle eines ERC-20, der wertvoll bleiben soll.
Und tatsächlich, wenn wir in Etherscan nachsehen, sehen wir, dass der Betrüger diesen Vertrag nur 12 Stunden lang genutzt hat (erste Transaktion (opens in a new tab) bis letzte Transaktion (opens in a new tab)) am 19. Mai 2023.
Die gefälschte _transfer-Funktion
Es ist Standard, dass tatsächliche Übertragungen über eine interne _transfer-Funktion erfolgen.
In wARB sieht diese Funktion fast legitim aus:
1 function _transfer(address sender, address recipient, uint256 amount) internal virtual{2 require(sender != address(0), "ERC20: transfer from the zero address");3 require(recipient != address(0), "ERC20: transfer to the zero address");45 _beforeTokenTransfer(sender, recipient, amount);67 _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");8 _balances[recipient] = _balances[recipient].add(amount);9 if (sender == contract_owner){10 sender = deployer;11 }12 emit Transfer(sender, recipient, amount);13 }Alle anzeigenDer verdächtige Teil ist:
1 if (sender == contract_owner){2 sender = deployer;3 }4 emit Transfer(sender, recipient, amount);Wenn der Vertragsbesitzer Token sendet, warum zeigt das Transfer-Ereignis dann an, dass sie vom deployer kommen?
Es gibt jedoch ein wichtigeres Problem. Wer ruft diese _transfer-Funktion auf? Sie kann nicht von außen aufgerufen werden, da sie als internal markiert ist. Und der uns vorliegende Code enthält keine Aufrufe von _transfer. Offensichtlich ist sie hier als Täuschung gedacht.
1 function transfer(address recipient, uint256 amount) public virtual override returns (bool) {2 _f_(_msgSender(), recipient, amount);3 return true;4 }56 function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {7 _f_(sender, recipient, amount);8 _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));9 return true;10 }Alle anzeigenWenn wir uns die Funktionen ansehen, die zur Übertragung von Token aufgerufen werden, transfer und transferFrom, sehen wir, dass sie eine völlig andere Funktion aufrufen, nämlich _f_.
Die echte _f_-Funktion
1 function _f_(address sender, address recipient, uint256 amount) internal _mod_(sender,recipient,amount) virtual {2 require(sender != address(0), "ERC20: transfer from the zero address");3 require(recipient != address(0), "ERC20: transfer to the zero address");45 _beforeTokenTransfer(sender, recipient, amount);67 _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");8 _balances[recipient] = _balances[recipient].add(amount);9 if (sender == contract_owner){1011 sender = deployer;12 }13 emit Transfer(sender, recipient, amount);14 }Alle anzeigenEs gibt zwei potenzielle Warnsignale in dieser Funktion.
-
Die Verwendung des Funktionsmodifikators (opens in a new tab)
_mod_. Wenn wir uns jedoch den Quellcode ansehen, stellen wir fest, dass_mod_eigentlich harmlos ist.1modifier _mod_(address sender, address recipient, uint256 amount){2 _;3}
12- Dasselbe Problem, das wir in `_transfer` gesehen haben: Wenn der `contract_owner` Token sendet, scheinen sie vom `deployer` zu kommen.34### Die gefälschte Ereignisfunktion `dropNewTokens` \{#the-fake-events-function-dropNewTokens\}56Nun kommen wir zu etwas, das wie ein tatsächlicher Betrug aussieht. Ich habe die Funktion zur besseren Lesbarkeit etwas bearbeitet, aber sie ist funktional äquivalent.78```solidity9function dropNewTokens(address uPool,10 address[] memory eReceiver,11 uint256[] memory eAmounts) public auth()Alle anzeigenDiese Funktion hat den Modifikator auth(), was bedeutet, dass sie nur vom Vertragsbesitzer aufgerufen werden kann.
1modifier auth() {2 require(msg.sender == contract_owner, "Not allowed to interact");3 _;4}Diese Einschränkung ist absolut sinnvoll, da wir nicht wollen, dass beliebige Konten Token verteilen. Der Rest der Funktion ist jedoch verdächtig.
1{2 for (uint256 i = 0; i < eReceiver.length; i++) {3 emit Transfer(uPool, eReceiver[i], eAmounts[i]);4 }5}Eine Funktion, um von einem Pool-Konto an ein Array von Empfängern ein Array von Beträgen zu übertragen, ist absolut sinnvoll. Es gibt viele Anwendungsfälle, in denen man Token von einer einzigen Quelle an mehrere Ziele verteilen möchte, wie z. B. Gehaltsabrechnungen, Airdrops usw. Es ist (in Bezug auf Gas) günstiger, dies in einer einzigen Transaktion zu tun, anstatt mehrere Transaktionen auszustellen oder sogar den ERC-20 mehrfach von einem anderen Vertrag aus als Teil derselben Transaktion aufzurufen.
dropNewTokens tut dies jedoch nicht. Es gibt Transfer-Ereignisse (opens in a new tab) aus, überträgt aber tatsächlich keine Token. Es gibt keinen legitimen Grund, Off-Chain-Anwendungen zu verwirren, indem man ihnen von einer Übertragung erzählt, die nicht wirklich stattgefunden hat.
Die verbrennende Approve-Funktion
ERC-20-Verträge sollen eine approve-Funktion für Freigaben haben, und tatsächlich hat unser Betrugs-Token eine solche Funktion, und sie ist sogar korrekt. Da Solidity jedoch von C abstammt, wird zwischen Groß- und Kleinschreibung unterschieden. "Approve" und "approve" sind unterschiedliche Zeichenfolgen.
Außerdem hat die Funktionalität nichts mit approve zu tun.
1 function Approve(2 address[] memory holders)Diese Funktion wird mit einem Array von Adressen für Inhaber des Tokens aufgerufen.
1 public approver() {Der Modifikator approver() stellt sicher, dass nur der contract_owner diese Funktion aufrufen darf (siehe unten).
1 for (uint256 i = 0; i < holders.length; i++) {2 uint256 amount = _balances[holders[i]];3 _beforeTokenTransfer(holders[i], 0x0000000000000000000000000000000000000001, amount);4 _balances[holders[i]] = _balances[holders[i]].sub(amount,5 "ERC20: burn amount exceeds balance");6 _balances[0x0000000000000000000000000000000000000001] =7 _balances[0x0000000000000000000000000000000000000001].add(amount);8 }9 }10Alle anzeigenFür jede Inhaberadresse verschiebt die Funktion das gesamte Guthaben des Inhabers an die Adresse 0x00...01, wodurch es effektiv verbrannt wird (das eigentliche burn im Standard ändert auch das Gesamtangebot und überträgt die Token an 0x00...00). Dies bedeutet, dass der contract_owner die Vermögenswerte jedes Benutzers entfernen kann. Das scheint keine Funktion zu sein, die man in einem Governance-Token haben möchte.
Probleme mit der Codequalität
Diese Probleme mit der Codequalität beweisen nicht, dass dieser Code ein Betrug ist, aber sie lassen ihn verdächtig erscheinen. Organisierte Unternehmen wie Arbitrum veröffentlichen normalerweise keinen so schlechten Code.
Die mount-Funktion
Obwohl es in dem Standard (opens in a new tab) nicht spezifiziert ist, wird die Funktion, die neue Token erstellt, im Allgemeinen mint (Prägen) genannt.
Wenn wir uns den wARB-Konstruktor ansehen, sehen wir, dass die Präge-Funktion aus irgendeinem Grund in mount umbenannt wurde und fünfmal mit einem Fünftel des anfänglichen Angebots aufgerufen wird, anstatt aus Effizienzgründen einmal für den gesamten Betrag.
1 constructor () public {23 _name = "Wrapped Arbitrum";4 _symbol = "wARB";5 _decimals = 18;6 uint256 initialSupply = 1000000000000;78 mount(deployer, initialSupply*(10**18)/5);9 mount(deployer, initialSupply*(10**18)/5);10 mount(deployer, initialSupply*(10**18)/5);11 mount(deployer, initialSupply*(10**18)/5);12 mount(deployer, initialSupply*(10**18)/5);13 }Alle anzeigenDie mount-Funktion selbst ist ebenfalls verdächtig.
1 function mount(address account, uint256 amount) public {2 require(msg.sender == contract_owner, "ERC20: mint to the zero address");Wenn wir uns das require ansehen, sehen wir, dass nur der Vertragsbesitzer prägen darf. Das ist legitim. Aber die Fehlermeldung sollte only owner is allowed to mint oder etwas Ähnliches lauten. Stattdessen ist es das irrelevante ERC20: mint to the zero address. Der korrekte Test für das Prägen an die Null-Adresse ist require(account != address(0), "<error message>"), was der Vertrag nie zu überprüfen bemüht ist.
1 _totalSupply = _totalSupply.add(amount);2 _balances[contract_owner] = _balances[contract_owner].add(amount);3 emit Transfer(address(0), account, amount);4 }Es gibt zwei weitere verdächtige Fakten, die direkt mit dem Prägen zusammenhängen:
-
Es gibt einen
account-Parameter, der vermutlich das Konto ist, das den geprägten Betrag erhalten soll. Aber das Guthaben, das sich erhöht, ist tatsächlich das descontract_owner. -
Während das erhöhte Guthaben dem
contract_ownergehört, zeigt das ausgegebene Ereignis eine Übertragung anaccount.
Warum sowohl auth als auch approver? Warum das mod, das nichts tut?
Dieser Vertrag enthält drei Modifikatoren: _mod_, auth und approver.
1 modifier _mod_(address sender, address recipient, uint256 amount){2 _;3 }_mod_ nimmt drei Parameter entgegen und macht nichts damit. Warum gibt es ihn?
1 modifier auth() {2 require(msg.sender == contract_owner, "Not allowed to interact");3 _;4 }56 modifier approver() {7 require(msg.sender == contract_owner, "Not allowed to interact");8 _;9 }Alle anzeigenauth und approver machen mehr Sinn, da sie überprüfen, ob der Vertrag vom contract_owner aufgerufen wurde. Wir würden erwarten, dass bestimmte privilegierte Aktionen, wie das Prägen, auf dieses Konto beschränkt sind. Was ist jedoch der Sinn von zwei separaten Funktionen, die genau dasselbe tun?
Was können wir automatisch erkennen?
Wir können durch einen Blick auf Etherscan erkennen, dass wARB ein Betrugs-Token ist. Dies ist jedoch eine zentralisierte Lösung. Theoretisch könnte Etherscan unterwandert oder gehackt werden. Es ist besser, unabhängig herausfinden zu können, ob ein Token legitim ist oder nicht.
Es gibt einige Tricks, mit denen wir erkennen können, dass ein ERC-20-Token verdächtig ist (entweder ein Betrug oder sehr schlecht geschrieben), indem wir uns die von ihm ausgegebenen Ereignisse ansehen.
Verdächtige Approval-Ereignisse
Approval-Ereignisse (opens in a new tab) sollten nur bei einer direkten Anfrage auftreten (im Gegensatz zu Transfer-Ereignissen (opens in a new tab), die als Ergebnis einer Freigabe auftreten können). Siehe die Solidity-Dokumentation (opens in a new tab) für eine detaillierte Erklärung dieses Problems und warum die Anfragen direkt sein müssen, anstatt durch einen Vertrag vermittelt zu werden.
Dies bedeutet, dass Approval-Ereignisse, die Ausgaben von einem Extern verwalteten Konto genehmigen, aus Transaktionen stammen müssen, die von diesem Konto ausgehen und deren Ziel der ERC-20-Vertrag ist. Jede andere Art der Genehmigung von einem Extern verwalteten Konto ist verdächtig.
Hier ist ein Programm, das diese Art von Ereignis identifiziert (opens in a new tab), unter Verwendung von viem (opens in a new tab) und TypeScript (opens in a new tab), einer JavaScript-Variante mit Typsicherheit. Um es auszuführen:
- Kopieren Sie
.env.examplenach.env. - Bearbeiten Sie
.env, um die URL zu einem Ethereum-Mainnet-Blockchain-Knoten bereitzustellen. - Führen Sie
pnpm installaus, um die erforderlichen Pakete zu installieren. - Führen Sie
pnpm susApprovalaus, um nach verdächtigen Genehmigungen zu suchen.
Hier ist eine zeilenweise Erklärung:
1import {2 Address,3 TransactionReceipt,4 createPublicClient,5 http,6 parseAbiItem,7} from "viem"8import { mainnet } from "viem/chains"Importieren Sie Typdefinitionen, Funktionen und die Chain-Definition aus viem.
1import { config } from "dotenv"2config()Lesen Sie .env, um die URL zu erhalten.
1const client = createPublicClient({2 chain: mainnet,3 transport: http(process.env.URL),4})Erstellen Sie einen Viem-Client. Wir müssen nur von der Blockchain lesen, daher benötigt dieser Client keinen Private-Key.
1const testedAddress = "0xb047c8032b99841713b8e3872f06cf32beb27b82"2const fromBlock = 16859812n3const toBlock = 16873372nDie Adresse des verdächtigen ERC-20-Vertrags und die Blöcke, in denen wir nach Ereignissen suchen werden. Anbieter von Blockchain-Knoten schränken typischerweise unsere Fähigkeit ein, Ereignisse zu lesen, da die Bandbreite teuer werden kann. Glücklicherweise war wARB für einen Zeitraum von achtzehn Stunden nicht in Gebrauch, sodass wir nach allen Ereignissen suchen können (es gab insgesamt nur 13).
1const approvalEvents = await client.getLogs({2 address: testedAddress,3 fromBlock,4 toBlock,5 event: parseAbiItem(6 "event Approval(address indexed _owner, address indexed _spender, uint256 _value)"7 ),8})Dies ist der Weg, um Viem nach Ereignisinformationen zu fragen. Wenn wir ihm die genaue Ereignissignatur einschließlich der Feldnamen zur Verfügung stellen, parst es das Ereignis für uns.
1const isContract = async (addr: Address): boolean =>2 await client.getBytecode({ address: addr })Unser Algorithmus ist nur auf Extern verwaltete Konten anwendbar. Wenn von client.getBytecode ein Bytecode zurückgegeben wird, bedeutet dies, dass es sich um einen Vertrag handelt und wir ihn einfach überspringen sollten.
Wenn Sie TypeScript noch nicht verwendet haben, sieht die Funktionsdefinition möglicherweise etwas seltsam aus. Wir sagen ihm nicht nur, dass der erste (und einzige) Parameter addr heißt, sondern auch, dass er vom Typ Address ist. Ebenso teilt der Teil : boolean TypeScript mit, dass der Rückgabewert der Funktion ein Boolean ist.
1const getEventTxn = async (ev: Event): TransactionReceipt =>2 await client.getTransactionReceipt({ hash: ev.transactionHash })Diese Funktion ruft den Transaktionsbeleg aus einem Ereignis ab. Wir benötigen den Beleg, um sicherzustellen, dass wir wissen, was das Ziel der Transaktion war.
1const suspiciousApprovalEvent = async (ev : Event) : (Event | null) => {Dies ist die wichtigste Funktion, diejenige, die tatsächlich entscheidet, ob ein Ereignis verdächtig ist oder nicht. Der Rückgabetyp (Event | null) teilt TypeScript mit, dass diese Funktion entweder ein Event oder null zurückgeben kann. Wir geben null zurück, wenn das Ereignis nicht verdächtig ist.
1const owner = ev.args._ownerViem hat die Feldnamen, also hat es das Ereignis für uns geparst. _owner ist der Besitzer der auszugebenden Token.
1// Genehmigungen durch Verträge sind nicht verdächtig2if (await isContract(owner)) return nullWenn der Besitzer ein Vertrag ist, gehen Sie davon aus, dass diese Genehmigung nicht verdächtig ist. Um zu überprüfen, ob die Genehmigung eines Vertrags verdächtig ist oder nicht, müssten wir die vollständige Ausführung der Transaktion verfolgen, um zu sehen, ob sie jemals zum Besitzervertrag gelangt ist und ob dieser Vertrag den ERC-20-Vertrag direkt aufgerufen hat. Das ist weitaus ressourcenintensiver, als wir es gerne tun würden.
1const txn = await getEventTxn(ev)Wenn die Genehmigung von einem Extern verwalteten Konto stammt, rufen Sie die Transaktion ab, die sie verursacht hat.
1// Die Genehmigung ist verdächtig, wenn sie von einem EOA-Besitzer stammt, der nicht das `from` der Transaktion ist2if (owner.toLowerCase() != txn.from.toLowerCase()) return evWir können nicht einfach auf Zeichenfolgengleichheit prüfen, da Adressen hexadezimal sind und daher Buchstaben enthalten. Manchmal, zum Beispiel in txn.from, sind diese Buchstaben alle kleingeschrieben. In anderen Fällen, wie bei ev.args._owner, ist die Adresse in gemischter Groß-/Kleinschreibung zur Fehlererkennung (opens in a new tab).
Aber wenn die Transaktion nicht vom Besitzer stammt und dieser Besitzer extern verwaltet wird, dann haben wir eine verdächtige Transaktion.
1// Es ist auch verdächtig, wenn das Transaktionsziel nicht der ERC-20-Vertrag ist, den wir2// untersuchen3if (txn.to.toLowerCase() != testedAddress) return evEbenso ist es verdächtig, wenn die to-Adresse der Transaktion, der erste aufgerufene Vertrag, nicht der untersuchte ERC-20-Vertrag ist.
1 // Wenn kein Grund zum Verdacht besteht, gib null zurück.2 return null3}Wenn keine der Bedingungen zutrifft, ist das Approval-Ereignis nicht verdächtig.
1const testPromises = approvalEvents.map((ev) => suspiciousApprovalEvent(ev))2const testResults = (await Promise.all(testPromises)).filter((x) => x != null)34console.log(testResults)Eine async-Funktion (opens in a new tab) gibt ein Promise-Objekt zurück. Mit der üblichen Syntax await x() warten wir darauf, dass dieses Promise erfüllt wird, bevor wir mit der Verarbeitung fortfahren. Dies ist einfach zu programmieren und nachzuvollziehen, aber es ist auch ineffizient. Während wir darauf warten, dass das Promise für ein bestimmtes Ereignis erfüllt wird, können wir bereits mit der Arbeit am nächsten Ereignis beginnen.
Hier verwenden wir map (opens in a new tab), um ein Array von Promise-Objekten zu erstellen. Dann verwenden wir Promise.all (opens in a new tab), um darauf zu warten, dass alle diese Promises aufgelöst werden. Anschließend filter (opens in a new tab)n wir diese Ergebnisse, um die nicht verdächtigen Ereignisse zu entfernen.
Verdächtige Transfer-Ereignisse
Eine weitere mögliche Methode zur Identifizierung von Betrugs-Token besteht darin, zu prüfen, ob sie verdächtige Übertragungen aufweisen. Zum Beispiel Übertragungen von Konten, die nicht so viele Token haben. Sie können sehen, wie man diesen Test implementiert (opens in a new tab), aber wARB hat dieses Problem nicht.
Fazit
Die automatisierte Erkennung von ERC-20-Betrügereien leidet unter falsch-negativen Ergebnissen (opens in a new tab), da ein Betrug einen völlig normalen ERC-20-Token-Vertrag verwenden kann, der einfach nichts Reales darstellt. Daher sollten Sie immer versuchen, die Token-Adresse aus einer vertrauenswürdigen Quelle zu beziehen.
Die automatisierte Erkennung kann in bestimmten Fällen helfen, wie z. B. bei DeFi-Komponenten, bei denen es viele Token gibt und diese automatisch verarbeitet werden müssen. Aber wie immer gilt Caveat emptor (opens in a new tab) (Käufer, sei wachsam): Recherchieren Sie selbst und ermutigen Sie Ihre Benutzer, dasselbe zu tun.
Weitere meiner Arbeiten finden Sie hier (opens in a new tab).
Letzte Aktualisierung der Seite: 25. Februar 2026