Ethereum Erc-20 Ödeme Takibi

Faruk Terzioğlu
Blokzincir
Published in
10 min readSep 4, 2019

Minimalist bir Ethereum Erc-20 Token Takipçisi

Ethereum blockchain’ini takip ederek, belirlediğimiz adreslere belirli Erc-20 token’larından gelen transferleri dinleyecek ve bunları bir RabbitMq kuyruğuna ekleyeceğiz.

İlk olarak örnek bir Erc-20 token’ını dinleyecek ve testlerimizi simülasyon blockchain’i olarak kullanacağımız Ganache üzerinde çalıştıracağız. Daha sonra da bir full Ethereum node’una bağlanarak Tether transferlerini dinleyecek ve bizim adreslerimize gelen transferleri kuyruğa aktaracağız.

Uygulamayı .Net Core ile geliştireceğim ve blockchain ile konuşmak için Nethereum kütüphanesini kullanacağım. Bir unit test projesi içerisinde Nethereum kullanarak yeni smart contract deploy etmek, bakiye sorgulamak, yeni transfer göndermek ve transfer sonucu üretilen event’leri dinlemeye dair örnekler yapacağım. Daha sonra burada yazdığımız kodları örnek alarak asıl uygulamayı geliştireceğim.

Geliştirilen uygulamayı Docker üzerinde çalıştırabilirsiniz ve tüm modlarını farklı ortam değişkenleri göndererek deneyebilirsiniz. Örnek olarak, full node üzerinden (node ip kaynağı) tüm Tether transferlerini 8363860. blokdan itibaren şu şekilde dinleyebiliriz;

$ docker run \
-e NODEURL=http://52.208.46.161:8546 \
-e CONTRACTADDRESSES:0=0xdAC17F958D2ee523a2206206994597C13D831ec7 \
-e LASTPROCESSEDBLOCK=8363860 \
erc20-tracker

Sadece belli bir adresi takip etmek için ekleyeceğimiz ortam değişkeni;

-e TRACKEDADDRESSES:0=0xde769dda17eD7d2178a5646D279382d0CaEC8079

Belli bir HD wallet seed’ine ait ilk 100 adresi takip etmek için;

$ SEED="carry fix ... habit"
$ docker run ... -e SEED=$SEED -e HDADDRESSCOUNT=100 ...

İkinci bir Erc20 token’ı da dinlemek için de ek olarak yeni smart contract’ın adresini ekleyelim;

-e CONTRACTADDRESSES:1=0xB8c77482e45F1F44dE1745F52C74426C631bDD52

Tüm seçenekleri şurada görebiliriz;

$ docker run \
-e NodeUrl=http://localhost:7545 \
-e CONTRACTADDRESSES:0=0xda... \
-e CONTRACTADDRESSES:1=0xB8... \
-e TRACKEDADDRESSES:0=0xde7... \
-e SEED="carry fix ... habit" \
-e HDADDRESSCOUNT=100 \
-e RABBITMQHOSTNAME=localhost \
-e RABBITMQUSERNAME=guest \
-e RABBITMQPASSWORD=guest \
-e RABBITMQEXCHANGENAME=Erc20Transactions \
erc20-tracker

Uygulamanın kodlarına şuradan erişebilirsiniz;

Simülasyon Ethereum Node : Ganache

Örnek bir Erc20 smart contract’ını deploy etmek, örnek transferler gönderip ilgili event’leri dinlemek üzere bir Ethereum node’una ihtiyacımız var. Bunları mainnet üzerinde denemek maliyetli olacağı ve gerçek Ether gerektirdiği için denemelerimizi simülasyon Ethereum ağı Ganache ile yapacağız.

Ganache’ı şuradan indirip çalıştırabilirsiniz;

Ganache

Nethereum

Ethereum Node’u ile haberleşmek için Nethereum kütüphanesini kullanacağız. İlk etapta örnekleri bir unit test projesinde yapacağım, daha sonrasında buradaki kodlar ile uygulamayı geliştireceğim. Unit test projesine aşağıdaki paketi yükleyelim;

$ dotnet add package Nethereum.Web3

Kullanacağımız namespace’ler;

using Nethereum.Web3;
using Nethereum.Web3.Accounts;

Aşağıda vereceğim kod örneğinde, Ethereum node’u ile haberleşebilmek üzere Web3 API’ını kullanacağımız için Web3 sınıfından bir nesne oluşturuyoruz. Node üzerinde yapacağımız işlemler (contract oluşturmak, token transferi gibi) belli miktarda GAS gerektirdiği için, içinde yeterli miktarda Ether‘in olduğu bir hesap kullanmalıyız. Bu hesabı kullanmak için de Account sınıfından bir nesne alıyoruz ve constructor parametresi olarak da GAS’ın alınacağı hesabın private key’ini kullanıyoruz. Hesap olarak Ganache’daki ilk hesabı kullanabiliriz. Private key’e erişmek için Acocunt sekmesinde aşağıda gösterilen yere tıklayalım;

Private key’i kopyalayalım;

Ganache varsayılan olarak şu adreste çalışmakta: http://localhost:7545. Farklı bir port ayarlamak isterseniz Ganache’ın ayarlarından yapabilirsiniz.

Bu bilgiler ile node’a rpc isteği göndermek için gerekli kodları yazalım;

Ganache’dan kopyaladığımız private key ile bir Account oluşturduk. Bu account ve url bilgisi ile yeni bir Web3 nesnesi oluşturduk. Artık Ethereum ile haberleşmek için gerekli olan Web3 nesnesine sahibiz.

İlk olarak örnek bir Erc20 smart contract’ını deploy edeceğiz. Bunun için bir Erc20 smart contract’ını derlemeli ve oluşan byte code’u Ethereum node’una göndermeliyiz. Yazıda daha çok smart contract’lar ile haberleşmeye değineceğim için bu kısmın detaylarına girmiyorum. Daha önce smart contract geliştirmediyseniz veya Erc-20'nin detaylarına hakim değilseniz ilk olarak bunu araştırabilirsiniz. Eğer Truffle kullanıyorsanız derleyerek byte code’una erişebilirsiniz. Örnek bir Erc-20 smart contract’ının byte code’u;

Yukarıda byte code’unu verdiğim derlenmiş Erc-20 smart contract’ını simülasyon blockchain’i Ganache üzerinde deploy etmek için Web3 API ile rpc isteği göndermeliyiz. Bunu C# ile yapmak için de Nethereum kütüphanesini kullanıyoruz. Smart contract’ları Nethereum ile deploy etmek için ‘ContractDeploymentMessage’ sınıfından bir nesne üretmeliyiz ve constructor parametresi olarak da derlenmiş byte code’u vermeliyiz. Nethereum kütüphanesindeki ContractDeploymentMessage sınıfını incelersek;

Deploy etmek istediğimiz Erc-20 smart contract’ını temsilen de ContractDeploymentMessage sınıfından türeyen bir ‘TokenDeployment’ sınıfı oluşturuyoruz ve toplam token sayısını belirtmek üzere ‘TotalSupply’ alanını tanımlıyoruz. Türettiğimiz sınıfa da deploy etmek istediğimiz smart contract’ın byte code’unu gönderiyoruz.

Bu sınıfı kullanarak yeni bir Erc20 token deploy edebiliriz. Bunu bir unit test metodu olarak şu şekilde yazabiliriz;

Toplam token adedini belirtmek üzere ‘TotalSupply’ değeri 10 olarak ayarlıyoruz. Yeni bir smart contract’ı (Erc-20) kendisi üzerinden deploy edeceğimiz Web3'ün GetContractDeploymentHandler metodunu çağırarak bir ‘handler’ üretiyoruz. Generic tip olarak da deploy edeceğimiz smart contract’ın tipi olan ‘TokenDeployment’ veriyoruz. Bu ‘deploymentHandler’ i kullanarak deployment isteğini göndereceğiz. Handler üzerinden ‘SendRequestAndWaitForReceiptAsync’ metodunu çağırarak Erc-20 smart contract’ımızı deploy etmiş oluyoruz. Handler’ın ‘SendRequestAsync’ metodu yerine ‘SendRequestAndWaitForReceiptAsync’ metodunu çağırarak ‘deployment transaction’ının bir blok içerisine yazılmasını bekliyoruz ve dönüş değeri üzerinden de yeni oluşturulan contract’ın adresine erişiyoruz.

Bu test metodunu çalıştırırsak Erc20 contract’ımız deploy edilmiş olacak. Ganache üzerinden ‘Transaction’ sekmesine geçersek oluşan ‘CONTRACT CREATION’ transaction’ını görebiliriz;

Buradan oluşan smart contract’ın adresini not alalım;

0xD267808D8fB2F2D6b02074DAC2F00036D5FcB3EE

Bu contract’ı deploy etmek için 541423 GAS harcanmış. Eğer tekrar ‘ACCOUNTS’ sekmesine geçerseniz bu miktarın, private key’ini kullandığımız adresten kesildiğini görebiliriz;

Erc-20 smart contract’ını bu adres ile deploy ettiğimiz için tüm bakiye (10) bu adrese tanımlandı. Bunun sebebini Tether contract’ın kodlarında görebiliriz;

https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code

Contract sahibinin bakiyesini ve herhangi birinin (2. account gibi) bakiyesini sorgulamak için aşağıdaki test metodunu yazalım;

Contract üzerinden read-only sorgulama yaptığımız için bu işlem sonucunda yeni bir transaction oluşmadı ve dolayısıyla GAS harcanmadı. Contract sorgulamaları için ‘GetContractQueryHandler’ ile bir handler (readonly) oluşturuyoruz. Generic tip olarak da sorgulamak istediğimiz mesaj tipini veriyoruz. Bakiye sorgulayacağımız için Erc20 smart contract’ının ‘balanceOf’ metodunu çağıracağız;

https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code

‘balanceOf’ metodunu temsilen aşağıdaki C# sınıfını tanımlayalım;

Yukarıdaki kodda BalanceOfFunction sınıfına ‘Function’ attribute’ü ekledik. Bu attribute içinde belirtmemiz gerekenler, smart contract’da hangi fonksiyona denk geldiği (balanceOf) ve dönüş değerinin tipinin ne olduğu (uint256).

‘QueryAsync’ metodu ile daha önce oluşturduğumuz contract adresine ‘.QueryAsync’ metodu ile bir sorgu gönderiyoruz. Generic tip olarak dönüş değerini ‘BigInteger’ olarak belirliyoruz. İlk parametresi sorgunun gönderileceği contract adresi, ikinci parametresi ise bakiyesini sorgulamak istediğimiz adresi tutan BalanceOfFunction nesnesi.

Test metodunda örnek olarak da 2. adresi sorguladık ve bakiyesinin 0 olduğunu görebiliriz. Birazdan bu adrese token göndereceğiz.

Token Transfer

Oluşturduğumuz Erc-20 üzerinden token transfer etmek için çağıracağımız smart contract fonksiyonu şu şekilde;

https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code

Bu metodu çağırmak için kullanacağımız C# sınıfı;

Burada dikkat ederseniz smart contract’daki ‘transfer’ metodunun ‘_to’ parametresini temsilen C# sınıfında (TransferFunction) ‘To’ isminde bir alan veriyoruz ve ‘Parameter’ attribute’ü ile de hangi parametreye denk geldiğini (_to), tipinin ne olduğunu (address) ve kaçıncı sıradaki parametre olduğunu (1) belirtiyoruz. Aynı şekilde TokenAmount (_value) alanını da tanımlamalıyız.

Transfer fonksiyonunu test etmek üzere aşağıdaki metodu yazalım;

Token transferleri smart contract state’inde değişiklik yaptığı için bunu bir Ethereum transaction’ı olarak göndermemiz gerekiyor. Bunun için gerekli olan handler’ı ‘GetContractTransactionHandler’ metodu ile alıyoruz.

1 adet token transferini temsilen aşağıdaki gibi nesne oluşturalım;

new TransferFunction() { To = receiverAddress, TokenAmount = 1 };

Bunu aynen contract deployment’ında olduğu gibi “.SendRequestAndWaitForReceiptAsync” metodu ile çağıracağız ve bir bloğa yazılmasını bekleyeceğiz. Daha sonrasında da bir önceki test metodunda yaptığımız gibi bakiyeleri kontrol edebiliriz. Eğer transfer sonucu hemen lazım değilse gönderdikten sonra blok oluşmasını beklememek için ‘SendRequestAsync’ metodunu da kullanabilirsiniz.

Bu işlem sonucunda oluşan Ethereum transaction’ını Ganache üzerinde görebiliriz;

Event: Transferred

Smart contract’larda state değiştiren metotlarda geri dönüş değeri alamıyoruz. Onun için bir işlemin gerçekleşip gerçekleşmediğini, contract’dan gönderilen event’ler ile anlayabiliriz. Erc-20 smart contract’larında bir transfer gerçekleştiği zaman ‘Transfer’ event’i gönderilmekte. Erc20 contract’ını incelersek;

https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code

Başarılı bir transfer sonucunda da bu event’in çağırıldığı yeri şurada görebiliriz;

https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code

Asıl geliştirmek istediğimiz ve belli bir contract’a gönderilen transferleri takip etmek istediğimiz ‘Erc-20 Tracker’ uygulamasında bu event’leri dinleyeceğiz.

Transfer event’ini temsilen aşağıdaki sınıfı oluşturalım;

Daha önce deploy ettiğimiz smart contract için oluşan tüm event’leri çekmek üzere aşağıdaki test metodunu oluşturalım;

Yukarıda yaptığımız, ‘.GetEvent()’ metodu ile ilgilendiğimiz contract için bir event handler tanımlıyoruz. Bu handler üzerinden de ‘.CreateFilterInput()’ metodunu çağırarak bir filtre oluşturuyoruz (daha sonra detaylandıracağız)
‘.GetAllChanges()’ metodu ile de oluşturduğumuz filtreye göre contract’da meydana gelen tüm event’leri alıyoruz. Şimdiye kadar sadece bir transfer yaptığımız için çıktı şu şekilde;

Gerçekleşen olay (token transferi) ile ilgili detaylara ‘transferEvent.Event’ üzerinden, olayın gerçekleştiği block ve transaction ile ilgili detaylara ‘transferEvent.Log’ üzerinden erişebiliriz.

Tüm event’leri değil de, sadece belli adreslere gönderilen veya belli adreslerden gönderilen veyahut sadece belli bloklar arasında gönderilen event’leri filtrelemek isterseniz aşağıdaki gibi filtreler kullanabilirsiniz;

‘.CreateFilterInput()’ metoduna birinci parametre olarak gönderen adresi yazarsanız sadece bu adresten gönderilenler gelecektir. Birinci parametreyi null bırakıp ikinci parametreye alıcı adreslerini yazarsanız sadece bu adreslere gönderilen transferler listenecektir. Veya bunların yerine, 0. ve 10. bloklar arasındaki event’leri filtrelemek isterseniz de ‘BlockParameter’ tipinde parametreler gönderebilirsiniz.

HD Wallet

HD (hierarchical deterministic) Wallet, yani hiyerarşik deterministik cüzdan, tek bir seed ile aynı private key’lerin determinist olarak tekrar tekrar üretilebildiği ve her bir private key’den hiyerarşik olarak yeni başka alt key’lerin üretilebildiği cüzdan türüdür. (Detayları: HD Wallet )

https://programmingblockchain.gitbook.io/programmingblockchain/key_generation/bip_32

Yapacağımız örnekte, sahip olduğumuz bir seed ile birkaç tane adres üreteceğiz ve bu adreslere gelen Erc-20 transferlerini takip edeceğiz.

HD Wallet’ı kullanmak üzere aşağıdaki paketi ekleyelim;

dotnet add package Nethereum.HdWallet

Kullanmamız gereken namespace;

using Nethereum.HdWallet;

Örnek olarak Ganache’dan alacağımız seed’i kullanabiliriz;

Bu seed’i kullanarak ‘Wallet’ sınıfından yeni bir nesne oluşturalım;

string words = "carry ... habit";
string password = "";
var wallet = new Wallet(words, password);

Seed’e ek olarak bir şifrede kullanabiliriz. Bu sayede eğer seed başkalarının eline geçerse şifreniz olmadan kullanamayacaktır. Fakat eğer şifreyi unutursanız seed’i kullanmanız mümkün olmaz ve size ait private key’leri üretemez, dolayısıyla da bu hesaplara gönderdiğiniz token’lara erişemezsiniz.

HD cüzdanlarda deterministik olarak hep ayrı key’lere erişebileceğimi söylemiştim. Seed’imiz ile ürettiğimiz ‘Wallet’ üzerinden 1. hesap bilgisine erişmek için;

var account = wallet.GetAccount(0);

Artık bu hesabın adresine ve private key’ine erişebiliriz;

Eğer Ganache’dan kontrol ederseniz aynı adresin (deterministik olarak) üretildiğini görebiliriz;

Bu bilgiler ile bizim seed’imize ait ilk 5 adet hesabın adreslerini üretelim ve bu adreslere gelen Erc-20 transferlerini dinlemek için şu kodları yazabiliriz;

‘.GetAllChanges()’ metodunu çağırarak belirlediğimiz adreslere (addressList) herhangi bir transfer geldiğinde haberdar olmuş olacağız.

Dikkat etmemiz gereken bir konu, eğer çok fazla adres takip etmek istersek (yüzlerce gibi), Nethereum yavaş cevap verebilir. Bunun yerine, daha önce örneğini yaptığımız gibi tüm transferleri dinleyip, bize ait olup olmadığını kod içerisinde kontrol edebiliriz (List<T>.Contains(T) gibi).

Yazdığımız test metotlarını şurada bulabilirsiniz;

Erc-20 Tracker

Yukarıda kullandığımız yöntemler ile, yazının başında bahsettiğim uygulamamızı geliştirebiliriz.

İlk olarak Erc-20 transfer işlemlerini hangi blokdan itibaren takip edeceğimizi belirlemeliyiz. Bunun için ‘LastProcessedBlock’ isminde bir değişkeni ortam değişkenlerinden alalım;

-e LASTPROCESSEDBLOCK=8363860

Başlangıç bloğunu belirlediğimize göre artık hangi bloğa kadar sorgulayacağımızı belirlemeliyiz. Bunun için de Nethereum ile en son blok bilgisini almalıyız;

_web3.Eth.Blocks.GetBlockNumber.SendRequestAsync()

Uygulamamız birden fazla Erc-20 smart contract’ı takip edebilmekte. Bunların bilgisini yazının başında bahsettiğimiz gibi şu şekilde alııyoruz;

-e CONTRACTADDRESSES:0=0xdac17f958d2ee523a2206206994597c13d831ec7
-e CONTRACTADDRESSES:1=0xB8c77482e45F1F44dE1745F52C74426C631bDD52

Uygulama içerisinde bu contract’lara göre ayrı ayrı sorgulayacağız;

foreach (var contract in _settings.ContractAddresses)
{
...
}

Her bir smart contract için başlangıç-bitiş blokları arasındaki tüm transfer bilgilerini yukarıda unit testte yaptığımız gibi çekmeliyiz;

var filter = transferEventHandler.CreateFilterInput(
fromBlock: new BlockParameter(lastProcessedBlock + 1),
toBlock: new BlockParameter(blockNumber)
);
var events = await transferEventHandler.GetAllChanges(filter);

Daha sonra bu event’ler arasında sadece bizim adreslerimize gelenleri filtreleyeceğiz;

var trackedEventList = events.Where( transactionEvent => _trackedAddresses.Contains(transactionEvent.Event.To)).ToList();

Elde ettiğimiz bu transaction listesini RabbitMq exchange’ine gönderiyoruz;

foreach (var tx in transactionList) {
var txData = JsonConvert.SerializeObject(tx);
var body = Encoding.UTF8.GetBytes(txData);
_channel.BasicPublish(
exchange: _settings.RabbitMqExchangeName,
routingKey: tx.ContractAddress,
basicProperties: null,
body: body);
}

Uygulamanın tamamını şurada bulabilirsiniz;

Uygulamayı bir Ethereum full node’una bağlanarak çalıştırmak üzere herkese açık olan şu ip adresine bağlanacağız; 52.208.46.161

Gerçek Ether kullandığınız durumlarda ya kendi node’unuza bağlanmalı yada güvenli olduğundan emin olduğunuz bir node’a bağlanmalısınız.

Örnek olarak Tether Erc-20 smart contract’ını dinleyeceğiz. Bunun adresine ulaşmak için Etherscan sitesini kullanacağız.

Tether contact adresi : 0xdac17f958d2ee523a2206206994597c13d831ec7

Güncel blok sayısını da Etherscan’den alalım;

03 Eylül 2019 23:45:40 : 8479450

Elde edilen transfer bilgileri göndermek üzere Docker ile bir RabbitMq çalıştıralım;

docker run -d --hostname my-rabbit --name some-rabbit -p 4369:4369 -p 5671:5671 -p 5672:5672 -p 15672:15672 rabbitmq:3-management

Uygulamanın Dockerfile’ını derleyerek yeni bir imaj (erc20-tracker) oluşturalım;

docker build -t erc20-tracker .

Uygulamayı yukarıdaki toparladığımız parametreler ile çalıştırırsak;

docker run -e NODEURL=http://52.208.46.161:8546 -e CONTRACTADDRESSES:0=0xdAC17F958D2ee523a2206206994597C13D831ec7 -e LASTPROCESSEDBLOCK=8479450 erc20-tracker

Sonuçları şu şekilde görebiliriz;

Eğer özellikle takip etmek istediğimiz bir adres belirlemek istersek şu ortam değişkenini de ekleyelim;

-e TRACKEDADDRESSES:0=0xde769dda17eD7d2178a5646D279382d0CaEC8079

Bu adrese herhangi bir transfer gerçekleşirse RabbitMq altında belirlediğiniz bir exchange’e, contract adresine göre routingKey belirleyerek gönderilecektir. Exchange adını belirlemek için gerekli ortam değişkeni;

-e RABBITMQEXCHANGENAME=Erc20Transactions

Kuyruğa eklenen transfer mesajlarını görmek üzere RabbitMq arayüzüne şuradan erişebilirsiniz: http://localhost:15672

--

--