├── Cache_simu_bitcoin_fr.zip ├── Inputs ├── ETH │ └── ETH_Addresses_exemple.csv ├── TXS_Categ_exemple.csv ├── BTC │ └── BTC_Addresses_exemple.csv └── Kraken │ └── simulation-bitcoin-fr.csv ├── .gitignore ├── blockstream ├── blockstream.go ├── bch.go ├── lbtc.go ├── bcd.go ├── api_test.go └── btg.go ├── monero ├── monero.go └── csv.go ├── mycelium ├── mycelium.go ├── csv_test.go └── csv.go ├── ledgerlive ├── ledgerlive.go ├── csv_test.go └── csv.go ├── blockchain ├── blockchain.go └── json.go ├── make.sh ├── uphold ├── uphold.go ├── csv_test.go └── csv.go ├── bitfinex └── bitfinex.go ├── revolut ├── revolut.go └── csv_test.go ├── utils └── utils.go ├── btc ├── csv.go └── btc.go ├── coinbasepro ├── coinbasepro.go └── csv_fills.go ├── etherscan ├── csv.go ├── etherscan.go ├── api_internal.go ├── api_normal.go ├── api_nft.go └── api_token.go ├── localbitcoin ├── localbitcoin.go └── csv_test.go ├── config_simu_bitcoin_fr.yml ├── poloniex ├── poloniex.go ├── csv_distributions.go ├── csv_deposits.go ├── csv_withdrawals.go └── csv_trades.go ├── coinbase ├── coinbase.go └── csv_test.go ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── category ├── csv.go └── category.go ├── binance ├── csv_test.go ├── api_test.go ├── binance.go ├── api_asset_dividend.go ├── api_deposits.go ├── api_withdrawals.go ├── api_assets_info.go └── api_spot_trades.go ├── go.mod ├── cryptocom ├── api_exchange_test.py ├── csv_app_crypto_test.go ├── api_exchange_test.go ├── csv_exchange_supercharger.go ├── csv_exchange_stake.go ├── csv_exchange_transfer.go ├── cryptocom.go ├── json_exporter.js ├── csv_exchange_spot_trade.go ├── api_exchange_spot_trades.go ├── api_exchange_deposits.go └── api_exchange_withdrawals.go ├── kraken ├── api_assets.go ├── csv_test.go ├── kraken.go └── api_trades.go ├── LICENSE ├── wallet ├── wallet_test.go ├── coinlayer.go ├── coinapi.go └── coingecko.go ├── bitstamp ├── bitstamp.go └── api_crypto_transactions.go ├── bittrex ├── bittrex.go ├── csv_test.go ├── api_test.go ├── api_withdrawals.go ├── api_deposits.go └── api_trades.go ├── hitbtc ├── hitbtc.go ├── api_trades.go ├── csv_transactions.go ├── api_account_transactions.go ├── csv_trades.go └── api.go ├── CONTRIBUTING.md └── source └── source.go /Cache_simu_bitcoin_fr.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiscafacile/CryptoFiscaFacile/HEAD/Cache_simu_bitcoin_fr.zip -------------------------------------------------------------------------------- /Inputs/ETH/ETH_Addresses_exemple.csv: -------------------------------------------------------------------------------- 1 | Address,Description 2 | 0x9302F624d2C35fe880BFce22A36917b5dB5FAFeD,exemple avec l'adresse ETH de don à CryptoFiscaFacile 3 | -------------------------------------------------------------------------------- /Inputs/TXS_Categ_exemple.csv: -------------------------------------------------------------------------------- 1 | TxID,Type,Description,Value,Currency 2 | 6654a91eaf3932fa8ac7a0ad5e2c89a8fe09b8b3c776172e80a072f3f93ce219,OUT,Achat du nom de domaine fiscafacile.crypto,40,USD -------------------------------------------------------------------------------- /Inputs/BTC/BTC_Addresses_exemple.csv: -------------------------------------------------------------------------------- 1 | Address,Description 2 | 36BTpmPbZaG2e5DyMpjEfDeEaiwjR8jGUM,exemple avec la première adresse BTC de don à CryptoFiscaFacile 3 | bc1qlmsx8vtk03jwcuafe7vzvddjzg4nsfvflgs4k9,exemple avec la seconde adresse BTC de don à CryptoFiscaFacile 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | Inputs/* 15 | Cache/ 16 | CryptoFiscaFacile* 17 | *.xlsx 18 | config.yml 19 | -------------------------------------------------------------------------------- /blockstream/blockstream.go: -------------------------------------------------------------------------------- 1 | package blockstream 2 | 3 | type Blockstream struct { 4 | apiTXs []apiTX 5 | done chan error 6 | } 7 | 8 | func New() *Blockstream { 9 | blkst := &Blockstream{} 10 | blkst.done = make(chan error) 11 | return blkst 12 | } 13 | 14 | func (blkst *Blockstream) WaitFinish() error { 15 | return <-blkst.done 16 | } 17 | -------------------------------------------------------------------------------- /monero/monero.go: -------------------------------------------------------------------------------- 1 | package monero 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 5 | ) 6 | 7 | type Monero struct { 8 | CsvTXs []CsvTX 9 | TXsByCategory wallet.TXsByCategory 10 | } 11 | 12 | func New() *Monero { 13 | xmr := &Monero{} 14 | xmr.TXsByCategory = make(map[string]wallet.TXs) 15 | return xmr 16 | } 17 | -------------------------------------------------------------------------------- /mycelium/mycelium.go: -------------------------------------------------------------------------------- 1 | package mycelium 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 5 | ) 6 | 7 | type MyCelium struct { 8 | CsvTXs []CsvTX 9 | TXsByCategory wallet.TXsByCategory 10 | } 11 | 12 | func New() *MyCelium { 13 | mc := &MyCelium{} 14 | mc.TXsByCategory = make(map[string]wallet.TXs) 15 | return mc 16 | } 17 | -------------------------------------------------------------------------------- /ledgerlive/ledgerlive.go: -------------------------------------------------------------------------------- 1 | package ledgerlive 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 5 | ) 6 | 7 | type LedgerLive struct { 8 | CsvTXs []CsvTX 9 | TXsByCategory wallet.TXsByCategory 10 | } 11 | 12 | func New() *LedgerLive { 13 | ll := &LedgerLive{} 14 | ll.TXsByCategory = make(map[string]wallet.TXs) 15 | return ll 16 | } 17 | -------------------------------------------------------------------------------- /blockchain/blockchain.go: -------------------------------------------------------------------------------- 1 | package blockchain 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 5 | ) 6 | 7 | type BlockChain struct { 8 | jsonTXs []JsonTX 9 | TXsByCategory wallet.TXsByCategory 10 | } 11 | 12 | func New() *BlockChain { 13 | cb := &BlockChain{} 14 | cb.TXsByCategory = make(map[string]wallet.TXs) 15 | return cb 16 | } 17 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PATH=$GOPATH/bin:$PATH 4 | 5 | VER=$(git describe --tags) 6 | 7 | BASENAME="CryptoFiscaFacile" 8 | 9 | GOOS=$(go env | grep GOOS | cut -d"=" -f2) 10 | GOARCH=$(go env | grep GOARCH | cut -d"=" -f2) 11 | 12 | echo "$GOOS"/"$GOARCH" 13 | 14 | if [ "$GOOS" = "windows" ] 15 | then 16 | EXT=".exe" 17 | else 18 | EXT="" 19 | fi 20 | go build -ldflags "-X main.version=$VER" && mv $BASENAME"$EXT" $BASENAME-"$VER"-"$GOOS"-"$GOARCH""$EXT" 21 | -------------------------------------------------------------------------------- /uphold/uphold.go: -------------------------------------------------------------------------------- 1 | package uphold 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/source" 5 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 6 | ) 7 | 8 | type Uphold struct { 9 | CsvTXs []CsvTX 10 | TXsByCategory wallet.TXsByCategory 11 | Sources source.Sources 12 | } 13 | 14 | func New() *Uphold { 15 | uh := &Uphold{} 16 | uh.TXsByCategory = make(map[string]wallet.TXs) 17 | uh.Sources = make(source.Sources) 18 | return uh 19 | } 20 | -------------------------------------------------------------------------------- /bitfinex/bitfinex.go: -------------------------------------------------------------------------------- 1 | package bitfinex 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/source" 5 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 6 | ) 7 | 8 | type Bitfinex struct { 9 | CsvTXs []CsvTX 10 | TXsByCategory wallet.TXsByCategory 11 | Sources source.Sources 12 | } 13 | 14 | func New() *Bitfinex { 15 | bf := &Bitfinex{} 16 | bf.TXsByCategory = make(wallet.TXsByCategory) 17 | bf.Sources = make(source.Sources) 18 | return bf 19 | } 20 | -------------------------------------------------------------------------------- /revolut/revolut.go: -------------------------------------------------------------------------------- 1 | package revolut 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/source" 5 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 6 | ) 7 | 8 | type Revolut struct { 9 | CsvTXs []CsvTX 10 | TXsByCategory wallet.TXsByCategory 11 | Sources source.Sources 12 | } 13 | 14 | func New() *Revolut { 15 | revo := &Revolut{} 16 | revo.TXsByCategory = make(map[string]wallet.TXs) 17 | revo.Sources = make(source.Sources) 18 | return revo 19 | } 20 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | ) 7 | 8 | func AppendUniq(strs []string, str string) []string { 9 | found := false 10 | for _, s := range strs { 11 | if str == s { 12 | found = true 13 | } 14 | } 15 | if !found { 16 | return append(strs, str) 17 | } 18 | return strs 19 | } 20 | 21 | func GetUniqueID(str string) string { 22 | hash := sha256.Sum256([]byte(str)) 23 | return hex.EncodeToString(hash[:]) 24 | } 25 | -------------------------------------------------------------------------------- /btc/csv.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | ) 7 | 8 | func (btc *BTC) ParseCSVAddresses(reader io.Reader) (err error) { 9 | csvReader := csv.NewReader(reader) 10 | records, err := csvReader.ReadAll() 11 | if err == nil { 12 | for _, r := range records { 13 | if r[0] != "Address" { 14 | a := Address{} 15 | a.Address = r[0] 16 | a.Description = r[1] 17 | btc.Addresses = append(btc.Addresses, a) 18 | } 19 | } 20 | } 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /coinbasepro/coinbasepro.go: -------------------------------------------------------------------------------- 1 | package coinbasepro 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/source" 5 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 6 | ) 7 | 8 | type CoinbasePro struct { 9 | CsvFillsTXs []CsvFillsTX 10 | CsvAccountTXs []CsvAccountTX 11 | TXsByCategory wallet.TXsByCategory 12 | Sources source.Sources 13 | } 14 | 15 | func New() *CoinbasePro { 16 | cbp := &CoinbasePro{} 17 | cbp.TXsByCategory = make(map[string]wallet.TXs) 18 | cbp.Sources = make(source.Sources) 19 | return cbp 20 | } 21 | -------------------------------------------------------------------------------- /etherscan/csv.go: -------------------------------------------------------------------------------- 1 | package etherscan 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "strings" 7 | ) 8 | 9 | func (ethsc *Etherscan) ParseCSVAddresses(reader io.Reader) (err error) { 10 | csvReader := csv.NewReader(reader) 11 | records, err := csvReader.ReadAll() 12 | if err == nil { 13 | for _, r := range records { 14 | if r[0] != "Address" { 15 | a := address{} 16 | a.address = strings.ToLower(r[0]) 17 | a.description = r[1] 18 | ethsc.addresses = append(ethsc.addresses, a) 19 | } 20 | } 21 | } 22 | return 23 | } 24 | -------------------------------------------------------------------------------- /localbitcoin/localbitcoin.go: -------------------------------------------------------------------------------- 1 | package localbitcoin 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/source" 5 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 6 | ) 7 | 8 | type LocalBitcoin struct { 9 | CsvTXsTrade []CsvTXTrade 10 | CsvTXsTransfer []CsvTXTransfer 11 | TXsByCategory wallet.TXsByCategory 12 | Sources source.Sources 13 | } 14 | 15 | func New() *LocalBitcoin { 16 | lb := &LocalBitcoin{} 17 | lb.TXsByCategory = make(map[string]wallet.TXs) 18 | lb.Sources = make(source.Sources) 19 | return lb 20 | } 21 | -------------------------------------------------------------------------------- /config_simu_bitcoin_fr.yml: -------------------------------------------------------------------------------- 1 | --- 2 | exchanges: 3 | kraken: 4 | csv: 5 | all: 6 | - Inputs/Kraken/simulation-bitcoin-fr.csv 7 | options: 8 | bcd: no 9 | bch: no 10 | binance-extended: no 11 | btg: no 12 | cashin-bnc: 13 | 2019: no 14 | 2020: no 15 | 2021: no 16 | export-2086: yes 17 | lbtc: no 18 | location: Europe/Paris 19 | native: EUR 20 | stats: yes 21 | txs-categ: # Inputs/TXS_Categ.csv 22 | tools: 23 | coinapi: 24 | # key: 25 | coinlayer: 26 | # key: 27 | etherscan: 28 | # key: 29 | -------------------------------------------------------------------------------- /poloniex/poloniex.go: -------------------------------------------------------------------------------- 1 | package poloniex 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/source" 5 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 6 | ) 7 | 8 | type Poloniex struct { 9 | csvDepositsTXs []csvDepositsTX 10 | csvDistributionsTXs []csvDistributionsTX 11 | csvTradesTXs []csvTradesTX 12 | csvWithdrawalsTXs []csvWithdrawalsTX 13 | TXsByCategory wallet.TXsByCategory 14 | Sources source.Sources 15 | } 16 | 17 | func New() *Poloniex { 18 | pl := &Poloniex{} 19 | pl.TXsByCategory = make(wallet.TXsByCategory) 20 | pl.Sources = make(source.Sources) 21 | return pl 22 | } 23 | -------------------------------------------------------------------------------- /coinbase/coinbase.go: -------------------------------------------------------------------------------- 1 | package coinbase 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/fiscafacile/CryptoFiscaFacile/source" 7 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 8 | ) 9 | 10 | type Coinbase struct { 11 | CsvTXs []CsvTX 12 | TXsByCategory wallet.TXsByCategory 13 | Sources source.Sources 14 | } 15 | 16 | func New() *Coinbase { 17 | cb := &Coinbase{} 18 | cb.TXsByCategory = make(map[string]wallet.TXs) 19 | cb.Sources = make(source.Sources) 20 | return cb 21 | } 22 | 23 | func ReplaceAssets(assetToReplace string) string { 24 | assetRplcr := strings.NewReplacer( 25 | "CGLD", "CELO", 26 | ) 27 | return assetRplcr.Replace(assetToReplace) 28 | } 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | Command Line '...' 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. Windows/Mac/Linux] 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /btc/btc.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 5 | ) 6 | 7 | type Address struct { 8 | Address string 9 | Description string 10 | } 11 | 12 | type BTC struct { 13 | Addresses []Address 14 | TXsByCategory wallet.TXsByCategory 15 | } 16 | 17 | func New() *BTC { 18 | btc := &BTC{} 19 | btc.TXsByCategory = make(map[string]wallet.TXs) 20 | return btc 21 | } 22 | 23 | func (btc *BTC) AddListAddresses(list []string) { 24 | for _, add := range list { 25 | btc.Addresses = append(btc.Addresses, Address{Address: add}) 26 | } 27 | } 28 | 29 | func (btc BTC) OwnAddress(add string) bool { 30 | for _, a := range btc.Addresses { 31 | if a.Address == add { 32 | return true 33 | } 34 | } 35 | return false 36 | } 37 | -------------------------------------------------------------------------------- /mycelium/csv_test.go: -------------------------------------------------------------------------------- 1 | package mycelium 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_CSVParseExemple(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | csv string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "ParseCSV", 16 | csv: "Compte 1,563c9ae8edca798b1d13eb4f167f4a8735385ad9dcec767a1bf0377e43bf3929,16Rp4mkpFY4rgSzX7VFFbmUuJSZymqz83c,2018-11-06T23:08Z,-0.00924295,Bitcoin,", 17 | wantErr: false, 18 | }, 19 | } 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | mc := New() 23 | err := mc.ParseCSV(strings.NewReader(tt.csv)) 24 | if (err != nil) != tt.wantErr { 25 | t.Errorf("MyCelium.ParseCSV() error = %v, wantErr %v", err, tt.wantErr) 26 | } 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /category/csv.go: -------------------------------------------------------------------------------- 1 | package category 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | 8 | "github.com/shopspring/decimal" 9 | ) 10 | 11 | type csvCategorie struct { 12 | txID string 13 | kind string 14 | description string 15 | value decimal.Decimal 16 | currency string 17 | } 18 | 19 | func (cat *Category) ParseCSVCategory(reader io.Reader) { 20 | const SOURCE = "TXs Categorie CSV :" 21 | csvReader := csv.NewReader(reader) 22 | records, err := csvReader.ReadAll() 23 | if err == nil { 24 | for _, r := range records { 25 | if r[0] != "TxID" { 26 | a := csvCategorie{} 27 | a.txID = r[0] 28 | a.kind = r[1] 29 | a.description = r[2] 30 | if r[3] != "" { 31 | a.value, err = decimal.NewFromString(r[3]) 32 | if err != nil { 33 | log.Println(SOURCE, "Error Parsing Value", r[3]) 34 | } 35 | } 36 | a.currency = r[4] 37 | cat.csvCategories = append(cat.csvCategories, a) 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /binance/csv_test.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_CSVParseExemple(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | csv string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "ParseCSV Buy", 16 | csv: "2020-04-13 09:22:50,Spot,Buy,ETH,0.75673607,\"\"", 17 | wantErr: false, 18 | }, 19 | { 20 | name: "ParseCSV Sell", 21 | csv: "2020-04-13 09:22:50,Spot,Sell,BNB,-8.29000000,\"\"", 22 | wantErr: false, 23 | }, 24 | { 25 | name: "ParseCSV Withdraw", 26 | csv: "2020-04-13 09:33:17,Spot,Withdraw,ETH,-0.75597933,Withdraw fee is included", 27 | wantErr: false, 28 | }, 29 | } 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | b := New() 33 | err := b.ParseCSV(strings.NewReader(tt.csv)) 34 | if (err != nil) != tt.wantErr { 35 | t.Errorf("Binance.ParseCSV() error = %v, wantErr %v", err, tt.wantErr) 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fiscafacile/CryptoFiscaFacile 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/360EntSecGroup-Skylar/excelize v1.4.1 7 | github.com/anaskhan96/base58check v0.0.0-20181220122047-b05365d494c4 8 | github.com/davecgh/go-spew v1.1.1 9 | github.com/go-resty/resty/v2 v2.6.0 10 | github.com/google/uuid v1.3.0 11 | github.com/jcelliott/lumber v0.0.0-20160324203708-dd349441af25 // indirect 12 | github.com/kr/pretty v0.2.1 // indirect 13 | github.com/nanobox-io/golang-scribble v0.0.0-20190309225732-aa3e7c118975 14 | github.com/shopspring/decimal v1.2.0 15 | github.com/spf13/pflag v1.0.5 16 | github.com/stretchr/testify v1.6.1 // indirect 17 | github.com/superoo7/go-gecko v1.0.0 18 | golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect 19 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 20 | gopkg.in/resty.v1 v1.12.0 21 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b 22 | ) 23 | 24 | require github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect 25 | -------------------------------------------------------------------------------- /cryptocom/api_exchange_test.py: -------------------------------------------------------------------------------- 1 | import hmac 2 | import hashlib 3 | import json 4 | import time 5 | 6 | API_KEY = "API_KEY" 7 | SECRET_KEY = "SECRET_KEY" 8 | 9 | req = { 10 | "id": 11, 11 | "method": "private/get-order-detail", 12 | "api_key": API_KEY, 13 | "params": { 14 | "order_id": "337843775021233500", 15 | }, 16 | "nonce": int(time.time() * 1000) 17 | }; 18 | 19 | # First ensure the params are alphabetically sorted by key 20 | paramString = "" 21 | 22 | if "params" in req: 23 | for key in sorted(req['params']): 24 | paramString += key 25 | paramString += str(req['params'][key]) 26 | 27 | sigPayload = req['method'] + str(req['id']) + req['api_key'] + paramString + str(req['nonce']) 28 | 29 | req['sig'] = hmac.new( 30 | bytes(str(SECRET_KEY), 'utf-8'), 31 | msg=bytes(sigPayload, 'utf-8'), 32 | digestmod=hashlib.sha256 33 | ).hexdigest() 34 | 35 | print(req['nonce']) # 1619956517732 36 | print(req['sig']) # '8c17b4cfbb7073a5453e348ecb1b20a1e709af910a7d1fe4b4569bcf29736e58' -------------------------------------------------------------------------------- /cryptocom/csv_app_crypto_test.go: -------------------------------------------------------------------------------- 1 | package cryptocom 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_CSVParseExemple(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | csv string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "ParseCSVAppCrypto referral_card_cashback", 16 | csv: "2020-12-31 15:43:19,Card Cashback,CRO,26.96195063,,,EUR,1.27,1.5174771149,referral_card_cashback", 17 | wantErr: false, 18 | }, 19 | { 20 | name: "ParseCSVAppCrypto exchange_to_crypto_transfer", 21 | csv: "2020-11-05 11:45:03,Transfer: Exchange -> App wallet,CRO,50.47244468,,,EUR,3.35,4.0027939645,exchange_to_crypto_transfer", 22 | wantErr: false, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | cdc := New() 28 | err := cdc.ParseCSVAppCrypto(strings.NewReader(tt.csv)) 29 | if (err != nil) != tt.wantErr { 30 | t.Errorf("CryptoCom.ParseCSVAppCrypto() error = %v, wantErr %v", err, tt.wantErr) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /kraken/api_assets.go: -------------------------------------------------------------------------------- 1 | package kraken 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | scribble "github.com/nanobox-io/golang-scribble" 8 | ) 9 | 10 | func (api *api) getAPIAssets() { 11 | const SOURCE = "Kraken API Assets :" 12 | useCache := true 13 | db, err := scribble.New("./Cache", nil) 14 | if err != nil { 15 | useCache = false 16 | } 17 | if useCache { 18 | err = db.Read("Kraken/public", "Assets", &api.assets.Result) 19 | } 20 | if !useCache || err != nil { 21 | resource := "/0/public/Assets" 22 | headers := make(map[string]string) 23 | headers["Content-Type"] = "application/json" 24 | _, err := api.clientAssets.R(). 25 | SetHeaders(headers). 26 | SetResult(&api.assets). 27 | Post(api.basePath + resource) 28 | if err != nil || len(api.assets.Error) > 0 { 29 | fmt.Println(SOURCE, "Error Requesting AssetsInfo"+strings.Join(api.assets.Error, "")) 30 | } 31 | if useCache { 32 | err = db.Write("Kraken/public", "Assets", api.assets.Result) 33 | if err != nil { 34 | fmt.Println(SOURCE, "Error Caching AssetsInfo") 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /blockstream/bch.go: -------------------------------------------------------------------------------- 1 | package blockstream 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/fiscafacile/CryptoFiscaFacile/btc" 9 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 10 | ) 11 | 12 | func (blkst *Blockstream) DetectBCH(b *btc.BTC) { 13 | bchForkDate := time.Date(2017, time.August, 1, 15, 16, 0, 0, time.UTC) 14 | w := b.TXsByCategory.GetWallets(bchForkDate, false, false) 15 | w.Println("BTC (at time of BCH Fork)", "BTC") 16 | fmt.Println("Addresses :") 17 | for _, a := range b.Addresses { 18 | bal, err := blkst.GetAddressBalanceAtDate(a.Address, bchForkDate) 19 | if err != nil { 20 | log.Println("") 21 | break 22 | } 23 | if !bal.IsZero() { 24 | fmt.Println(" ", a.Address, "balance", bal) 25 | t := wallet.TX{Timestamp: bchForkDate, Note: "Blockstream API : 478558 Bitcoin Cash Fork on " + a.Address} 26 | t.Items = make(map[string]wallet.Currencies) 27 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: "BCH", Amount: bal}) 28 | b.TXsByCategory["Forks"] = append(b.TXsByCategory["Forks"], t) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /blockstream/lbtc.go: -------------------------------------------------------------------------------- 1 | package blockstream 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/fiscafacile/CryptoFiscaFacile/btc" 9 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 10 | ) 11 | 12 | func (blkst *Blockstream) DetectLBTC(b *btc.BTC) { 13 | lbtcForkDate := time.Date(2017, time.December, 18, 18, 34, 0, 0, time.UTC) 14 | w := b.TXsByCategory.GetWallets(lbtcForkDate, false, false) 15 | w.Println("BTC (at time of LBTC Fork)", "BTC") 16 | fmt.Println("Addresses :") 17 | for _, a := range b.Addresses { 18 | bal, err := blkst.GetAddressBalanceAtDate(a.Address, lbtcForkDate) 19 | if err != nil { 20 | log.Println("") 21 | break 22 | } 23 | if !bal.IsZero() { 24 | fmt.Println(" ", a.Address, "balance", bal) 25 | t := wallet.TX{Timestamp: lbtcForkDate, Note: "Blockstream API : 499999 Lightning Bitcoin Fork on " + a.Address} 26 | t.Items = make(map[string]wallet.Currencies) 27 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: "LBTC", Amount: bal}) 28 | b.TXsByCategory["Forks"] = append(b.TXsByCategory["Forks"], t) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /revolut/csv_test.go: -------------------------------------------------------------------------------- 1 | package revolut 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_CSVParseExemple(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | csv string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "ParseCSV CashIn", 16 | csv: "Completed Date,Description,Paid Out (BTC),Paid In (BTC),Exchange Out, Exchange In, Balance (BTC), Category, Notes\n26 nov. 2019,Échanger to BTC FX Rate 1 ₿ = 6530.9001 €,,0.01,EUR 65.31,,0.057,Général,", 17 | wantErr: false, 18 | }, 19 | { 20 | name: "ParseCSV CashOut", 21 | csv: "Completed Date,Description,Paid Out (BTC),Paid In (BTC),Exchange Out, Exchange In, Balance (BTC), Category, Notes\n15 févr. 2020,Échanger BTC to FX Rate 1 ₿ = 9297.4833 €,0.057,,BTC 0.057,,0.00,Général,", 22 | wantErr: false, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | revo := New() 28 | err := revo.ParseCSV(strings.NewReader(tt.csv)) 29 | if (err != nil) != tt.wantErr { 30 | t.Errorf("Revolut.ParseCSV() error = %v, wantErr %v", err, tt.wantErr) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 fiscafacile 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cryptocom/api_exchange_test.go: -------------------------------------------------------------------------------- 1 | package cryptocom 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_API_Exchange_sign(t *testing.T) { 8 | type args struct { 9 | body map[string]interface{} 10 | } 11 | tests := []struct { 12 | name string 13 | args args 14 | apiKey string 15 | apiSecret string 16 | wantSig string 17 | }{ 18 | { 19 | name: "Test Signature on Official doc example", 20 | args: args{ 21 | body: map[string]interface{}{ 22 | "id": 11, 23 | "method": "private/get-order-detail", 24 | "params": map[string]interface{}{ 25 | "order_id": "337843775021233500", 26 | }, 27 | "nonce": 1619956517732, 28 | }, 29 | }, 30 | apiKey: "API_KEY", 31 | apiSecret: "SECRET_KEY", 32 | wantSig: "8c17b4cfbb7073a5453e348ecb1b20a1e709af910a7d1fe4b4569bcf29736e58", 33 | }, 34 | } 35 | for _, tt := range tests { 36 | cdc := New() 37 | cdc.NewExchangeAPI(tt.apiKey, tt.apiSecret) 38 | t.Run(tt.name, func(t *testing.T) { 39 | cdc.apiEx.sign(tt.args.body) 40 | if tt.args.body["sig"] != tt.wantSig { 41 | t.Errorf("TXs.SortByDate() body = %v, wantSig %v", tt.args.body, tt.wantSig) 42 | } 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /blockstream/bcd.go: -------------------------------------------------------------------------------- 1 | package blockstream 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/fiscafacile/CryptoFiscaFacile/btc" 9 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | func (blkst *Blockstream) DetectBCD(b *btc.BTC) { 14 | bcdForkDate := time.Date(2017, time.November, 24, 10, 20, 0, 0, time.UTC) 15 | w := b.TXsByCategory.GetWallets(bcdForkDate, false, false) 16 | w.Println("BTC (at time of BCD Fork)", "BTC") 17 | fmt.Println("Addresses :") 18 | for _, a := range b.Addresses { 19 | bal, err := blkst.GetAddressBalanceAtDate(a.Address, bcdForkDate) 20 | if err != nil { 21 | log.Println("") 22 | break 23 | } 24 | if !bal.IsZero() { 25 | bal = bal.Mul(decimal.NewFromInt(int64(10))) 26 | fmt.Println(" ", a.Address, "balance", bal) 27 | t := wallet.TX{Timestamp: bcdForkDate, Note: "Blockstream API : 495866 Bitcoin Diamond Fork on " + a.Address} 28 | t.Items = make(map[string]wallet.Currencies) 29 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: "BCD", Amount: bal}) 30 | b.TXsByCategory["Forks"] = append(b.TXsByCategory["Forks"], t) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ledgerlive/csv_test.go: -------------------------------------------------------------------------------- 1 | package ledgerlive 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_CSVParseExemple(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | csv string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "ParseCSV Deposit", 16 | csv: "2019-12-02T09:51:31.000Z,BTC,IN,2.8949079,0.0001922,f978fc4c94e054fa473ac4099f584bd9c0c58ade49f1a0c5941fad3a180e54e6,Bitcoin,xpub6DCB4S5L5Mp4Whnd6waASfGnXDLZXqBGRTRZ45QHqXgreimLRiYyen6HYuCqxgZDwCAW5AE1DgTy5RKSsYdhFGcueUSNH9vbTvWZTmEkh2Z", 17 | wantErr: false, 18 | }, 19 | { 20 | name: "ParseCSV Withdrawal", 21 | csv: "2020-05-01T09:28:20.000Z,ETH,OUT,0.000336789,0.000336789,0x83066f0381f81a11c8c2849312252bc2e23ce957ec46db90583699d89d4238da,Ethereum,xpub6DXuQW1FgeHbgaWqEH46Mk1v5E8sGyNRcJ8zx6945Mys9YRy7ZsqGPPhDhJCWM4rYAk6JR6PqJosRn9sJFyWBHWEoEPHES7eg9x7tkddNxs", 22 | wantErr: false, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | ll := New() 28 | err := ll.ParseCSV(strings.NewReader(tt.csv)) 29 | if (err != nil) != tt.wantErr { 30 | t.Errorf("LedgerLive.ParseCSV() error = %v, wantErr %v", err, tt.wantErr) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /uphold/csv_test.go: -------------------------------------------------------------------------------- 1 | package uphold 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_CSVParseExemple(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | csv string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "ParseCSV Deposit", 16 | csv: "Date,Destination,Destination Amount,Destination Currency,Fee Amount,Fee Currency,Id,Origin,Origin Amount,Origin Currency,Status,Type\nFri Apr 09 2021 23:58:41 GMT+0000,uphold,21.375,BAT,,,f0fc27a7-a0af-4a1a-8d42-24795a27f8fe,uphold,21.375,BAT,completed,in", 17 | wantErr: false, 18 | }, 19 | { 20 | name: "ParseCSV Withdrawal", 21 | csv: "Date,Destination,Destination Amount,Destination Currency,Fee Amount,Fee Currency,Id,Origin,Origin Amount,Origin Currency,Status,Type\nWed May 05 2021 11:48:48 GMT+0000,uphold,5,BAT,,,7271cd79-b02d-4f52-b6dd-e663dc7c4cf9,uphold,5,BAT,completed,out", 22 | wantErr: false, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | uh := New() 28 | err := uh.ParseCSV(strings.NewReader(tt.csv)) 29 | if (err != nil) != tt.wantErr { 30 | t.Errorf("Uphold.ParseCSV() error = %v, wantErr %v", err, tt.wantErr) 31 | } 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /binance/api_test.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_API_sign(t *testing.T) { 8 | type args struct { 9 | queryParams map[string]string 10 | } 11 | tests := []struct { 12 | name string 13 | args args 14 | apiKey string 15 | apiSecret string 16 | wantSig string 17 | debug bool 18 | }{ 19 | { 20 | name: "Test Signature", 21 | args: args{ 22 | queryParams: map[string]string{ 23 | "asset": "ETH", 24 | "address": "0x6915f16f8791d0a1cc2bf47c13a6b2a92000504b", 25 | "amount": "1", 26 | "recvWindow": "5000", 27 | "name": "test", 28 | "timestamp": "1510903211000", 29 | }, 30 | }, 31 | debug: false, 32 | apiSecret: "NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j", 33 | wantSig: "89294eb90c24e5b1736723bd6afb03388b95ec793a359f49dca208142645839a", 34 | }, 35 | } 36 | for _, tt := range tests { 37 | b := New() 38 | b.NewAPI(tt.apiKey, tt.apiSecret, tt.debug) 39 | t.Run(tt.name, func(t *testing.T) { 40 | b.api.sign(tt.args.queryParams) 41 | if tt.args.queryParams["signature"] != tt.wantSig { 42 | t.Errorf("TXs.SortByDate() queryParams = %v, wantSig %v", tt.args.queryParams, tt.wantSig) 43 | } 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /etherscan/etherscan.go: -------------------------------------------------------------------------------- 1 | package etherscan 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/fiscafacile/CryptoFiscaFacile/category" 7 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 8 | ) 9 | 10 | type address struct { 11 | address string 12 | description string 13 | } 14 | 15 | type Etherscan struct { 16 | api api 17 | addresses []address 18 | done chan error 19 | TXsByCategory wallet.TXsByCategory 20 | } 21 | 22 | func New() *Etherscan { 23 | ethsc := &Etherscan{} 24 | ethsc.done = make(chan error) 25 | ethsc.TXsByCategory = make(map[string]wallet.TXs) 26 | return ethsc 27 | } 28 | 29 | func (ethsc *Etherscan) AddListAddresses(list []string) { 30 | for _, add := range list { 31 | ethsc.addresses = append(ethsc.addresses, address{address: strings.ToLower(add)}) 32 | } 33 | } 34 | 35 | func (ethsc *Etherscan) GetAPITXs(cat category.Category) { 36 | addresses := []string{} 37 | for _, a := range ethsc.addresses { 38 | addresses = append(addresses, a.address) 39 | } 40 | err := ethsc.api.getAllTXs(addresses, cat) 41 | if err != nil { 42 | ethsc.done <- err 43 | return 44 | } 45 | ethsc.TXsByCategory.Add(ethsc.api.txsByCategory) 46 | ethsc.done <- nil 47 | } 48 | 49 | func (ethsc *Etherscan) WaitFinish() error { 50 | return <-ethsc.done 51 | } 52 | -------------------------------------------------------------------------------- /blockstream/api_test.go: -------------------------------------------------------------------------------- 1 | package blockstream 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAPI_TXsSortByHeight(t *testing.T) { 8 | type args struct { 9 | asc bool 10 | } 11 | tests := []struct { 12 | name string 13 | args args 14 | startTXs apiTXs 15 | wantTXs apiTXs 16 | }{ 17 | { 18 | name: "TXs Sort By Date order asc", 19 | args: args{ 20 | asc: true, 21 | }, 22 | startTXs: apiTXs{ 23 | apiTX{Status: apiTXStatus{BlockHeight: 200}}, 24 | apiTX{Status: apiTXStatus{BlockHeight: 100}}, 25 | }, 26 | wantTXs: apiTXs{ 27 | apiTX{Status: apiTXStatus{BlockHeight: 100}}, 28 | apiTX{Status: apiTXStatus{BlockHeight: 200}}, 29 | }, 30 | }, 31 | { 32 | name: "TXs Sort By Height order desc", 33 | args: args{ 34 | asc: false, 35 | }, 36 | startTXs: apiTXs{ 37 | apiTX{Status: apiTXStatus{BlockHeight: 100}}, 38 | apiTX{Status: apiTXStatus{BlockHeight: 200}}, 39 | }, 40 | wantTXs: apiTXs{ 41 | apiTX{Status: apiTXStatus{BlockHeight: 200}}, 42 | apiTX{Status: apiTXStatus{BlockHeight: 100}}, 43 | }, 44 | }, 45 | } 46 | for _, tt := range tests { 47 | t.Run(tt.name, func(t *testing.T) { 48 | tt.startTXs.SortByHeight(tt.args.asc) 49 | for i := range tt.startTXs { 50 | if tt.startTXs[i].Status.BlockHeight != tt.wantTXs[i].Status.BlockHeight { 51 | t.Errorf("TXs.SortByDate() startTXs = %v, wantTXs %v", tt.startTXs, tt.wantTXs) 52 | } 53 | } 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /blockstream/btg.go: -------------------------------------------------------------------------------- 1 | package blockstream 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/anaskhan96/base58check" 9 | "github.com/fiscafacile/CryptoFiscaFacile/btc" 10 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 11 | ) 12 | 13 | func (blkst *Blockstream) DetectBTG(b *btc.BTC) { 14 | btgForkDate := time.Date(2017, time.August, 1, 15, 16, 0, 0, time.UTC) 15 | w := b.TXsByCategory.GetWallets(btgForkDate, false, false) 16 | w.Println("BTC (at time of BTG Fork)", "BTC") 17 | fmt.Println("Addresses :") 18 | for _, a := range b.Addresses { 19 | bal, err := blkst.GetAddressBalanceAtDate(a.Address, btgForkDate) 20 | if err != nil { 21 | log.Println("") 22 | break 23 | } 24 | if !bal.IsZero() { 25 | decoded, err := base58check.Decode(a.Address) 26 | if err != nil { 27 | log.Println("BTG base58 Decode error", a.Address, err) 28 | } else { 29 | version := "26" 30 | if a.Address[0] == '3' { 31 | version = "17" 32 | } 33 | encoded, err := base58check.Encode(version, decoded[2:]) 34 | if err != nil { 35 | log.Println("BTG base58 Encode error", decoded, err) 36 | } else { 37 | fmt.Println(" ", encoded, "balance", bal) 38 | t := wallet.TX{Timestamp: btgForkDate, Note: "Blockstream API : 491407 Bitcoin Gold Fork from " + a.Address + " to " + encoded} 39 | t.Items = make(map[string]wallet.Currencies) 40 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: "BTG", Amount: bal}) 41 | b.TXsByCategory["Forks"] = append(b.TXsByCategory["Forks"], t) 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /kraken/csv_test.go: -------------------------------------------------------------------------------- 1 | package kraken 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_CSVParseExemple(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | csv string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "ParseCSV Deposit", 16 | csv: "LYXXX-XXXXXX-XXXL5X,QCXXXXX-PNXXXX-PBXXXX,2018-01-05 11:36:20,deposit,,currency,ZEUR,10.0000,0.0000,10.0000,\"\"", 17 | wantErr: false, 18 | }, 19 | { 20 | name: "ParseCSV Withdraw", 21 | csv: "LYXXX-XXXXXX-XXXL5X,QCXXXXX-PNXXXX-PBXXXX,2018-01-09 22:01:31,withdrawal,,currency,XLTC,-0.4700300000,0.0010000000,0.0010000000,\"\"", 22 | wantErr: false, 23 | }, 24 | { 25 | name: "ParseCSV Trade", 26 | csv: "LYXXX-XXXXXX-XXXL5X,QCXXXXX-PNXXXX-PBXXXX,2018-01-09 13:21:13,trade,,currency,ZEUR,-149.7389,0.3893,350.7110,\"\"", 27 | wantErr: false, 28 | }, 29 | { 30 | name: "ParseCSV Staking", 31 | csv: "LYXXX-XXXXXX-XXXL5X,QCXXXXX-PNXXXX-PBXXXX,2021-03-06 01:08:57,staking,,currency,DOT.S,0.0085120600,0.0000000000,10.0085120600,\"\"", 32 | wantErr: false, 33 | }, 34 | { 35 | name: "ParseCSV Transfer", 36 | csv: "LYXXX-XXXXXX-XXXL5X,QCXXXXX-PNXXXX-PBXXXX,2021-03-02 10:49:32,transfer,stakingfromspot,currency,DOT.S,10.0000000000,0.0000000000,10.0000000000,\"\"", 37 | wantErr: false, 38 | }, 39 | } 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | kr := New() 43 | err := kr.ParseCSV(strings.NewReader(tt.csv)) 44 | if (err != nil) != tt.wantErr { 45 | t.Errorf("Kraken.ParseCSV() error = %v, wantErr %v", err, tt.wantErr) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /wallet/wallet_test.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestWallet_TXsSortByDate(t *testing.T) { 9 | type args struct { 10 | chrono bool 11 | } 12 | tests := []struct { 13 | name string 14 | args args 15 | startTXs TXs 16 | wantTXs TXs 17 | }{ 18 | { 19 | name: "TXs Sort By Date order Chrono", 20 | args: args{ 21 | chrono: true, 22 | }, 23 | startTXs: TXs{ 24 | TX{Timestamp: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)}, 25 | TX{Timestamp: time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC)}, 26 | }, 27 | wantTXs: TXs{ 28 | TX{Timestamp: time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC)}, 29 | TX{Timestamp: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)}, 30 | }, 31 | }, 32 | { 33 | name: "TXs Sort By Date order anti Chrono", 34 | args: args{ 35 | chrono: false, 36 | }, 37 | startTXs: TXs{ 38 | TX{Timestamp: time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC)}, 39 | TX{Timestamp: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)}, 40 | }, 41 | wantTXs: TXs{ 42 | TX{Timestamp: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC)}, 43 | TX{Timestamp: time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC)}, 44 | }, 45 | }, 46 | } 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | tt.startTXs.SortByDate(tt.args.chrono) 50 | for i := range tt.startTXs { 51 | if !tt.startTXs[i].Timestamp.Equal(tt.wantTXs[i].Timestamp) { 52 | t.Errorf("TXs.SortByDate() startTXs = %v, wantTXs %v", tt.startTXs, tt.wantTXs) 53 | } 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cryptocom/csv_exchange_supercharger.go: -------------------------------------------------------------------------------- 1 | package cryptocom 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "github.com/fiscafacile/CryptoFiscaFacile/utils" 10 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | type csvSupercharger struct { 15 | txsByCategory wallet.TXsByCategory 16 | } 17 | 18 | type csvExSuperchargerTX struct { 19 | Time time.Time 20 | ID string 21 | Currency string 22 | Amount decimal.Decimal 23 | Description string 24 | } 25 | 26 | func (cdc *CryptoCom) ParseCSVExchangeSupercharger(reader io.Reader) (err error) { 27 | const SOURCE = "Crypto.com Exchange SuperCharger CSV :" 28 | csvReader := csv.NewReader(reader) 29 | records, err := csvReader.ReadAll() 30 | if err == nil { 31 | for _, r := range records { 32 | if r[0] != "create_time_utc" { 33 | tx := csvExSuperchargerTX{} 34 | tx.Time, err = time.Parse("2006-01-02 15:04:05", r[0]) 35 | if err != nil { 36 | log.Println("Error Parsing Time : ", r[0]) 37 | } 38 | tx.ID = utils.GetUniqueID(SOURCE + tx.Time.String()) 39 | tx.Currency = r[1] 40 | tx.Amount, err = decimal.NewFromString(r[2]) 41 | if err != nil { 42 | log.Println("Error Parsing Amount : ", r[2]) 43 | } 44 | tx.Description = r[3] 45 | cdc.csvExSuperchargerTXs = append(cdc.csvExSuperchargerTXs, tx) 46 | t := wallet.TX{Timestamp: tx.Time, ID: tx.ID, Note: SOURCE + " " + tx.Description} 47 | t.Items = make(map[string]wallet.Currencies) 48 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount}) 49 | cdc.csvSupercharger.txsByCategory["Minings"] = append(cdc.csvSupercharger.txsByCategory["Minings"], t) 50 | } 51 | } 52 | } 53 | return 54 | } 55 | -------------------------------------------------------------------------------- /localbitcoin/csv_test.go: -------------------------------------------------------------------------------- 1 | package localbitcoin 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_CSVParseTradeExemple(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | csv string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "ParseTradeCSV CashIn", 16 | csv: "id,created_at,buyer,seller,trade_type,btc_amount,btc_traded,fee_btc,btc_amount_less_fee,btc_final,fiat_amount,fiat_fee,fiat_per_btc,currency,exchange_rate,transaction_released_at,online_provider,reference\n53688525,2019-11-29 06:34:25+00:00,moquette31,honestrade,ONLINE_SELL,0.09999955,0.09999955,0.00,0.09999955,0.09999955,6011.30,0.00,60113.27,HKD,60113.27,2019-11-29 06:48:36+00:00,NATIONAL_BANK,L53688525BVYQBX", 17 | wantErr: false, 18 | }, 19 | } 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | lb := New() 23 | err := lb.ParseTradeCSV(strings.NewReader(tt.csv)) 24 | if (err != nil) != tt.wantErr { 25 | t.Errorf("LocalBitcoin.ParseTradeCSV() error = %v, wantErr %v", err, tt.wantErr) 26 | } 27 | }) 28 | } 29 | } 30 | 31 | func Test_CSVLBParseTransferExemple(t *testing.T) { 32 | tests := []struct { 33 | name string 34 | csv string 35 | wantErr bool 36 | }{ 37 | { 38 | name: "ParseTransferCSV CashIn", 39 | csv: "TXID, Created, Received, Sent, TXtype, TXdesc, TXNotes\n,2019-11-29T07:29:43+00:00,,1.76351515,Send to address,32F5pyzpge5KEi3CNZV5z9kE8d9ciqkm8k,", 40 | wantErr: false, 41 | }, 42 | } 43 | for _, tt := range tests { 44 | t.Run(tt.name, func(t *testing.T) { 45 | lb := New() 46 | err := lb.ParseTransferCSV(strings.NewReader(tt.csv)) 47 | if (err != nil) != tt.wantErr { 48 | t.Errorf("LocalBitcoin.ParseTransferCSV() error = %v, wantErr %v", err, tt.wantErr) 49 | } 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /bitstamp/bitstamp.go: -------------------------------------------------------------------------------- 1 | package bitstamp 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/source" 5 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 6 | ) 7 | 8 | type Bitstamp struct { 9 | api api 10 | csvTXs []csvTX 11 | done chan error 12 | TXsByCategory wallet.TXsByCategory 13 | Sources source.Sources 14 | } 15 | 16 | func New() *Bitstamp { 17 | bs := &Bitstamp{} 18 | bs.done = make(chan error) 19 | bs.TXsByCategory = make(wallet.TXsByCategory) 20 | bs.Sources = make(map[string]source.Source) 21 | return bs 22 | } 23 | 24 | func (bs *Bitstamp) GetAPIAllTXs() { 25 | err := bs.api.getAllTXs() 26 | if err != nil { 27 | bs.done <- err 28 | return 29 | } 30 | bs.done <- nil 31 | } 32 | 33 | func (bs *Bitstamp) MergeTXs() { 34 | // Merge TX without Duplicates 35 | bs.TXsByCategory.AddUniq(bs.api.txsByCategory) 36 | } 37 | 38 | func (bs *Bitstamp) WaitFinish(account string) error { 39 | err := <-bs.done 40 | // Add 3916 Source infos 41 | if _, ok := bs.Sources["Bitstamp"]; ok { 42 | if bs.Sources["Bitstamp"].OpeningDate.After(bs.api.firstTimeUsed) { 43 | src := bs.Sources["Bitstamp"] 44 | src.OpeningDate = bs.api.firstTimeUsed 45 | bs.Sources["Bitstamp"] = src 46 | } 47 | if bs.Sources["Bitstamp"].ClosingDate.Before(bs.api.lastTimeUsed) { 48 | src := bs.Sources["Bitstamp"] 49 | src.ClosingDate = bs.api.lastTimeUsed 50 | bs.Sources["Bitstamp"] = src 51 | } 52 | } else { 53 | bs.Sources["Bitstamp"] = source.Source{ 54 | Crypto: true, 55 | AccountNumber: account, 56 | OpeningDate: bs.api.firstTimeUsed, 57 | ClosingDate: bs.api.lastTimeUsed, 58 | LegalName: "Bitstamp Ltd", 59 | Address: "5 New Street Square,\nLondon EC4A 3TW,\nRoyaume-Uni", 60 | URL: "https://bitstamp.com", 61 | } 62 | } 63 | return err 64 | } 65 | -------------------------------------------------------------------------------- /bittrex/bittrex.go: -------------------------------------------------------------------------------- 1 | package bittrex 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/source" 5 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 6 | ) 7 | 8 | type Bittrex struct { 9 | api api 10 | done chan error 11 | TXsByCategory wallet.TXsByCategory 12 | Sources source.Sources 13 | } 14 | 15 | func New() *Bittrex { 16 | btrx := &Bittrex{} 17 | btrx.done = make(chan error) 18 | btrx.TXsByCategory = make(wallet.TXsByCategory) 19 | btrx.Sources = make(source.Sources) 20 | return btrx 21 | } 22 | 23 | func (btrx *Bittrex) GetAPIAllTXs() { 24 | err := btrx.api.getAllTXs() 25 | if err != nil { 26 | btrx.done <- err 27 | return 28 | } 29 | btrx.done <- nil 30 | } 31 | 32 | func (btrx *Bittrex) MergeTXs() { 33 | // Merge TX without Duplicates 34 | btrx.TXsByCategory.AddUniq(btrx.api.txsByCategory) 35 | } 36 | 37 | func (btrx *Bittrex) WaitFinish(account string) error { 38 | err := <-btrx.done 39 | // Add 3916 Source infos 40 | if _, ok := btrx.Sources["Bittrex"]; ok { 41 | if btrx.Sources["Bittrex"].OpeningDate.After(btrx.api.firstTimeUsed) { 42 | src := btrx.Sources["Bittrex"] 43 | src.OpeningDate = btrx.api.firstTimeUsed 44 | btrx.Sources["Bittrex"] = src 45 | } 46 | if btrx.Sources["Bittrex"].ClosingDate.Before(btrx.api.lastTimeUsed) { 47 | src := btrx.Sources["Bittrex"] 48 | src.ClosingDate = btrx.api.lastTimeUsed 49 | btrx.Sources["Bittrex"] = src 50 | } 51 | } else { 52 | btrx.Sources["Bittrex"] = source.Source{ 53 | Crypto: true, 54 | AccountNumber: account, 55 | OpeningDate: btrx.api.firstTimeUsed, 56 | ClosingDate: btrx.api.lastTimeUsed, 57 | LegalName: "Bittrex International GmbH", 58 | Address: "Dr. Grass-Strasse 12, 9490 Vaduz,\nPrincipality of Liechtenstein", 59 | URL: "https://global.bittrex.com", 60 | } 61 | } 62 | return err 63 | } 64 | -------------------------------------------------------------------------------- /binance/binance.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "github.com/fiscafacile/CryptoFiscaFacile/source" 5 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 6 | "time" 7 | ) 8 | 9 | type Binance struct { 10 | api api 11 | csvTXs []csvTX 12 | done chan error 13 | TXsByCategory wallet.TXsByCategory 14 | Sources source.Sources 15 | } 16 | 17 | func New() *Binance { 18 | b := &Binance{} 19 | b.done = make(chan error) 20 | b.TXsByCategory = make(wallet.TXsByCategory) 21 | b.Sources = make(source.Sources) 22 | return b 23 | } 24 | 25 | func (b *Binance) GetAPIAllTXs(loc *time.Location) { 26 | err := b.api.getAllTXs(loc) 27 | if err != nil { 28 | b.done <- err 29 | return 30 | } 31 | b.done <- nil 32 | } 33 | 34 | func (b *Binance) MergeTXs() { 35 | // Merge TX without Duplicates 36 | b.TXsByCategory.AddUniq(b.api.txsByCategory) 37 | } 38 | 39 | func (b *Binance) WaitFinish(account string) error { 40 | err := <-b.done 41 | // Add 3916 Source infos 42 | if _, ok := b.Sources["Binance"]; ok { 43 | if b.Sources["Binance"].OpeningDate.After(b.api.firstTimeUsed) { 44 | src := b.Sources["Binance"] 45 | src.OpeningDate = b.api.firstTimeUsed 46 | b.Sources["Binance"] = src 47 | } 48 | if b.Sources["Binance"].ClosingDate.Before(b.api.lastTimeUsed) { 49 | src := b.Sources["Binance"] 50 | src.ClosingDate = b.api.lastTimeUsed 51 | b.Sources["Binance"] = src 52 | } 53 | } else { 54 | b.Sources["Binance"] = source.Source{ 55 | Crypto: true, 56 | AccountNumber: account, 57 | OpeningDate: b.api.firstTimeUsed, 58 | ClosingDate: b.api.lastTimeUsed, 59 | LegalName: "Binance Europe Services Limited", 60 | Address: "LEVEL G (OFFICE 1/1235), QUANTUM HOUSE,75 ABATE RIGORD STREET, TA' XBIEXXBX 1120\nMalta", 61 | URL: "https://www.binance.com/fr", 62 | } 63 | } 64 | return err 65 | } 66 | -------------------------------------------------------------------------------- /coinbase/csv_test.go: -------------------------------------------------------------------------------- 1 | package coinbase 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_CSVParseExemple(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | csv string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "ParseCSV Deposit", 16 | csv: "Timestamp,Transaction Type,Asset,Quantity Transacted,EUR Spot Price at Transaction,EUR Subtotal,EUR Total (inclusive of fees),EUR Fees,Notes\n2017-01-04T17:34:46Z,Receive,BTC,0.00979287,0.00,\"\",\"\",\"\",\"Received 0,00979287 BTC from an external account\"", 17 | wantErr: false, 18 | }, 19 | { 20 | name: "ParseCSV CashOut", 21 | csv: "Timestamp,Transaction Type,Asset,Quantity Transacted,EUR Spot Price at Transaction,EUR Subtotal,EUR Total (inclusive of fees),EUR Fees,Notes\n2017-08-10T13:26:40Z,Sell,BTC,0.00979287,2909.26,28.49,26.50,1.99,\"Sold 0,00979287 BTC for 26,50 € EUR\"", 22 | wantErr: false, 23 | }, 24 | { 25 | name: "ParseCSV CashIn", 26 | csv: "Timestamp,Transaction Type,Asset,Quantity Transacted,EUR Spot Price at Transaction,EUR Subtotal,EUR Total (inclusive of fees),EUR Fees,Notes\n2017-08-16T06:37:40Z,Buy,BTC,0.00728461,3431.89,25.00,26.49,1.49,\"Bought 0,00728461 BTC for 26,49 € EUR\"", 27 | wantErr: false, 28 | }, 29 | { 30 | name: "ParseCSV Withdrawals", 31 | csv: "Timestamp,Transaction Type,Asset,Quantity Transacted,EUR Spot Price at Transaction,EUR Subtotal,EUR Total (inclusive of fees),EUR Fees,Notes\n2017-11-08T13:55:22Z,Send,BTC,0.15465874,6460.85,\"\",\"\",\"\",\"Sent 0,15465874 BTC to 1FBkm4BVF1bL164KD3sz4WXGwnkULZaG6X\"", 32 | wantErr: false, 33 | }, 34 | } 35 | for _, tt := range tests { 36 | t.Run(tt.name, func(t *testing.T) { 37 | cb := New() 38 | err := cb.ParseCSV(strings.NewReader(tt.csv)) 39 | if (err != nil) != tt.wantErr { 40 | t.Errorf("Coinbase.ParseCSV() error = %v, wantErr %v", err, tt.wantErr) 41 | } 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cryptocom/csv_exchange_stake.go: -------------------------------------------------------------------------------- 1 | package cryptocom 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "github.com/fiscafacile/CryptoFiscaFacile/utils" 10 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | type csvStake struct { 15 | txsByCategory wallet.TXsByCategory 16 | } 17 | 18 | type csvExStakeTX struct { 19 | Time time.Time 20 | ID string 21 | Stake wallet.Currency 22 | Apr string 23 | Interest wallet.Currency 24 | Status string 25 | } 26 | 27 | func (cdc *CryptoCom) ParseCSVExchangeStake(reader io.Reader) (err error) { 28 | const SOURCE = "Crypto.com Exchange Stake CSV :" 29 | csvReader := csv.NewReader(reader) 30 | records, err := csvReader.ReadAll() 31 | if err == nil { 32 | for _, r := range records { 33 | if r[0] != "create_time_utc" { 34 | tx := csvExStakeTX{} 35 | tx.Time, err = time.Parse("2006-01-02 15:04:05.000", r[0]) 36 | if err != nil { 37 | log.Println("Error Parsing Time : ", r[0]) 38 | } 39 | tx.ID = utils.GetUniqueID(SOURCE + tx.Time.String()) 40 | tx.Stake.Code = r[1] 41 | tx.Stake.Amount, err = decimal.NewFromString(r[2]) 42 | if err != nil { 43 | log.Println("Error Parsing Stake.Amount : ", r[2]) 44 | } 45 | tx.Apr = r[3] 46 | tx.Interest.Code = r[4] 47 | tx.Interest.Amount, err = decimal.NewFromString(r[5]) 48 | if err != nil { 49 | log.Println("Error Parsing Interest.Amount : ", r[5]) 50 | } 51 | tx.Status = r[6] 52 | cdc.csvExStakeTXs = append(cdc.csvExStakeTXs, tx) 53 | t := wallet.TX{Timestamp: tx.Time, ID: tx.ID, Note: SOURCE + " " + tx.Stake.Amount.String() + " " + tx.Stake.Code + " " + tx.Apr} 54 | t.Items = make(map[string]wallet.Currencies) 55 | t.Items["To"] = append(t.Items["To"], tx.Interest) 56 | cdc.csvStake.txsByCategory["Interests"] = append(cdc.csvStake.txsByCategory["Interests"], t) 57 | } 58 | } 59 | } 60 | return 61 | } 62 | -------------------------------------------------------------------------------- /wallet/coinlayer.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/http" 7 | "os" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/nanobox-io/golang-scribble" 12 | // "github.com/shopspring/decimal" 13 | "gopkg.in/resty.v1" 14 | ) 15 | 16 | type HistoricalData struct { 17 | Success bool `json:"success"` 18 | Terms string `json:"terms"` 19 | Privacy string `json:"privacy"` 20 | Timestamp int `json:"timestamp"` 21 | Target string `json:"target"` 22 | Historical bool `json:"historical"` 23 | Date string `json:"date"` 24 | Rates map[string]float64 `json:"rates"` 25 | } 26 | 27 | type CoinLayer struct { 28 | } 29 | 30 | func CoinLayerSetKey(key string) error { 31 | return os.Setenv("COINLAYER_KEY", key) 32 | } 33 | 34 | func (api CoinLayer) GetExchangeRates(date time.Time, native string) (rates HistoricalData, err error) { 35 | db, err := scribble.New("./Cache", nil) 36 | if err != nil { 37 | return 38 | } 39 | err = db.Read("CoinLayer", native+"-"+date.UTC().Format("2006-01-02"), &rates) 40 | if err != nil { 41 | if os.Getenv("COINLAYER_KEY") == "" { 42 | return rates, errors.New("Need CoinLayer Key") 43 | } 44 | url := "http://api.coinlayer.com/" + date.UTC().Format("2006-01-02") + "?access_key=" + os.Getenv("COINLAYER_KEY") + "&target=" + native 45 | resp, err := resty.R().SetHeaders(map[string]string{ 46 | "Accept": "application/json", 47 | }).Get(url) 48 | if err != nil { 49 | return rates, err 50 | } 51 | if resp.StatusCode() != http.StatusOK { 52 | err = errors.New("Error Status : " + strconv.Itoa(resp.StatusCode())) 53 | return rates, err 54 | } 55 | err = json.Unmarshal(resp.Body(), &rates) 56 | if err != nil { 57 | return rates, err 58 | } 59 | err = db.Write("CoinLayer", native+"-"+date.UTC().Format("2006-01-02"), rates) 60 | return rates, err 61 | } 62 | return 63 | } 64 | -------------------------------------------------------------------------------- /bittrex/csv_test.go: -------------------------------------------------------------------------------- 1 | package bittrex 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestBittrex_ParseCSV(t *testing.T) { 9 | tests := []struct { 10 | name string 11 | csv string 12 | wantErr bool 13 | }{ 14 | { 15 | name: "ParseCSV Exchange LIMIT_BUY", 16 | csv: "40a1adf3-e43d-4e34-8bc6-5d1f8g2c3z6d,BTC-GBYTE,12/22/2017 9:10:21 AM,LIMIT_BUY,0.03700010,6.32828762,0.00000000,0.00058503,0.23401959,0.03697992,False,,0.00000000,False,12/22/2017 9:10:23 AM,0,\n", 17 | wantErr: false, 18 | }, 19 | { 20 | name: "ParseCSV Exchange MARKET_BUY", 21 | csv: "48545d51-8bc6-e43d-4e34-5d1f8g2c3z6d,BTC-MANA,1/15/2017 9:04:21 PM,MARKET_BUY,0.03705010,10.32828762,0.00000000,0.00088503,0.23401359,0.08697992,False,,0.00000000,False,1/15/2020 9:06:50 PM,0,\n", 22 | wantErr: false, 23 | }, 24 | { 25 | name: "ParseCSV Exchange LIMIT_SELL", 26 | csv: "48545d51-8bc6-e43d-4e34-5d1f8g2c3z6d,BTC-MCO,1/9/2020 9:04:21 PM,LIMIT_SELL,0.03705010,10.32828762,0.00000000,0.00088503,0.23401359,0.08697992,False,,0.00000000,False,1/9/2020 9:06:50 PM,0,\n", 27 | wantErr: false, 28 | }, 29 | { 30 | name: "ParseCSV Exchange MARKET_SELL", 31 | csv: "40a1adf3-e43d-4e34-8bc6-5d1f8g2c3z6d,BTC-GBYTE,12/22/2017 9:10:21 AM,MARKET_SELL,0.03700010,6.32828762,0.00000000,0.00058503,0.23401959,0.03697992,False,,0.00000000,False,12/22/2017 9:10:23 AM,0,\n", 32 | wantErr: false, 33 | }, 34 | { 35 | name: "ParseCSV Exchange CEILING_MARKET_BUY", 36 | csv: "48545d51-8bc6-e43d-4e34-5d1f8g2c3z6d,BTC-MANA,1/15/2017 9:04:21 PM,CEILING_MARKET_BUY,0.03705010,10.32828762,0.00000000,0.00088503,0.23401359,0.08697992,False,,0.00000000,False,1/15/2020 9:06:50 PM,0,\n", 37 | wantErr: false, 38 | }, 39 | } 40 | for _, tt := range tests { 41 | t.Run(tt.name, func(t *testing.T) { 42 | btrx := New() 43 | err := btrx.ParseCSV(strings.NewReader(tt.csv)) 44 | if (err != nil) != tt.wantErr { 45 | t.Errorf("Bittrex.ParseCSV() error = %v, wantErr %v", err, tt.wantErr) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bittrex/api_test.go: -------------------------------------------------------------------------------- 1 | package bittrex 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBittrexAPI_sign(t *testing.T) { 8 | type args struct { 9 | apiKey string 10 | secretKey string 11 | timestamp string 12 | ressource string 13 | method string 14 | hash string 15 | queryParamEncoded string 16 | } 17 | tests := []struct { 18 | name string 19 | args args 20 | wantSign string 21 | }{ 22 | { 23 | name: "sign deposit", 24 | args: args{ 25 | apiKey: "apiKey", 26 | secretKey: "apiSecret", 27 | timestamp: "1620319842000", 28 | ressource: "deposits/closed", 29 | method: "GET", 30 | hash: "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", 31 | queryParamEncoded: "?status=COMPLETED&pageSize=200", 32 | }, 33 | wantSign: "d412349b312138676da649a5f9c14a286eeca3cc5d6d29d5ce6ec7cac60b50d0256f385423442034be67202cd0b9ae043ee8bc45c383d9dfdde58fadceded14a", 34 | }, 35 | { 36 | name: "sign orders", 37 | args: args{ 38 | apiKey: "apiKey", 39 | secretKey: "apiSecret", 40 | timestamp: "1620319842000", 41 | ressource: "orders/closed", 42 | method: "GET", 43 | hash: "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", 44 | queryParamEncoded: "?pageSize=200", 45 | }, 46 | wantSign: "53217db44dad750db36c16cc815d7aa23c586d52dfe79bc79c36bace29dd53b7595d803505c4e2518b110fd0d1bfa10c0fa6bf18448835063b8f0f84a4931304", 47 | }, 48 | } 49 | for _, tt := range tests { 50 | t.Run(tt.name, func(t *testing.T) { 51 | btrx := New() 52 | btrx.NewAPI(tt.args.apiKey, tt.args.secretKey, false) 53 | _, sig := btrx.api.sign(tt.args.timestamp, tt.args.ressource, tt.args.method, tt.args.hash, tt.args.queryParamEncoded) 54 | if sig != tt.wantSign { 55 | t.Errorf("Bittrex.sign() sig = %v, wantSign %v", sig, tt.wantSign) 56 | } 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /wallet/coinapi.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "log" 7 | "net/http" 8 | "os" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/nanobox-io/golang-scribble" 13 | "github.com/shopspring/decimal" 14 | "gopkg.in/resty.v1" 15 | ) 16 | 17 | type Rate struct { 18 | Time time.Time `json:"time"` 19 | Quote string `json:"asset_id_quote"` 20 | Rate decimal.Decimal `json:"rate"` 21 | } 22 | 23 | type ExchangeRates struct { 24 | Base string `json:"asset_id_base"` 25 | Rates []Rate `json:"rates"` 26 | } 27 | 28 | type CoinAPI struct { 29 | } 30 | 31 | func CoinAPISetKey(key string) error { 32 | return os.Setenv("COINAPI_KEY", key) 33 | } 34 | 35 | func (api CoinAPI) GetExchangeRates(date time.Time, native string) (rates ExchangeRates, err error) { 36 | db, err := scribble.New("./Cache", nil) 37 | if err != nil { 38 | return 39 | } 40 | err = db.Read("CoinAPI/exchangerate", native+"-"+date.UTC().Format("2006-01-02-15-04-05"), &rates) 41 | if err != nil { 42 | if os.Getenv("COINAPI_KEY") == "" { 43 | return rates, errors.New("Need CoinAPI Key") 44 | } 45 | hour := 0 46 | for len(rates.Rates) == 0 && hour < 15 { 47 | url := "http://rest.coinapi.io/v1/exchangerate/" + native + "?invert=true&time=" + date.Add(time.Duration(hour)*time.Hour).UTC().Format(time.RFC3339) 48 | resp, err := resty.R().SetHeaders(map[string]string{ 49 | "Accept": "application/json", 50 | "X-CoinAPI-Key": os.Getenv("COINAPI_KEY"), 51 | }).Get(url) 52 | if err != nil { 53 | return rates, err 54 | } 55 | if resp.StatusCode() != http.StatusOK { 56 | err = errors.New("Error Status : " + strconv.Itoa(resp.StatusCode())) 57 | return rates, err 58 | } 59 | err = json.Unmarshal(resp.Body(), &rates) 60 | if err != nil { 61 | return rates, err 62 | } 63 | if len(rates.Rates) == 0 { 64 | hour += 1 65 | if hour == 15 { 66 | log.Println("CoinAPI Get void Rates:", url) 67 | } 68 | } 69 | } 70 | if len(rates.Rates) != 0 { 71 | err = db.Write("CoinAPI/exchangerate", native+"-"+date.UTC().Format("2006-01-02-15-04-05"), rates) 72 | } 73 | return rates, err 74 | } 75 | return 76 | } 77 | -------------------------------------------------------------------------------- /blockchain/json.go: -------------------------------------------------------------------------------- 1 | package blockchain 2 | 3 | import ( 4 | "encoding/json" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | type Wallet struct { 14 | Address string `json:address` 15 | Amount decimal.Decimal `json:amount` 16 | } 17 | 18 | type Wallets []Wallet 19 | 20 | type JsonTX struct { 21 | TxID string `json:txid` 22 | Date string `json:date` 23 | Fee decimal.Decimal `json:fee,omitempty` 24 | From Wallets `json:from,omitempty` 25 | To Wallets `json:to,omitempty` 26 | } 27 | 28 | func (bc *BlockChain) ParseTXsJSON(reader io.Reader, currency string) (err error) { 29 | var txs []JsonTX 30 | jsonDecoder := json.NewDecoder(reader) 31 | err = jsonDecoder.Decode(&txs) 32 | if err == nil { 33 | bc.jsonTXs = append(bc.jsonTXs, txs...) 34 | // Fill TXsByCategory 35 | for _, tx := range txs { 36 | date, err := time.Parse("Jan 2, 2006 15:04:05 PM", tx.Date) 37 | if err != nil { 38 | log.Println("BlockChain JSON :", err) 39 | } 40 | t := wallet.TX{Timestamp: date, Note: "BlockChain JSON : " + tx.TxID} 41 | t.Items = make(map[string]wallet.Currencies) 42 | if !tx.Fee.IsZero() { 43 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: currency, Amount: tx.Fee}) 44 | } 45 | haveFrom := false 46 | for _, w := range tx.From { 47 | haveFrom = true 48 | t.Note += " " + w.Address 49 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: currency, Amount: w.Amount.Sub(tx.Fee)}) 50 | } 51 | t.Note += " ->" 52 | haveTo := false 53 | for _, w := range tx.To { 54 | haveTo = true 55 | t.Note += " " + w.Address 56 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: currency, Amount: w.Amount}) 57 | } 58 | if haveFrom && haveTo { 59 | bc.TXsByCategory["Transfers"] = append(bc.TXsByCategory["Transfers"], t) 60 | } else if haveFrom { 61 | bc.TXsByCategory["Withdrawals"] = append(bc.TXsByCategory["Withdrawals"], t) 62 | } else if haveTo { 63 | bc.TXsByCategory["Deposits"] = append(bc.TXsByCategory["Deposits"], t) 64 | } else { 65 | bc.TXsByCategory["Fees"] = append(bc.TXsByCategory["Fees"], t) 66 | } 67 | } 68 | } 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /poloniex/csv_distributions.go: -------------------------------------------------------------------------------- 1 | package poloniex 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "github.com/fiscafacile/CryptoFiscaFacile/source" 10 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | type csvDistributionsTX struct { 15 | Date time.Time // 2020-09-22 16 | Currency string // REPV2 17 | Amount decimal.Decimal // 82.50028940 18 | Wallet string // exchange 19 | } 20 | 21 | func (pl *Poloniex) ParseDistributionsCSV(reader io.Reader, account string) (err error) { 22 | firstTimeUsed := time.Now() 23 | lastTimeUsed := time.Date(2009, time.January, 1, 0, 0, 0, 0, time.UTC) 24 | const SOURCE = "Poloniex Distributions CSV :" 25 | csvReader := csv.NewReader(reader) 26 | records, err := csvReader.ReadAll() 27 | if err == nil { 28 | for _, r := range records { 29 | if r[0] != "date" { 30 | tx := csvDistributionsTX{} 31 | tx.Date, err = time.Parse("2006-01-02", r[0]) 32 | if err != nil { 33 | log.Println(SOURCE, "Error Parsing Date", r[0]) 34 | } 35 | tx.Currency = r[1] 36 | tx.Amount, err = decimal.NewFromString(r[2]) 37 | if err != nil { 38 | log.Println(SOURCE, "Error Parsing Amount", r[2]) 39 | } 40 | tx.Wallet = r[3] 41 | pl.csvDistributionsTXs = append(pl.csvDistributionsTXs, tx) 42 | if tx.Date.Before(firstTimeUsed) { 43 | firstTimeUsed = tx.Date 44 | } 45 | if tx.Date.After(lastTimeUsed) { 46 | lastTimeUsed = tx.Date 47 | } 48 | // Fill TXsByCategory 49 | t := wallet.TX{Timestamp: tx.Date, Note: SOURCE + " " + tx.Wallet} 50 | t.Items = make(map[string]wallet.Currencies) 51 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount}) 52 | pl.TXsByCategory["Deposits"] = append(pl.TXsByCategory["Deposits"], t) 53 | } 54 | } 55 | } 56 | if _, ok := pl.Sources["Poloniex"]; !ok { 57 | pl.Sources["Poloniex"] = source.Source{ 58 | Crypto: true, 59 | AccountNumber: account, 60 | OpeningDate: firstTimeUsed, 61 | ClosingDate: lastTimeUsed, 62 | LegalName: "Polo Digital Assets Ltd", 63 | Address: "F20, 1st Floor, Eden Plaza,\nEden Island,\nSeychelles", 64 | URL: "https://poloniex.com/", 65 | } 66 | } 67 | return 68 | } 69 | -------------------------------------------------------------------------------- /hitbtc/hitbtc.go: -------------------------------------------------------------------------------- 1 | package hitbtc 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/fiscafacile/CryptoFiscaFacile/source" 7 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 8 | ) 9 | 10 | type HitBTC struct { 11 | api api 12 | csvTradeTXs []csvTradeTX 13 | csvTransactionTXs []csvTransactionTX 14 | done chan error 15 | TXsByCategory wallet.TXsByCategory 16 | emails []string 17 | Sources source.Sources 18 | } 19 | 20 | func New() *HitBTC { 21 | hb := &HitBTC{} 22 | hb.done = make(chan error) 23 | hb.TXsByCategory = make(wallet.TXsByCategory) 24 | hb.Sources = make(map[string]source.Source) 25 | return hb 26 | } 27 | 28 | func (hb *HitBTC) GetAPIAllTXs() { 29 | err := hb.api.getAllTXs() 30 | if err != nil { 31 | hb.done <- err 32 | return 33 | } 34 | hb.done <- nil 35 | } 36 | 37 | func (hb *HitBTC) MergeTXs() { 38 | // Merge TX without Duplicates 39 | hb.TXsByCategory.AddUniq(hb.api.txsByCategory) 40 | } 41 | 42 | func (hb *HitBTC) WaitFinish(account string) error { 43 | err := <-hb.done 44 | // Add 3916 Source infos 45 | if _, ok := hb.Sources["HitBTC"]; ok { 46 | if hb.Sources["HitBTC"].OpeningDate.After(hb.api.firstTimeUsed) { 47 | src := hb.Sources["HitBTC"] 48 | src.OpeningDate = hb.api.firstTimeUsed 49 | hb.Sources["HitBTC"] = src 50 | } 51 | if hb.Sources["HitBTC"].ClosingDate.Before(hb.api.lastTimeUsed) { 52 | src := hb.Sources["HitBTC"] 53 | src.ClosingDate = hb.api.lastTimeUsed 54 | hb.Sources["HitBTC"] = src 55 | } 56 | } else { 57 | hb.Sources["HitBTC"] = source.Source{ 58 | Crypto: true, 59 | AccountNumber: account, 60 | OpeningDate: hb.api.firstTimeUsed, 61 | ClosingDate: hb.api.lastTimeUsed, 62 | LegalName: "Hit Tech Solutions Development Ltd.", 63 | Address: "Suite 15, Oliaji Trade Centre, Francis Rachel Street,\nVictoria, Mahe,\nSeychelles", 64 | URL: "https://hitbtc.com", 65 | } 66 | } 67 | return err 68 | } 69 | 70 | func csvCurrencyCure(c string) string { 71 | return strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(c, "BCHSV", "BSV"), "BCHABC", "BCH"), "BCCF", "BCH")) 72 | } 73 | 74 | func apiCurrencyCure(c string) string { 75 | // https://blog.hitbtc.com/we-will-change-the-ticker-of-bchabc-to-bch-and-bchsv-will-be-displayed-as-hbv/ 76 | return strings.ReplaceAll(strings.ReplaceAll(c, "BCHA", "BCH"), "BCHOLD", "BCH") 77 | } 78 | -------------------------------------------------------------------------------- /poloniex/csv_deposits.go: -------------------------------------------------------------------------------- 1 | package poloniex 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "github.com/fiscafacile/CryptoFiscaFacile/source" 10 | "github.com/fiscafacile/CryptoFiscaFacile/utils" 11 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 12 | "github.com/shopspring/decimal" 13 | ) 14 | 15 | type csvDepositsTX struct { 16 | Date time.Time 17 | ID string 18 | Currency string 19 | Amount decimal.Decimal 20 | Address string 21 | Status string 22 | } 23 | 24 | func (pl *Poloniex) ParseDepositsCSV(reader io.Reader, account string) (err error) { 25 | firstTimeUsed := time.Now() 26 | lastTimeUsed := time.Date(2009, time.January, 1, 0, 0, 0, 0, time.UTC) 27 | const SOURCE = "Poloniex Deposits CSV :" 28 | csvReader := csv.NewReader(reader) 29 | records, err := csvReader.ReadAll() 30 | if err == nil { 31 | for _, r := range records { 32 | if r[0] != "Date" { 33 | tx := csvDepositsTX{} 34 | tx.Date, err = time.Parse("2006-01-02 15:04:05", r[0]) 35 | if err != nil { 36 | log.Println(SOURCE, "Error Parsing Date", r[0]) 37 | } 38 | tx.ID = utils.GetUniqueID(SOURCE + tx.Date.String()) 39 | tx.Currency = r[1] 40 | tx.Amount, err = decimal.NewFromString(r[2]) 41 | if err != nil { 42 | log.Println(SOURCE, "Error Parsing Amount", r[2]) 43 | } 44 | tx.Address = r[3] 45 | tx.Status = r[4] 46 | pl.csvDepositsTXs = append(pl.csvDepositsTXs, tx) 47 | if tx.Date.Before(firstTimeUsed) { 48 | firstTimeUsed = tx.Date 49 | } 50 | if tx.Date.After(lastTimeUsed) { 51 | lastTimeUsed = tx.Date 52 | } 53 | // Fill TXsByCategory 54 | t := wallet.TX{Timestamp: tx.Date, ID: tx.ID, Note: SOURCE + " " + tx.Address + " " + tx.Status} 55 | t.Items = make(map[string]wallet.Currencies) 56 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount}) 57 | pl.TXsByCategory["Deposits"] = append(pl.TXsByCategory["Deposits"], t) 58 | } 59 | } 60 | } 61 | if _, ok := pl.Sources["Poloniex"]; !ok { 62 | pl.Sources["Poloniex"] = source.Source{ 63 | Crypto: true, 64 | AccountNumber: account, 65 | OpeningDate: firstTimeUsed, 66 | ClosingDate: lastTimeUsed, 67 | LegalName: "Polo Digital Assets Ltd", 68 | Address: "F20, 1st Floor, Eden Plaza,\nEden Island,\nSeychelles", 69 | URL: "https://poloniex.com/", 70 | } 71 | } 72 | return 73 | } 74 | -------------------------------------------------------------------------------- /Inputs/Kraken/simulation-bitcoin-fr.csv: -------------------------------------------------------------------------------- 1 | "txid","refid","time","type","subtype","aclass","asset","amount","fee","balance" 2 | "","0000000-000000-000001","2017-07-17 00:00:01","deposit","","currency","ZEUR",9005.0000,0.0000,"" 3 | "00000A","0000000-000000-000001","2017-07-17 00:00:01","deposit","","currency","ZEUR",9005.0000,0.0000,9005.0000 4 | "00000B","000002","2017-07-17 00:01:01","trade","","currency","ZEUR",-9005.0000,0.0000,0.0000 5 | "00000C","000002","2017-07-17 00:01:01","trade","","currency","XXBT",5.0000000000,0.0000000000,5.0000000000 6 | "","0000000-000000-000003","2017-12-15 00:00:01","deposit","","currency","ZEUR",16346.0000,0.0000,"" 7 | "00000D","0000000-000000-000003","2017-12-15 00:00:01","deposit","","currency","ZEUR",16346.0000,0.0000,16346.0000 8 | "00000E","000004","2017-12-15 00:01:01","trade","","currency","ZEUR",-16346.0000,0.0000,0.0000 9 | "00000F","000004","2017-12-15 00:01:01","trade","","currency","XXBT",1.0000000000,0.0000000000,6.0000000000 10 | "00000G","000005","2019-01-09 00:01:01","trade","","currency","XXBT",-1.0000000000,0.0000000000,5.0000000000 11 | "00000H","000005","2019-01-09 00:01:01","trade","","currency","ZEUR",3744.0000000000,0.0000000000,3744.0000000000 12 | "","0000000-000000-000006","2019-03-24 00:00:01","deposit","","currency","ZEUR",5536.0000,0.0000,"" 13 | "00000I","0000000-000000-000006","2019-03-24 00:00:01","deposit","","currency","ZEUR",5536.0000,0.0000,9280.0000 14 | "00000J","000008","2019-03-24 00:01:01","trade","","currency","ZEUR",-9280.0000,0.0000,0.0000 15 | "00000K","000008","2019-03-24 00:01:01","trade","","currency","XXBT",2.5000000000,0.0000000000,7.5000000000 16 | "","0000000-000000-000007","2019-03-27 00:00:01","deposit","","currency","ZEUR",225.0000,0.0000,"" 17 | "00000L","0000000-000000-000007","2019-03-27 00:00:01","deposit","","currency","ZEUR",225.0000,0.0000,225.0000 18 | "00000M","000009","2019-03-27 00:01:01","trade","","currency","ZEUR",-225.0000,0.0000,0.0000 19 | "00000N","000009","2019-03-27 00:01:01","trade","","currency","GRIN",100.0000000000,0.0000000000,100.0000000000 20 | "00000O","000010","2019-06-16 00:01:01","trade","","currency","GRIN",-50.0000000000,0.0000000000,50.0000000000 21 | "00000P","000010","2019-06-16 00:01:01","trade","","currency","ZEUR",224.0000000000,0.0000000000,224.0000000000 22 | "00000Q","000011","2019-06-25 00:01:01","trade","","currency","XXBT",-1.0000000000,0.0000000000,6.5000000000 23 | "00000R","000011","2019-06-25 00:01:01","trade","","currency","ZEUR",10463.0000000000,0.0000000000,10687.0000000000 24 | 25 | -------------------------------------------------------------------------------- /cryptocom/csv_exchange_transfer.go: -------------------------------------------------------------------------------- 1 | package cryptocom 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "github.com/fiscafacile/CryptoFiscaFacile/utils" 10 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | type csvTransfer struct { 15 | txsByCategory wallet.TXsByCategory 16 | } 17 | 18 | type csvExTransferTX struct { 19 | Time time.Time 20 | ID string 21 | Currency string 22 | Amount decimal.Decimal 23 | Fee decimal.Decimal 24 | Address string 25 | Status string 26 | } 27 | 28 | func (cdc *CryptoCom) ParseCSVExchangeTransfer(reader io.Reader) (err error) { 29 | const SOURCE = "Crypto.com Exchange Transfer CSV :" 30 | csvReader := csv.NewReader(reader) 31 | records, err := csvReader.ReadAll() 32 | if err == nil { 33 | for _, r := range records { 34 | if r[0] != "create_time_utc" { 35 | tx := csvExTransferTX{} 36 | tx.Time, err = time.Parse("2006-01-02 15:04:05.000", r[0]) 37 | if err != nil { 38 | log.Println(SOURCE, "Error Parsing Time", r[0]) 39 | } 40 | tx.ID = utils.GetUniqueID(SOURCE + tx.Time.String()) 41 | tx.Currency = r[1] 42 | tx.Amount, err = decimal.NewFromString(r[2]) 43 | if err != nil { 44 | log.Println(SOURCE, "Error Parsing Amount", r[2]) 45 | } 46 | tx.Fee, err = decimal.NewFromString(r[3]) 47 | if err != nil { 48 | log.Println(SOURCE, "Error Parsing Fee", r[3]) 49 | } 50 | tx.Address = r[4] 51 | tx.Status = r[5] 52 | if tx.Address == "EARLY_SWAP_BONUS_DEPOSIT" || 53 | tx.Address == "INTERNAL_DEPOSIT" { 54 | cdc.csvExTransferTXs = append(cdc.csvExTransferTXs, tx) 55 | t := wallet.TX{Timestamp: tx.Time, ID: tx.ID, Note: SOURCE + " " + tx.Address} 56 | t.Items = make(map[string]wallet.Currencies) 57 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount}) 58 | cdc.csvTransfer.txsByCategory["Deposits"] = append(cdc.csvTransfer.txsByCategory["Deposits"], t) 59 | } else { 60 | cdc.csvExTransferTXs = append(cdc.csvExTransferTXs, tx) 61 | t := wallet.TX{Timestamp: tx.Time, ID: tx.ID, Note: SOURCE + " " + tx.Address} 62 | t.Items = make(map[string]wallet.Currencies) 63 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount}) 64 | cdc.csvTransfer.txsByCategory["Withdrawals"] = append(cdc.csvTransfer.txsByCategory["Withdrawals"], t) 65 | } 66 | } 67 | } 68 | } 69 | return 70 | } 71 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Pour contribuer, dans la majorité des cas vous allez vouloir ajouter le support pour une plateforme manquante. Je vous porposer de prendre comme base la Source Crypto.com existante qui est la plus aboutie. 2 | 3 | Il vous faudra : 4 | 5 | - [x] copier tout le répertoire, épurer ce dont vous avez pas besoin (ex: pas d'API, CSV uniquement, ou l'inverse) et renomer pour votre plateforme. 6 | 7 | - [x] adapter le code pour votre format de CSV ou votre API. 8 | 9 | - [x] vérifier que vos TXs n'aient aucun `Amount` négatif. 10 | 11 | - [x] vérifier que les `Items` ne soient que des `To`/`From`/`Fee` (il faut raisoner aux bornes de la Source en question, pas du portefeuille global, faire abstraction des autres Sources). 12 | 13 | - [x] vérifier que les `Deposits` n'aient pas de `From` (la source d'un dépot n'est pas à intégrer dans votre portefeuille relatif à la Source en cours, sauf si elle vous appartient auquel cas ce n'est pas un `Withdrawals` mais un `Transfers` qu'il faut faire). 14 | 15 | - [x] vérifier que si vos `Deposits` ont des `Fee`, ils aient bien été payés par vous (souvent c'est la source du dépot qui paye les frais). 16 | 17 | - [x] vérifier que les `Withrawals` n'aient pas de `To` (la destination d'un retrait n'est pas à intégrer dans votre portefeuille relatif à la Source en cours, sauf si elle vous appartient auquel cas ce n'est pas un `Deposits` mais un `Transfers` qu'il faut faire). 18 | 19 | - [x] vérifier que les `Transfers` aient une balance nulle (frais compris). 20 | 21 | - [x] si possible faites des tests unitaires pour vérifier votre module uniquement. 22 | 23 | - [x] ajouter votre Source dans la doc du README.md (prendre exemple sur un autre). 24 | 25 | - [x] ajouter le support de votre Source dans le main.go (ajouter le flag, la création de l'instance, la récupération des TXs (ParseCSV ou GetAPI) puis intégration des TXs par catégorie au portefeuille global). 26 | 27 | - [x] faire un test d'ensemble en ne fournissant que votre source à l'outil compilé 28 | 29 | - [x] contrôler que la balance correspond à ce que vous attendiez (attention à la date qui par défaut est au 1 Jan 2021), si une ou plusieurs balance ne correspond pas, il faut isoler un coin et suivre toutes ses transactions avec les options `-txs_display` et `-curr_filter`. 30 | 31 | - [x] faire un `-check` pour vérifier que vos TXs répondent au critères de l'outil. 32 | 33 | - [x] refaire un test d'ensemble avec une autre Source pour voir si des `Deposits` et des `Withdrawals` correspondants sont fusionés en `Transfers`. Si ce n'est pas le cas, vérifier les montant (doivent correspondre) ou les dates (doivent etre à 24h près). 34 | 35 | - [ ] bien faire un go fmt, puis git commit / push et enfin demander le Pull Request. 36 | 37 | Pour toute question, vous pouvez venir sur le groupe telegram donné en bas de la doc officielle. 38 | -------------------------------------------------------------------------------- /kraken/kraken.go: -------------------------------------------------------------------------------- 1 | package kraken 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/fiscafacile/CryptoFiscaFacile/source" 7 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 8 | ) 9 | 10 | type Kraken struct { 11 | api api 12 | csvTXs []csvTX 13 | done chan error 14 | TXsByCategory wallet.TXsByCategory 15 | Sources source.Sources 16 | } 17 | 18 | func New() *Kraken { 19 | kr := &Kraken{} 20 | kr.done = make(chan error) 21 | kr.TXsByCategory = make(map[string]wallet.TXs) 22 | kr.Sources = make(source.Sources) 23 | return kr 24 | } 25 | 26 | func (kr *Kraken) GetAPIAllTXs() { 27 | err := kr.api.getAPITxs() 28 | if err != nil { 29 | kr.done <- err 30 | return 31 | } 32 | kr.done <- nil 33 | } 34 | 35 | func (kr *Kraken) MergeTXs() { 36 | // Merge TX without Duplicates 37 | kr.TXsByCategory.AddUniq(kr.api.txsByCategory) 38 | } 39 | 40 | func (kr *Kraken) WaitFinish(account string) error { 41 | err := <-kr.done 42 | // Add 3916 Source infos 43 | if _, ok := kr.Sources["Kraken"]; ok { 44 | if kr.Sources["Kraken"].OpeningDate.After(kr.api.firstTimeUsed) { 45 | src := kr.Sources["Kraken"] 46 | src.OpeningDate = kr.api.firstTimeUsed 47 | kr.Sources["Kraken"] = src 48 | } 49 | if kr.Sources["Kraken"].ClosingDate.Before(kr.api.lastTimeUsed) { 50 | src := kr.Sources["Kraken"] 51 | src.ClosingDate = kr.api.lastTimeUsed 52 | kr.Sources["Kraken"] = src 53 | } 54 | } else { 55 | kr.Sources["Kraken"] = source.Source{ 56 | Crypto: true, 57 | AccountNumber: account, 58 | OpeningDate: kr.api.firstTimeUsed, 59 | ClosingDate: kr.api.lastTimeUsed, 60 | LegalName: "Payward Ltd.", 61 | Address: "6th Floor,\nOne London Wall,\nLondon, EC2Y 5EB,\nRoyaume-Uni", 62 | URL: "https://www.kraken.com", 63 | } 64 | } 65 | return err 66 | } 67 | 68 | func ReplaceAssets(assetToReplace string) string { 69 | assetRplcr := strings.NewReplacer( 70 | "ADA.S", "ADA", 71 | "ATOM.S", "ATOM", 72 | "DOT.S", "DOT", 73 | "ETH2.S", "ETH", 74 | "ETH2", "ETH", 75 | "EUR.HOLD", "EUR", 76 | "EUR.M", "EUR", 77 | "FLOW.S", "FLOW", 78 | "FLOWH", "FLOW", 79 | "FLOWH.S", "FLOW", 80 | "KAVA.S", "KAVA", 81 | "KFEE", "FEE", 82 | "KSM.S", "KSM", 83 | "SOL.S", "SOL", 84 | "USD.HOLD", "USD", 85 | "USD.M", "USD", 86 | "XBT", "BTC", 87 | "XBT.M", "BTC", 88 | "XETC", "ETC", 89 | "XETH", "ETH", 90 | "XLTC", "LTC", 91 | "XMLN", "MLN", 92 | "XREP", "REP", 93 | "XTZ", "XTZ", 94 | "XTZ.S", "XTZ", 95 | "XXBT", "BTC", 96 | "XXDG", "DOGE", 97 | "XXLM", "XLM", 98 | "XXMR", "XMR", 99 | "XXRP", "XRP", 100 | "XZEC", "ZEC", 101 | "ZAUD", "AUD", 102 | "ZCAD", "CAD", 103 | "ZEUR", "EUR", 104 | "ZGBP", "GBP", 105 | "ZJPY", "JPY", 106 | "ZRX", "ZRX", 107 | "ZUSD", "USD", 108 | ) 109 | return assetRplcr.Replace(assetToReplace) 110 | } 111 | -------------------------------------------------------------------------------- /mycelium/csv.go: -------------------------------------------------------------------------------- 1 | package mycelium 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "regexp" 8 | "strings" 9 | "time" 10 | 11 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 12 | "github.com/shopspring/decimal" 13 | ) 14 | 15 | type CsvTX struct { 16 | Account string 17 | ID string 18 | DestAddress string 19 | Timestamp time.Time 20 | Value decimal.Decimal 21 | Currency string 22 | Label string 23 | } 24 | 25 | func (mc *MyCelium) ParseCSV(reader io.Reader) (err error) { 26 | csvReader := csv.NewReader(reader) 27 | records, err := csvReader.ReadAll() 28 | if err == nil { 29 | for _, r := range records { 30 | if r[0] != "Account" { 31 | tx := CsvTX{} 32 | tx.Account = r[0] 33 | tx.ID = r[1] 34 | tx.DestAddress = r[2] 35 | tx.Timestamp, err = time.Parse("2006-01-02T15:04Z", r[3]) 36 | if err != nil { 37 | log.Println("Error Parsing Timestamp : ", r[3]) 38 | } 39 | tx.Value, err = decimal.NewFromString(r[4]) 40 | if err != nil { 41 | log.Println("Error Parsing Value : ", r[4]) 42 | } 43 | tx.Currency = r[5] 44 | tx.Label = r[6] 45 | mc.CsvTXs = append(mc.CsvTXs, tx) 46 | // Fill TXsByCategory 47 | if tx.Value.IsPositive() { 48 | t := wallet.TX{Timestamp: tx.Timestamp, Note: "MyCelium CSV : " + tx.ID + " from " + tx.DestAddress + " " + tx.Label} 49 | t.Items = make(map[string]wallet.Currencies) 50 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: ticker(tx.Currency), Amount: tx.Value}) 51 | // t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.Currency, Amount: tx.Fees}) 52 | mc.TXsByCategory["Deposits"] = append(mc.TXsByCategory["Deposits"], t) 53 | } else if tx.Value.IsNegative() { 54 | t := wallet.TX{Timestamp: tx.Timestamp, Note: "MyCelium CSV : " + tx.ID + " to " + tx.DestAddress + " " + tx.Label} 55 | t.Items = make(map[string]wallet.Currencies) 56 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: ticker(tx.Currency), Amount: tx.Value.Neg()}) 57 | if strings.Contains(tx.Label, "crypto_payment") { 58 | r := regexp.MustCompile(`\(([+-]?([0-9]*[.,])?[0-9]+)€\)`) 59 | if r.MatchString(tx.Label) { 60 | to, err := decimal.NewFromString(r.FindStringSubmatch(tx.Label)[1]) 61 | if err == nil { 62 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: "EUR", Amount: to}) 63 | } 64 | } 65 | mc.TXsByCategory["CashOut"] = append(mc.TXsByCategory["CashOut"], t) 66 | } else { 67 | // t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.Currency, Amount: tx.Fees}) 68 | mc.TXsByCategory["Withdrawals"] = append(mc.TXsByCategory["Withdrawals"], t) 69 | } 70 | } else { 71 | log.Println("Unmanaged ", tx) 72 | } 73 | } 74 | } 75 | } 76 | return 77 | } 78 | 79 | func ticker(currency string) string { 80 | if currency == "Bitcoin" { 81 | return "BTC" 82 | } 83 | return "???" 84 | } 85 | -------------------------------------------------------------------------------- /cryptocom/cryptocom.go: -------------------------------------------------------------------------------- 1 | package cryptocom 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/fiscafacile/CryptoFiscaFacile/source" 7 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 8 | ) 9 | 10 | type CryptoCom struct { 11 | apiEx apiEx 12 | jsonEx jsonEx 13 | csvSpotTrade csvSpotTrade 14 | csvStake csvStake 15 | csvSupercharger csvSupercharger 16 | csvTransfer csvTransfer 17 | csvAppCryptoTXs []csvAppCryptoTX 18 | csvExTransferTXs []csvExTransferTX 19 | csvExSpotTradeTXs []csvExSpotTradeTX 20 | csvExStakeTXs []csvExStakeTX 21 | csvExSuperchargerTXs []csvExSuperchargerTX 22 | done chan error 23 | TXsByCategory wallet.TXsByCategory 24 | Sources source.Sources 25 | } 26 | 27 | func New() *CryptoCom { 28 | cdc := &CryptoCom{} 29 | cdc.done = make(chan error) 30 | cdc.TXsByCategory = make(wallet.TXsByCategory) 31 | cdc.jsonEx.txsByCategory = make(wallet.TXsByCategory) 32 | cdc.csvSpotTrade.txsByCategory = make(wallet.TXsByCategory) 33 | cdc.csvStake.txsByCategory = make(wallet.TXsByCategory) 34 | cdc.csvSupercharger.txsByCategory = make(wallet.TXsByCategory) 35 | cdc.csvTransfer.txsByCategory = make(wallet.TXsByCategory) 36 | cdc.Sources = make(source.Sources) 37 | return cdc 38 | } 39 | 40 | func (cdc *CryptoCom) GetAPIExchangeTXs(loc *time.Location) { 41 | err := cdc.apiEx.getAllTXs(loc) 42 | if err != nil { 43 | cdc.done <- err 44 | return 45 | } 46 | cdc.done <- nil 47 | } 48 | 49 | func (cdc *CryptoCom) MergeTXs() { 50 | // Merge TX without Duplicates 51 | cdc.TXsByCategory.Add(cdc.jsonEx.txsByCategory) // Add only because UNI Supercharger as a date bug 52 | cdc.TXsByCategory.AddUniq(cdc.apiEx.txsByCategory) 53 | cdc.TXsByCategory.AddUniq(cdc.csvStake.txsByCategory) 54 | cdc.TXsByCategory.AddUniq(cdc.csvSupercharger.txsByCategory) 55 | cdc.TXsByCategory.AddUniq(cdc.csvSpotTrade.txsByCategory) 56 | cdc.TXsByCategory.AddUniq(cdc.csvTransfer.txsByCategory) 57 | } 58 | 59 | func (cdc *CryptoCom) WaitFinish(account string) error { 60 | err := <-cdc.done 61 | // Add 3916 Source infos 62 | if _, ok := cdc.Sources["CdC Exchange"]; ok { 63 | if cdc.Sources["CdC Exchange"].OpeningDate.After(cdc.apiEx.firstTimeUsed) { 64 | src := cdc.Sources["CdC Exchange"] 65 | src.OpeningDate = cdc.apiEx.firstTimeUsed 66 | cdc.Sources["CdC Exchange"] = src 67 | } 68 | if cdc.Sources["CdC Exchange"].ClosingDate.Before(cdc.apiEx.lastTimeUsed) { 69 | src := cdc.Sources["CdC Exchange"] 70 | src.ClosingDate = cdc.apiEx.lastTimeUsed 71 | cdc.Sources["CdC Exchange"] = src 72 | } 73 | } else { 74 | cdc.Sources["CdC Exchange"] = source.Source{ 75 | Crypto: true, 76 | AccountNumber: account, 77 | OpeningDate: cdc.apiEx.firstTimeUsed, 78 | ClosingDate: cdc.apiEx.lastTimeUsed, 79 | LegalName: "MCO Malta DAX Limited", 80 | Address: "Level 7, Spinola Park, Triq Mikiel Ang Borg,\nSt Julian's SPK 1000,\nMalte", 81 | URL: "https://crypto.com/exchange", 82 | } 83 | } 84 | return err 85 | } 86 | -------------------------------------------------------------------------------- /cryptocom/json_exporter.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var date = new Date(); 3 | var dateStr = date.toISOString().split(/[T\.]/).slice(0, -1).join(" "); 4 | function getData(method, endpoint, additionalKeys = {}) { 5 | if (method == "POST") { 6 | var data = { 7 | uaTime: dateStr, 8 | securityInfo: { timestamp: dateStr, meta: {} }, 9 | pageSize: 200, 10 | page: 1, 11 | }; 12 | if (additionalKeys) 13 | for (const [key, value] of Object.entries(additionalKeys)) { 14 | data[key] = value; 15 | } 16 | return $.ajax({ 17 | url: "https://crypto.com/fe-ex-api/" + endpoint, 18 | type: "POST", 19 | dataType: "json", 20 | contentType: "application/json;charset=utf-8", 21 | headers: { 22 | "exchange-token": document.cookie.match(/token=([0-9a-zA-Z]+)/)[1], 23 | }, 24 | data: JSON.stringify(data), 25 | }); 26 | } else if (method == "GET") { 27 | return $.ajax({ 28 | url: "https://crypto.com/fe-ex-api/" + endpoint, 29 | type: "GET", 30 | dataType: "json", 31 | contentType: "application/json;charset=utf-8", 32 | headers: { 33 | "exchange-token": document.cookie.match(/token=([0-9a-zA-Z]+)/)[1], 34 | }, 35 | }); 36 | } 37 | } 38 | 39 | $.when( 40 | getData("POST", "record/withdraw_list", { coinSymbol: null }), 41 | getData("POST", "record/deposit_list", { coinSymbol: null }), 42 | getData("POST", "record/staking_interest_history"), 43 | getData("POST", "record/soft_staking_interest_list"), 44 | getData("POST", "record/rebate_trading_fee_history"), 45 | getData( 46 | "GET", 47 | "syndicate/user/activities?isCompleted=true&page=1&pageSize=10" 48 | ), 49 | getData("POST", "record/supercharger_reward_history"), 50 | getData("GET", "referral/bonus/history?page=1&pageSize=200"), 51 | getData("GET", "referral/trade_commission/history?page=1&pageSize=200"), 52 | getData("GET", "referral/reward/info") 53 | ).done(function (withs, deps, cros, sstake, rebs, syn, sup, bon, tcom, rew) { 54 | var j = {}; 55 | if (withs[2].status == 200) j["withs"] = withs[0].data; 56 | if (deps[2].status == 200) j["deps"] = deps[0].data; 57 | if (cros[2].status == 200) j["cros"] = cros[0].data; 58 | if (sstake[2].status == 200) j["sstake"] = sstake[0].data; 59 | if (rebs[2].status == 200) j["rebs"] = rebs[0].data; 60 | if (syn[2].status == 200) j["syn"] = syn[0].data; 61 | if (sup[2].status == 200) j["sup"] = sup[0].data; 62 | if (tcom[2].status == 200) j["tcom"] = tcom[0].data; 63 | if (bon[2].status == 200) j["bon"] = bon[0].data; 64 | if (rew[2].status == 200) j["rew"] = rew[0].data; 65 | // Download the JSON 66 | var o = encodeURI("data:text/json;charset=utf-8," + JSON.stringify(j)); 67 | var link = document.createElement("a"); 68 | link.setAttribute("href", o); 69 | link.setAttribute("download", "CdC_Ex_ExportJS.json"); 70 | document.body.appendChild(link); 71 | link.click(); 72 | link.remove(); 73 | }); 74 | })(); 75 | -------------------------------------------------------------------------------- /hitbtc/api_trades.go: -------------------------------------------------------------------------------- 1 | package hitbtc 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/nanobox-io/golang-scribble" 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | type tradeTX struct { 14 | ID int 15 | OrderID int 16 | ClientOrderID string 17 | Symbol string 18 | Side string 19 | Quantity decimal.Decimal 20 | Price decimal.Decimal 21 | Fee decimal.Decimal 22 | Timestamp time.Time 23 | } 24 | 25 | func (api *api) getTradesTXs() { 26 | const SOURCE = "HitBTC API Trades :" 27 | trades, err := api.getTrades() 28 | if err != nil { 29 | api.doneTrade <- err 30 | return 31 | } 32 | for _, tra := range trades { 33 | tx := tradeTX{} 34 | tx.ID = tra.ID 35 | tx.OrderID = tra.OrderID 36 | tx.ClientOrderID = tra.ClientOrderID 37 | tx.Symbol = apiCurrencyCure(tra.Symbol) 38 | tx.Side = tra.Side 39 | tx.Quantity, err = decimal.NewFromString(tra.Quantity) 40 | if err != nil { 41 | log.Println(SOURCE, "Error Parsing Quantity : ", tra.Quantity) 42 | } 43 | tx.Price, err = decimal.NewFromString(tra.Price) 44 | if err != nil { 45 | log.Println(SOURCE, "Error Parsing Price : ", tra.Price) 46 | } 47 | tx.Fee, err = decimal.NewFromString(tra.Fee) 48 | if err != nil { 49 | log.Println(SOURCE, "Error Parsing Fee : ", tra.Fee) 50 | } 51 | tx.Timestamp = tra.Timestamp 52 | api.tradeTXs = append(api.tradeTXs, tx) 53 | } 54 | api.doneTrade <- nil 55 | } 56 | 57 | type GetTradesResp []struct { 58 | ID int `json:"id"` 59 | OrderID int `json:"orderId"` 60 | ClientOrderID string `json:"clientOrderId"` 61 | Symbol string `json:"symbol"` 62 | Side string `json:"side"` 63 | Quantity string `json:"quantity"` 64 | Price string `json:"price"` 65 | Fee string `json:"fee"` 66 | Timestamp time.Time `json:"timestamp"` 67 | } 68 | 69 | func (api *api) getTrades() (trades GetTradesResp, err error) { 70 | const SOURCE = "HitBTC API Trades :" 71 | useCache := true 72 | db, err := scribble.New("./Cache", nil) 73 | if err != nil { 74 | useCache = false 75 | } 76 | if useCache { 77 | err = db.Read("HitBTC/history", "trades", &trades) 78 | } 79 | if !useCache || err != nil { 80 | method := "history/trades" 81 | resp, err := api.clientTrade.R(). 82 | SetBasicAuth(api.apiKey, api.secretKey). 83 | SetResult(&GetTradesResp{}). 84 | SetError(&ErrorResp{}). 85 | Get(api.basePath + method) 86 | if err != nil { 87 | return trades, errors.New(SOURCE + " Error Requesting") 88 | } 89 | if resp.StatusCode() > 300 { 90 | return trades, errors.New(SOURCE + " Error StatusCode" + strconv.Itoa(resp.StatusCode())) 91 | } 92 | trades = *resp.Result().(*GetTradesResp) 93 | if useCache { 94 | err = db.Write("HitBTC/history", "trades", trades) 95 | if err != nil { 96 | return trades, errors.New(SOURCE + " Error Caching") 97 | } 98 | } 99 | time.Sleep(api.timeBetweenReq) 100 | } 101 | return trades, nil 102 | } 103 | -------------------------------------------------------------------------------- /source/source.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | "time" 7 | 8 | "github.com/360EntSecGroup-Skylar/excelize" 9 | ) 10 | 11 | const URL_3916 = "https://www.impots.gouv.fr/portail/files/formulaires/3916/2021/3916_3425.pdf" 12 | 13 | type Source struct { 14 | Crypto bool 15 | AccountNumber string 16 | OpeningDate time.Time 17 | ClosingDate time.Time 18 | LegalName string 19 | Address string 20 | URL string 21 | } 22 | 23 | type Sources map[string]Source 24 | 25 | func (ss Sources) Add(srcs Sources) { 26 | for k, v := range srcs { 27 | ss[k] = v 28 | } 29 | } 30 | 31 | func (ss Sources) ToXlsx(filename string, loc *time.Location) error { 32 | sanitize := strings.NewReplacer( 33 | "@", "AROBASE", 34 | ".", "POINT", 35 | "-", "TIRET", 36 | ) 37 | if len(ss) > 0 { 38 | f := excelize.NewFile() 39 | for src, s := range ss { 40 | f.NewSheet(src) 41 | if s.Crypto { 42 | f.SetCellValue(src, "A1", "4.1 Désignation du compte d'actifs numériques ouvert, détenu, utilisé ou clos à l'étranger") 43 | f.SetCellValue(src, "A2", "Numéro de compte") 44 | f.SetCellValue(src, "B2", sanitize.Replace(s.AccountNumber)) 45 | f.SetCellValue(src, "A3", "Date d'ouverture*") 46 | f.SetCellValue(src, "B3", s.OpeningDate.In(loc).Format("02-01-2006")) 47 | f.SetCellValue(src, "A4", "Date de clôture*") 48 | f.SetCellValue(src, "B4", s.ClosingDate.In(loc).Format("02-01-2006")) 49 | f.SetCellValue(src, "A5", "Designation de l'organisme gestionnaire du compte") 50 | f.SetCellValue(src, "B5", s.LegalName) 51 | f.SetCellValue(src, "A6", "Adresse de l'organisme gestionnaire du compte") 52 | f.SetCellValue(src, "B6", s.Address) 53 | f.SetCellValue(src, "A7", "URL du site internet de l'organisme gestionnaire du compte") 54 | f.SetCellValue(src, "B7", s.URL) 55 | f.SetCellValue(src, "A8", "* Ce ne sont pas des dates formelles, juste des estimations en fonctions de vos transactions") 56 | } else { 57 | f.SetCellValue(src, "A1", "3.1 Désignation du compte bancaire ouvert, détenu, utilisé ou clos à l'étranger") 58 | f.SetCellValue(src, "A2", "Numéro de compte") 59 | f.SetCellValue(src, "B2", s.AccountNumber) 60 | f.SetCellValue(src, "A3", "Caractéristiques du compte") 61 | f.SetCellValue(src, "B3", "[x] Compte courant") 62 | f.SetCellValue(src, "A4", "Date d'ouverture*") 63 | f.SetCellValue(src, "B4", s.OpeningDate.In(loc).Format("02-01-2006")) 64 | f.SetCellValue(src, "A5", "Date de clôture*") 65 | f.SetCellValue(src, "B5", s.ClosingDate.In(loc).Format("02-01-2006")) 66 | f.SetCellValue(src, "A6", "Designation de l'organisme gestionnaire du compte") 67 | f.SetCellValue(src, "B6", s.LegalName) 68 | f.SetCellValue(src, "A7", "Adresse de l'organisme gestionnaire du compte") 69 | f.SetCellValue(src, "B7", s.Address) 70 | f.SetCellValue(src, "A8", "* Ce ne sont pas des dates formelles, juste des estimations en fonctions de vos transactions") 71 | } 72 | f.SetColWidth(src, "A", "B", 83) 73 | } 74 | f.DeleteSheet("Sheet1") 75 | return f.SaveAs(filename) 76 | } else { 77 | return errors.New("No Sources Available") 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /wallet/coingecko.go: -------------------------------------------------------------------------------- 1 | package wallet 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | "strings" 7 | "time" 8 | 9 | "github.com/nanobox-io/golang-scribble" 10 | "github.com/shopspring/decimal" 11 | "github.com/superoo7/go-gecko/v3" 12 | ) 13 | 14 | type CoinGeckoAPI struct { 15 | httpClient *http.Client 16 | client *coingecko.Client 17 | reqTime []time.Time 18 | } 19 | 20 | type CoinsListItem struct { 21 | ID string `json:"id"` 22 | Symbol string `json:"symbol"` 23 | Name string `json:"name"` 24 | } 25 | 26 | type CoinList []CoinsListItem 27 | 28 | func NewCoinGeckoAPI() (*CoinGeckoAPI, error) { 29 | cg := &CoinGeckoAPI{} 30 | cg.httpClient = &http.Client{ 31 | Timeout: time.Second * 10, 32 | } 33 | cg.client = coingecko.NewClient(cg.httpClient) 34 | db, err := scribble.New("./Cache", nil) 35 | if err != nil { 36 | return nil, err 37 | } 38 | var coinsList CoinList 39 | err = db.Read("CoinGecko/coins", "list", &coinsList) 40 | if err != nil { 41 | cgCoinsList, err := cg.client.CoinsList() 42 | if err != nil { 43 | return nil, err 44 | } 45 | err = db.Write("CoinGecko/coins", "list", cgCoinsList) 46 | return cg, err 47 | } 48 | return cg, err 49 | } 50 | 51 | func (api *CoinGeckoAPI) garbageOldReqTime() { 52 | var reqT []time.Time 53 | for _, t := range api.reqTime { 54 | if t.After(time.Now().Add(-time.Minute)) { 55 | reqT = append(reqT, t) 56 | } 57 | } 58 | api.reqTime = reqT 59 | } 60 | 61 | func (api *CoinGeckoAPI) GetExchangeRates(date time.Time, coin string) (rates ExchangeRates, err error) { 62 | db, err := scribble.New("./Cache", nil) 63 | if err != nil { 64 | return rates, err 65 | } 66 | coin = strings.ReplaceAll(coin, "DSH", "dash") 67 | coin = strings.ReplaceAll(coin, "IOT", "miota") 68 | coin = strings.ReplaceAll(coin, "MEET.ONE", "meetone") 69 | err = db.Read("CoinGecko/coins/history", coin+"-"+date.UTC().Format("2006-01-02"), &rates) 70 | if err != nil { 71 | err = nil 72 | coinID := "" 73 | var coinsList CoinList 74 | err = db.Read("CoinGecko/coins", "list", &coinsList) 75 | if err != nil { 76 | return rates, err 77 | } 78 | for _, c := range coinsList { 79 | if c.Symbol == strings.ToLower(coin) { 80 | coinID = c.ID 81 | break 82 | } 83 | } 84 | if coinID != "" { 85 | for len(api.reqTime) > 1200 { 86 | api.garbageOldReqTime() 87 | } 88 | api.reqTime = append(api.reqTime, time.Now()) 89 | hist, err := api.client.CoinsIDHistory(coinID, date.UTC().Format("02-01-2006"), false) 90 | if err != nil { 91 | return rates, err 92 | } 93 | rates.Base = coin 94 | if hist.MarketData == nil { 95 | err = errors.New("CoinGecko API replied a null MarketData") 96 | } else { 97 | for k, v := range hist.MarketData.CurrentPrice { 98 | r := Rate{Time: date, Quote: strings.ToUpper(k), Rate: decimal.NewFromFloat(v)} 99 | rates.Rates = append(rates.Rates, r) 100 | } 101 | db.Write("CoinGecko/coins/history", coin+"-"+date.UTC().Format("2006-01-02"), rates) 102 | if err != nil { 103 | return rates, err 104 | } 105 | } 106 | } 107 | } 108 | return rates, nil 109 | } 110 | -------------------------------------------------------------------------------- /bitstamp/api_crypto_transactions.go: -------------------------------------------------------------------------------- 1 | package bitstamp 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/davecgh/go-spew/spew" 9 | "github.com/nanobox-io/golang-scribble" 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | type cryptoTX struct { 14 | DateTime time.Time 15 | ID string 16 | Amount decimal.Decimal 17 | Currency string 18 | DestinationAddress string 19 | } 20 | 21 | func (api *api) getCryptoTXs() { 22 | const SOURCE = "Bitstamp API Crypto Transactions :" 23 | cryptoTXs, err := api.getCryptoTransactions() 24 | if err != nil { 25 | api.doneCryptoTrans <- err 26 | return 27 | } 28 | for _, t := range cryptoTXs.Deposits { 29 | tx := cryptoTX{} 30 | tx.ID = t.TxID 31 | tx.DateTime = time.Unix(t.DateTime, 0) 32 | tx.Amount = decimal.NewFromFloat(t.Amount) 33 | tx.Currency = t.Currency 34 | tx.DestinationAddress = t.DestinationAddress 35 | api.depositTXs = append(api.depositTXs, tx) 36 | } 37 | for _, t := range cryptoTXs.Withdrawals { 38 | tx := cryptoTX{} 39 | tx.ID = t.TxID 40 | tx.DateTime = time.Unix(t.DateTime, 0) 41 | tx.Amount = decimal.NewFromFloat(t.Amount) 42 | tx.Currency = t.Currency 43 | tx.DestinationAddress = t.DestinationAddress 44 | api.withdrawalTXs = append(api.withdrawalTXs, tx) 45 | } 46 | api.doneCryptoTrans <- nil 47 | } 48 | 49 | type CryptoTransaction struct { 50 | Currency string `json:"currency"` 51 | DestinationAddress string `json:"destinationAddress"` 52 | TxID string `json:"txid"` 53 | Amount float64 `json:"amount"` 54 | DateTime int64 `json:"datetime"` 55 | } 56 | 57 | type GetCryptoTransactionsResp struct { 58 | Deposits []CryptoTransaction `json:"deposits"` 59 | Withdrawals []CryptoTransaction `json:"withdrawals"` 60 | } 61 | 62 | func (api *api) getCryptoTransactions() (cryptoTXs GetCryptoTransactionsResp, err error) { 63 | const SOURCE = "Bitstamp API Crypto Transactions :" 64 | useCache := true 65 | db, err := scribble.New("./Cache", nil) 66 | if err != nil { 67 | useCache = false 68 | } 69 | if useCache { 70 | err = db.Read("Bitstamp", "crypto-transactions", &cryptoTXs) 71 | } 72 | if !useCache || err != nil { 73 | url := api.basePath + "crypto-transactions/" 74 | req := api.clientCryptoTrans.R(). 75 | SetFormData(map[string]string{ 76 | "limit": "1000", 77 | }) 78 | api.sign(req, "POST", url) 79 | resp, err := req.SetResult(&GetCryptoTransactionsResp{}). 80 | SetError(&ErrorResp{}). 81 | Post(url) 82 | if err != nil { 83 | return cryptoTXs, errors.New(SOURCE + " Error Requesting") 84 | } 85 | if resp.StatusCode() > 300 { 86 | return cryptoTXs, errors.New(SOURCE + " Error StatusCode" + strconv.Itoa(resp.StatusCode())) 87 | } 88 | spew.Dump(*resp) 89 | cryptoTXs = *resp.Result().(*GetCryptoTransactionsResp) 90 | if useCache { 91 | err = db.Write("Bitstamp", "crypto-transactions", cryptoTXs) 92 | if err != nil { 93 | return cryptoTXs, errors.New(SOURCE + " Error Caching") 94 | } 95 | } 96 | time.Sleep(api.timeBetweenReq) 97 | } 98 | return cryptoTXs, nil 99 | } 100 | -------------------------------------------------------------------------------- /poloniex/csv_withdrawals.go: -------------------------------------------------------------------------------- 1 | package poloniex 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/csv" 6 | "encoding/hex" 7 | "io" 8 | "log" 9 | "time" 10 | 11 | "github.com/fiscafacile/CryptoFiscaFacile/category" 12 | "github.com/fiscafacile/CryptoFiscaFacile/source" 13 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 14 | "github.com/shopspring/decimal" 15 | ) 16 | 17 | type csvWithdrawalsTX struct { 18 | Date time.Time 19 | ID string 20 | Currency string 21 | Amount decimal.Decimal 22 | FeeDeducted decimal.Decimal 23 | AmountFee decimal.Decimal 24 | Address string 25 | Status string 26 | } 27 | 28 | func (pl *Poloniex) ParseWithdrawalsCSV(reader io.Reader, cat category.Category, account string) (err error) { 29 | firstTimeUsed := time.Now() 30 | lastTimeUsed := time.Date(2009, time.January, 1, 0, 0, 0, 0, time.UTC) 31 | const SOURCE = "Poloniex Withdrawals CSV :" 32 | csvReader := csv.NewReader(reader) 33 | records, err := csvReader.ReadAll() 34 | if err == nil { 35 | for _, r := range records { 36 | if r[0] != "Date" { 37 | tx := csvWithdrawalsTX{} 38 | tx.Date, err = time.Parse("2006-01-02 15:04:05", r[0]) 39 | if err != nil { 40 | log.Println(SOURCE, "Error Parsing Date", r[0]) 41 | } 42 | tx.Currency = r[1] 43 | tx.Amount, err = decimal.NewFromString(r[2]) 44 | if err != nil { 45 | log.Println(SOURCE, "Error Parsing Amount", r[2]) 46 | } 47 | tx.FeeDeducted, err = decimal.NewFromString(r[3]) 48 | if err != nil { 49 | log.Println(SOURCE, "Error Parsing FeeDeducted", r[3]) 50 | } 51 | tx.AmountFee, err = decimal.NewFromString(r[4]) 52 | if err != nil { 53 | log.Println(SOURCE, "Error Parsing AmountFee", r[4]) 54 | } 55 | tx.Address = r[5] 56 | tx.Status = r[6] 57 | hash := sha256.Sum256([]byte(SOURCE + tx.Date.String())) 58 | tx.ID = hex.EncodeToString(hash[:]) 59 | pl.csvWithdrawalsTXs = append(pl.csvWithdrawalsTXs, tx) 60 | if tx.Date.Before(firstTimeUsed) { 61 | firstTimeUsed = tx.Date 62 | } 63 | if tx.Date.After(lastTimeUsed) { 64 | lastTimeUsed = tx.Date 65 | } 66 | // Fill TXsByCategory 67 | t := wallet.TX{Timestamp: tx.Date, ID: tx.ID, Note: SOURCE + " " + tx.Address + " " + tx.Status} 68 | t.Items = make(map[string]wallet.Currencies) 69 | if is, desc, val, curr := cat.IsTxShit(tx.ID); is { 70 | t.Note += " " + desc 71 | t.Items["Lost"] = append(t.Items["Lost"], wallet.Currency{Code: curr, Amount: val}) 72 | } 73 | if !tx.FeeDeducted.IsZero() { 74 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.Currency, Amount: tx.FeeDeducted}) 75 | } 76 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.Currency, Amount: tx.AmountFee}) 77 | pl.TXsByCategory["Withdrawals"] = append(pl.TXsByCategory["Withdrawals"], t) 78 | } 79 | } 80 | } 81 | if _, ok := pl.Sources["Poloniex"]; !ok { 82 | pl.Sources["Poloniex"] = source.Source{ 83 | Crypto: true, 84 | AccountNumber: account, 85 | OpeningDate: firstTimeUsed, 86 | ClosingDate: lastTimeUsed, 87 | LegalName: "Polo Digital Assets Ltd", 88 | Address: "F20, 1st Floor, Eden Plaza,\nEden Island,\nSeychelles", 89 | URL: "https://poloniex.com/", 90 | } 91 | } 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /hitbtc/csv_transactions.go: -------------------------------------------------------------------------------- 1 | package hitbtc 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "github.com/fiscafacile/CryptoFiscaFacile/source" 10 | "github.com/fiscafacile/CryptoFiscaFacile/utils" 11 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 12 | "github.com/shopspring/decimal" 13 | ) 14 | 15 | type csvTransactionTX struct { 16 | Email string 17 | Date time.Time 18 | OperationID string 19 | Type string 20 | Amount decimal.Decimal 21 | Hash string 22 | MainAccountBalance decimal.Decimal 23 | Currency string 24 | } 25 | 26 | func (hb *HitBTC) ParseCSVTransactions(reader io.Reader) (err error) { 27 | const SOURCE = "HitBTC CSV Transactions :" 28 | csvReader := csv.NewReader(reader) 29 | records, err := csvReader.ReadAll() 30 | if err == nil { 31 | alreadyAsked := []string{} 32 | for _, r := range records { 33 | if r[0] != "Email" { 34 | tx := csvTransactionTX{} 35 | tx.Email = r[0] 36 | tx.Date, err = time.Parse("2006-01-02 15:04:05", r[1]) 37 | if err != nil { 38 | log.Println(SOURCE, "Error Parsing Date", r[1]) 39 | } 40 | tx.OperationID = r[2] 41 | tx.Type = r[3] 42 | tx.Amount, err = decimal.NewFromString(r[4]) 43 | if err != nil { 44 | log.Println(SOURCE, "Error Parsing Amount", r[4]) 45 | } 46 | tx.Hash = r[5] 47 | tx.MainAccountBalance, err = decimal.NewFromString(r[6]) 48 | if err != nil { 49 | log.Println(SOURCE, "Error Parsing MainAccountBalance", r[6]) 50 | } 51 | tx.Currency = csvCurrencyCure(r[7]) 52 | hb.csvTransactionTXs = append(hb.csvTransactionTXs, tx) 53 | hb.emails = utils.AppendUniq(hb.emails, tx.Email) 54 | // Fill TXsByCategory 55 | if tx.Type == "Deposit" { 56 | t := wallet.TX{Timestamp: tx.Date, ID: tx.Hash, Note: SOURCE + " " + tx.OperationID} 57 | t.Items = make(map[string]wallet.Currencies) 58 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount}) 59 | hb.TXsByCategory["Deposits"] = append(hb.TXsByCategory["Deposits"], t) 60 | } else if tx.Type == "Withdrawal" { 61 | t := wallet.TX{Timestamp: tx.Date, ID: tx.Hash, Note: SOURCE + " " + tx.OperationID} 62 | t.Items = make(map[string]wallet.Currencies) 63 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount}) 64 | hb.TXsByCategory["Withdrawals"] = append(hb.TXsByCategory["Withdrawals"], t) 65 | } else if tx.Type == "Transfer to main account" || 66 | tx.Type == "Transfer to trading account" { 67 | // Do not use, it is Source internal transfers 68 | } else { 69 | alreadyAsked = wallet.AskForHelp(SOURCE+tx.Type, tx, alreadyAsked) 70 | } 71 | } 72 | } 73 | } 74 | for _, e := range hb.emails { 75 | if _, ok := hb.Sources["HitBTC_"+e]; !ok { 76 | hb.Sources["HitBTC_"+e] = source.Source{ 77 | Crypto: true, 78 | AccountNumber: e, 79 | OpeningDate: hb.api.firstTimeUsed, 80 | ClosingDate: hb.api.lastTimeUsed, 81 | LegalName: "Hit Tech Solutions Development Ltd.", 82 | Address: "Suite 15, Oliaji Trade Centre, Francis Rachel Street,\nVictoria, Mahe,\nSeychelles", 83 | URL: "https://hitbtc.com", 84 | } 85 | } 86 | } 87 | return 88 | } 89 | -------------------------------------------------------------------------------- /cryptocom/csv_exchange_spot_trade.go: -------------------------------------------------------------------------------- 1 | package cryptocom 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "strings" 8 | "time" 9 | 10 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | type csvSpotTrade struct { 15 | txsByCategory wallet.TXsByCategory 16 | } 17 | 18 | type csvExSpotTradeTX struct { 19 | AccountType string 20 | OrderID string 21 | TradeID string 22 | CreateTimeUTC time.Time 23 | SymbolLeft string 24 | SymbolRight string 25 | Side string 26 | LiquidityIndicator string 27 | TradedPrice decimal.Decimal 28 | TradedQuantity decimal.Decimal 29 | Fee decimal.Decimal 30 | FeeCurrency string 31 | } 32 | 33 | func (cdc *CryptoCom) ParseCSVExchangeSpotTrade(reader io.Reader) (err error) { 34 | const SOURCE = "Crypto.com Exchange Spot Trade CSV :" 35 | alreadyAsked := []string{} 36 | csvReader := csv.NewReader(reader) 37 | records, err := csvReader.ReadAll() 38 | if err == nil { 39 | for _, r := range records { 40 | if r[0] != "account_type" { 41 | tx := csvExSpotTradeTX{} 42 | tx.AccountType = r[0] 43 | tx.OrderID = r[1] 44 | tx.TradeID = r[2] 45 | tx.CreateTimeUTC, err = time.Parse("2006-01-02 15:04:05.000", r[3]) 46 | if err != nil { 47 | log.Println(SOURCE, "Error Parsing CreateTimeUTC", r[3]) 48 | } 49 | symbol := strings.Split(r[4], "_") 50 | tx.SymbolLeft = symbol[0] 51 | tx.SymbolRight = symbol[1] 52 | tx.Side = r[5] 53 | tx.LiquidityIndicator = r[6] 54 | tx.TradedPrice, err = decimal.NewFromString(r[7]) 55 | if err != nil { 56 | log.Println(SOURCE, "Error Parsing TradedPrice", r[7]) 57 | } 58 | tx.TradedQuantity, err = decimal.NewFromString(r[8]) 59 | if err != nil { 60 | log.Println(SOURCE, "Error Parsing TradedQuantity", r[8]) 61 | } 62 | tx.Fee, err = decimal.NewFromString(r[9]) 63 | if err != nil { 64 | log.Println(SOURCE, "Error Parsing Fee", r[9]) 65 | } 66 | tx.FeeCurrency = r[10] 67 | cdc.csvExSpotTradeTXs = append(cdc.csvExSpotTradeTXs, tx) 68 | // Fill txsByCategory 69 | t := wallet.TX{Timestamp: tx.CreateTimeUTC, ID: tx.OrderID + "-" + tx.TradeID, Note: SOURCE + " " + tx.Side + " " + tx.LiquidityIndicator} 70 | t.Items = make(map[string]wallet.Currencies) 71 | if !tx.Fee.IsZero() { 72 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.FeeCurrency, Amount: tx.Fee}) 73 | } 74 | if tx.Side == "BUY" { 75 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.SymbolLeft, Amount: tx.TradedQuantity}) 76 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.SymbolRight, Amount: tx.TradedQuantity.Mul(tx.TradedPrice)}) 77 | cdc.csvSpotTrade.txsByCategory["Exchanges"] = append(cdc.csvSpotTrade.txsByCategory["Exchanges"], t) 78 | } else if tx.Side == "SELL" { 79 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.SymbolLeft, Amount: tx.TradedQuantity}) 80 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.SymbolRight, Amount: tx.TradedQuantity.Mul(tx.TradedPrice)}) 81 | cdc.csvSpotTrade.txsByCategory["Exchanges"] = append(cdc.csvSpotTrade.txsByCategory["Exchanges"], t) 82 | } else { 83 | alreadyAsked = wallet.AskForHelp(SOURCE+" "+tx.Side, tx, alreadyAsked) 84 | } 85 | } 86 | } 87 | } 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /hitbtc/api_account_transactions.go: -------------------------------------------------------------------------------- 1 | package hitbtc 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/nanobox-io/golang-scribble" 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | type accountTX struct { 14 | ID string 15 | Index int64 16 | Type string 17 | Subtype string 18 | Status string 19 | Currency string 20 | Amount decimal.Decimal 21 | CreatedAt time.Time 22 | UpdatedAt time.Time 23 | Fee decimal.Decimal 24 | Hash string 25 | Address string 26 | Confirmations int64 27 | } 28 | 29 | func (api *api) getAccountTXs() { 30 | const SOURCE = "HitBTC API Account Transactions :" 31 | accTXs, err := api.getAccountTransactions() 32 | if err != nil { 33 | api.doneAccTrans <- err 34 | return 35 | } 36 | for _, t := range accTXs { 37 | tx := accountTX{} 38 | tx.ID = t.ID 39 | tx.Index = t.Index 40 | tx.Type = t.Type 41 | tx.Subtype = t.Subtype 42 | tx.Status = t.Status 43 | tx.Currency = apiCurrencyCure(t.Currency) 44 | tx.Amount, err = decimal.NewFromString(t.Amount) 45 | if err != nil { 46 | log.Println(SOURCE, "Error Parsing Amount : ", t.Amount) 47 | } 48 | tx.CreatedAt = t.CreatedAt 49 | tx.UpdatedAt = t.UpdatedAt 50 | if t.Fee != "" { 51 | tx.Fee, err = decimal.NewFromString(t.Fee) 52 | if err != nil { 53 | log.Println(SOURCE, "Error Parsing Fee : ", t.Fee) 54 | } 55 | } 56 | tx.Hash = t.Hash 57 | tx.Address = t.Address 58 | tx.Confirmations = t.Confirmations 59 | api.accountTXs = append(api.accountTXs, tx) 60 | } 61 | api.doneAccTrans <- nil 62 | } 63 | 64 | type GetAccountTransactionsResp []struct { 65 | ID string `json:"id"` 66 | Index int64 `json:"index"` 67 | Type string `json:"type"` 68 | Subtype string `json:"subType,omitempty"` 69 | Status string `json:"status"` 70 | Currency string `json:"currency"` 71 | Amount string `json:"amount"` 72 | CreatedAt time.Time `json:"createdAt"` 73 | UpdatedAt time.Time `json:"updatedAt"` 74 | Fee string `json:"fee,omitempty"` 75 | Hash string `json:"hash,omitempty"` 76 | Address string `json:"address,omitempty"` 77 | Confirmations int64 `json:"confirmations,omitempty"` 78 | } 79 | 80 | func (api *api) getAccountTransactions() (accTXs GetAccountTransactionsResp, err error) { 81 | const SOURCE = "HitBTC API Account Transactions :" 82 | useCache := true 83 | db, err := scribble.New("./Cache", nil) 84 | if err != nil { 85 | useCache = false 86 | } 87 | if useCache { 88 | err = db.Read("HitBTC/account", "transactions", &accTXs) 89 | } 90 | if !useCache || err != nil { 91 | method := "account/transactions" 92 | resp, err := api.clientAccTrans.R(). 93 | SetBasicAuth(api.apiKey, api.secretKey). 94 | SetResult(&GetAccountTransactionsResp{}). 95 | SetError(&ErrorResp{}). 96 | Get(api.basePath + method) 97 | if err != nil { 98 | return accTXs, errors.New(SOURCE + " Error Requesting") 99 | } 100 | if resp.StatusCode() > 300 { 101 | return accTXs, errors.New(SOURCE + " Error StatusCode" + strconv.Itoa(resp.StatusCode())) 102 | } 103 | accTXs = *resp.Result().(*GetAccountTransactionsResp) 104 | if useCache { 105 | err = db.Write("HitBTC/account", "transactions", accTXs) 106 | if err != nil { 107 | return accTXs, errors.New(SOURCE + " Error Caching") 108 | } 109 | } 110 | time.Sleep(api.timeBetweenReq) 111 | } 112 | return accTXs, nil 113 | } 114 | -------------------------------------------------------------------------------- /bittrex/api_withdrawals.go: -------------------------------------------------------------------------------- 1 | package bittrex 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/nanobox-io/golang-scribble" 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | type withdrawalTX struct { 14 | Time time.Time 15 | ID string 16 | CurrencySymbol string 17 | Quantity decimal.Decimal 18 | Fee decimal.Decimal 19 | Address string 20 | Status string 21 | } 22 | 23 | func (api *api) getWithdrawalsTXs() { 24 | const SOURCE = "Bittrex API Withdrawals :" 25 | withdrawals, err := api.getWithdrawals() 26 | if err != nil { 27 | api.doneWithdrawals <- err 28 | return 29 | } 30 | for _, wit := range withdrawals { 31 | tx := withdrawalTX{} 32 | tx.Time = wit.CompletedAt 33 | tx.ID = wit.ID 34 | tx.CurrencySymbol = wit.CurrencySymbol 35 | tx.Quantity, err = decimal.NewFromString(wit.Quantity) 36 | if err != nil { 37 | log.Println(SOURCE, "Error Parsing Quantity : ", wit.Quantity) 38 | } 39 | tx.Address = wit.CryptoAddress 40 | tx.Status = wit.Status 41 | if wit.TxCost != "" { 42 | tx.Fee, err = decimal.NewFromString(wit.TxCost) 43 | if err != nil { 44 | log.Println(SOURCE, "Error Parsing Fee : ", wit.TxCost) 45 | } 46 | } 47 | api.withdrawalTXs = append(api.withdrawalTXs, tx) 48 | } 49 | api.doneWithdrawals <- nil 50 | } 51 | 52 | type GetTransferResponse []struct { 53 | ID string `json:"id"` 54 | CurrencySymbol string `json:"currencySymbol"` 55 | Quantity string `json:"quantity"` 56 | CryptoAddress string `json:"cryptoAddress"` 57 | UpdatedAt time.Time `json:"updatedAt"` 58 | CompletedAt time.Time `json:"completedAt"` 59 | Status string `json:"status"` 60 | TxCost string `json:"txCost"` 61 | TxID string `json:"txId"` 62 | CreatedAt time.Time `json:"createdAt"` 63 | CryptoAddressTag string `json:"cryptoAddressTag,omitempty"` 64 | } 65 | 66 | func (api *api) getWithdrawals() (withdrawalResp GetTransferResponse, err error) { 67 | const SOURCE = "Bittrex API Withdrawals :" 68 | useCache := true 69 | db, err := scribble.New("./Cache", nil) 70 | if err != nil { 71 | useCache = false 72 | } 73 | if useCache { 74 | err = db.Read("Bittrex/withdrawals", "closed", &withdrawalResp) 75 | } 76 | if !useCache || err != nil { 77 | hash := api.hash("") 78 | ressource := "withdrawals/closed" 79 | timestamp, signature := api.sign("", ressource, "GET", hash, "?pageSize=200&status=COMPLETED") 80 | resp, err := api.clientWithdrawals.R(). 81 | SetQueryParams(map[string]string{ 82 | "pageSize": "200", 83 | "status": "COMPLETED", 84 | }). 85 | SetHeaders(map[string]string{ 86 | "Accept": "application/json", 87 | "Content-Type": "application/json", 88 | "Api-Content-Hash": hash, 89 | "Api-Key": api.apiKey, 90 | "Api-Signature": signature, 91 | "Api-Timestamp": timestamp, 92 | }). 93 | SetResult(&GetTransferResponse{}). 94 | // SetError(&ErrorResp{}). 95 | Get(api.basePath + ressource) 96 | if err != nil { 97 | return withdrawalResp, errors.New(SOURCE + " Error Requesting") 98 | } 99 | if resp.StatusCode() > 300 { 100 | return withdrawalResp, errors.New(SOURCE + " Error StatusCode" + strconv.Itoa(resp.StatusCode())) 101 | } 102 | withdrawalResp = *resp.Result().(*GetTransferResponse) 103 | if useCache { 104 | err = db.Write("Bittrex/withdrawals", "closed", withdrawalResp) 105 | if err != nil { 106 | return withdrawalResp, errors.New(SOURCE + " Error Caching") 107 | } 108 | } 109 | time.Sleep(api.timeBetweenReq) 110 | } 111 | return withdrawalResp, nil 112 | } 113 | -------------------------------------------------------------------------------- /bittrex/api_deposits.go: -------------------------------------------------------------------------------- 1 | package bittrex 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/nanobox-io/golang-scribble" 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | type depositTX struct { 14 | Time time.Time 15 | ID string 16 | CurrencySymbol string 17 | Quantity decimal.Decimal 18 | Fee decimal.Decimal 19 | Address string 20 | Status string 21 | } 22 | 23 | func (api *api) getDepositsTXs() { 24 | const SOURCE = "Bittrex API Deposits :" 25 | deposits, err := api.getDeposits() 26 | if err != nil { 27 | api.doneDeposits <- err 28 | return 29 | } 30 | for _, dep := range deposits { 31 | tx := depositTX{} 32 | tx.Time = dep.CompletedAt 33 | tx.ID = dep.ID 34 | tx.CurrencySymbol = dep.CurrencySymbol 35 | tx.Quantity, err = decimal.NewFromString(dep.Quantity) 36 | if err != nil { 37 | log.Println(SOURCE, "Error Parsing Quantity : ", dep.Quantity) 38 | } 39 | tx.Address = dep.CryptoAddress 40 | tx.Status = dep.Status 41 | if dep.TxCost != "" { 42 | tx.Fee, err = decimal.NewFromString(dep.TxCost) 43 | if err != nil { 44 | log.Println(SOURCE, "Error Parsing Fee : ", dep.TxCost) 45 | } 46 | } 47 | api.depositTXs = append(api.depositTXs, tx) 48 | } 49 | api.doneDeposits <- nil 50 | } 51 | 52 | type GetDepositResponse []struct { 53 | ID string `json:"id"` 54 | CurrencySymbol string `json:"currencySymbol"` 55 | Quantity string `json:"quantity"` 56 | CryptoAddress string `json:"cryptoAddress"` 57 | Confirmations int `json:"confirmations"` 58 | UpdatedAt time.Time `json:"updatedAt"` 59 | CompletedAt time.Time `json:"completedAt"` 60 | Status string `json:"status"` 61 | Source string `json:"source"` 62 | TxCost string `json:"txCost"` 63 | TxID string `json:"txId"` 64 | CreatedAt time.Time `json:"createdAt"` 65 | CryptoAddressTag string `json:"cryptoAddressTag,omitempty"` 66 | } 67 | 68 | func (api *api) getDeposits() (depositResp GetDepositResponse, err error) { 69 | const SOURCE = "Bittrex API Deposits :" 70 | useCache := true 71 | db, err := scribble.New("./Cache", nil) 72 | if err != nil { 73 | useCache = false 74 | } 75 | if useCache { 76 | err = db.Read("Bittrex/deposits", "closed", &depositResp) 77 | } 78 | if !useCache || err != nil { 79 | hash := api.hash("") 80 | ressource := "deposits/closed" 81 | timestamp, signature := api.sign("", ressource, "GET", hash, "?pageSize=200&status=COMPLETED") 82 | resp, err := api.clientDeposits.R(). 83 | SetQueryParams(map[string]string{ 84 | "pageSize": "200", 85 | "status": "COMPLETED", 86 | }). 87 | SetHeaders(map[string]string{ 88 | "Accept": "application/json", 89 | "Content-Type": "application/json", 90 | "Api-Content-Hash": hash, 91 | "Api-Key": api.apiKey, 92 | "Api-Signature": signature, 93 | "Api-Timestamp": timestamp, 94 | }). 95 | SetResult(&GetDepositResponse{}). 96 | // SetError(&ErrorResp{}). 97 | Get(api.basePath + ressource) 98 | if err != nil { 99 | return depositResp, errors.New(SOURCE + " Error Requesting") 100 | } 101 | if resp.StatusCode() > 300 { 102 | return depositResp, errors.New(SOURCE + " Error StatusCode" + strconv.Itoa(resp.StatusCode())) 103 | } 104 | depositResp = *resp.Result().(*GetDepositResponse) 105 | if useCache { 106 | err = db.Write("Bittrex/deposits", "closed", depositResp) 107 | if err != nil { 108 | return depositResp, errors.New(SOURCE + " Error Caching") 109 | } 110 | } 111 | time.Sleep(api.timeBetweenReq) 112 | } 113 | return depositResp, nil 114 | } 115 | -------------------------------------------------------------------------------- /category/category.go: -------------------------------------------------------------------------------- 1 | package category 2 | 3 | import ( 4 | "github.com/shopspring/decimal" 5 | ) 6 | 7 | type Category struct { 8 | csvCategories []csvCategorie 9 | } 10 | 11 | func New() *Category { 12 | cat := &Category{} 13 | return cat 14 | } 15 | 16 | func (cat Category) IsTxCashOut(txid string) (is bool, desc string, val decimal.Decimal, curr string) { 17 | is = false 18 | for _, a := range cat.csvCategories { 19 | if a.txID == txid && a.kind == "OUT" { 20 | is = true 21 | desc = a.description 22 | val = a.value 23 | curr = a.currency 24 | return 25 | } 26 | } 27 | return 28 | } 29 | 30 | func (cat Category) IsTxCashIn(txid string) (is bool, desc string, val decimal.Decimal, curr string) { 31 | is = false 32 | for _, a := range cat.csvCategories { 33 | if a.txID == txid && a.kind == "IN" { 34 | is = true 35 | desc = a.description 36 | val = a.value 37 | curr = a.currency 38 | return 39 | } 40 | } 41 | return 42 | } 43 | 44 | func (cat Category) IsTxExchange(txid string) (is bool, desc string, val decimal.Decimal, curr string) { 45 | is = false 46 | for _, a := range cat.csvCategories { 47 | if a.txID == txid && a.kind == "EXC" { 48 | is = true 49 | desc = a.description 50 | val = a.value 51 | curr = a.currency 52 | return 53 | } 54 | } 55 | return 56 | } 57 | 58 | func (cat Category) HasCustody(txid string) (is bool, desc string, val decimal.Decimal) { 59 | is = false 60 | for _, a := range cat.csvCategories { 61 | if a.txID == txid && a.kind == "CUS" { 62 | is = true 63 | desc = a.description 64 | val = a.value 65 | return 66 | } 67 | } 68 | return 69 | } 70 | 71 | func (cat Category) IsTxGift(txid string) (is bool, desc string) { 72 | is = false 73 | for _, a := range cat.csvCategories { 74 | if a.txID == txid && a.kind == "GIFT" { 75 | is = true 76 | desc = a.description 77 | return 78 | } 79 | } 80 | return 81 | } 82 | 83 | func (cat Category) IsTxAirDrop(txid string) (is bool, desc string) { 84 | is = false 85 | for _, a := range cat.csvCategories { 86 | if a.txID == txid && a.kind == "AIR" { 87 | is = true 88 | desc = a.description 89 | return 90 | } 91 | } 92 | return 93 | } 94 | 95 | func (cat Category) IsTxInterest(txid string) (is bool, desc string) { 96 | is = false 97 | for _, a := range cat.csvCategories { 98 | if a.txID == txid && a.kind == "INT" { 99 | is = true 100 | desc = a.description 101 | return 102 | } 103 | } 104 | return 105 | } 106 | 107 | func (cat Category) IsTxShit(txid string) (is bool, desc string, val decimal.Decimal, curr string) { 108 | is = false 109 | for _, a := range cat.csvCategories { 110 | if a.txID == txid && a.kind == "SHIT" { 111 | is = true 112 | desc = a.description 113 | val = a.value 114 | curr = a.currency 115 | return 116 | } 117 | } 118 | return 119 | } 120 | 121 | func (cat Category) IsTxTokenSale(txid string) (is bool, buy string) { 122 | is = false 123 | for _, a := range cat.csvCategories { 124 | if a.txID == txid && a.kind == "TOK" { 125 | is = true 126 | buy = a.description 127 | return 128 | } 129 | } 130 | return 131 | } 132 | 133 | func (cat Category) IsTxFee(txid string) (is bool, fee string) { 134 | is = false 135 | for _, a := range cat.csvCategories { 136 | if a.txID == txid && a.kind == "FEE" { 137 | is = true 138 | fee = a.description 139 | return 140 | } 141 | } 142 | return 143 | } 144 | 145 | func (cat Category) IsTxTransfer(txid string) (is bool, transid string) { 146 | is = false 147 | for _, a := range cat.csvCategories { 148 | if a.txID == txid && a.kind == "TRANS" { 149 | is = true 150 | transid = a.description 151 | return 152 | } 153 | } 154 | return 155 | } 156 | -------------------------------------------------------------------------------- /monero/csv.go: -------------------------------------------------------------------------------- 1 | package monero 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/fiscafacile/CryptoFiscaFacile/category" 11 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 12 | "github.com/shopspring/decimal" 13 | ) 14 | 15 | type CsvTX struct { 16 | BlockHeight string 17 | Epoch time.Time 18 | Date string 19 | Direction string 20 | Amount decimal.Decimal 21 | AtomicAmount decimal.Decimal 22 | Fee decimal.Decimal 23 | TxID string 24 | Label string 25 | SubaddrAccount string 26 | PaymentId string 27 | } 28 | 29 | func (xmr *Monero) ParseCSV(reader io.Reader, cat category.Category) (err error) { 30 | const SOURCE = "Monero CSV :" 31 | csvReader := csv.NewReader(reader) 32 | records, err := csvReader.ReadAll() 33 | if err == nil { 34 | alreadyAsked := []string{} 35 | for _, r := range records { 36 | if r[0] != "blockHeight" { 37 | tx := CsvTX{} 38 | tx.BlockHeight = r[0] 39 | epoch, err := strconv.ParseInt(r[1], 10, 64) 40 | if err != nil { 41 | log.Println(SOURCE, "Error Parsing Epoch", r[1]) 42 | } else { 43 | tx.Epoch = time.Unix(epoch, 0) 44 | } 45 | tx.Date = r[2] 46 | tx.Direction = r[3] 47 | tx.Amount, err = decimal.NewFromString(r[4]) 48 | if err != nil { 49 | log.Println(SOURCE, "Error Parsing Amount", r[4]) 50 | } 51 | atomic, err := strconv.ParseInt(r[5], 10, 64) 52 | if err != nil { 53 | log.Println(SOURCE, "Error Parsing AtomicAmount", r[5]) 54 | } else { 55 | tx.AtomicAmount = decimal.New(atomic, -12) 56 | } 57 | if r[6] != "" { 58 | tx.Fee, err = decimal.NewFromString(r[6]) 59 | if err != nil { 60 | log.Println(SOURCE, "Error Parsing Fee", r[6]) 61 | } 62 | } 63 | tx.TxID = r[7] 64 | tx.SubaddrAccount = r[8] 65 | tx.PaymentId = r[9] 66 | xmr.CsvTXs = append(xmr.CsvTXs, tx) 67 | } 68 | } 69 | for _, tx := range xmr.CsvTXs { 70 | // Fixmr TXsByCategory 71 | if tx.Direction == "in" { 72 | t := wallet.TX{Timestamp: tx.Epoch, ID: tx.TxID, Note: SOURCE + " " + tx.BlockHeight + " " + tx.Label} 73 | t.Items = make(map[string]wallet.Currencies) 74 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: "XMR", Amount: tx.AtomicAmount}) 75 | if !tx.Fee.IsZero() { 76 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: "XMR", Amount: tx.Fee}) 77 | } 78 | if is, desc, val, curr := cat.IsTxCashIn(tx.TxID); is { 79 | t.Note += " crypto_purchase " + desc 80 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: curr, Amount: val}) 81 | xmr.TXsByCategory["CashIn"] = append(xmr.TXsByCategory["CashIn"], t) 82 | } else { 83 | xmr.TXsByCategory["Deposits"] = append(xmr.TXsByCategory["Deposits"], t) 84 | } 85 | } else if tx.Direction == "out" { 86 | t := wallet.TX{Timestamp: tx.Epoch, ID: tx.TxID, Note: SOURCE + " " + tx.BlockHeight + " " + tx.Label} 87 | t.Items = make(map[string]wallet.Currencies) 88 | if !tx.Fee.IsZero() { 89 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: "XMR", Amount: tx.Fee}) 90 | } 91 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: "XMR", Amount: tx.AtomicAmount}) 92 | if is, desc, val, curr := cat.IsTxCashOut(tx.TxID); is { 93 | t.Note += " crypto_payment " + desc 94 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: curr, Amount: val}) 95 | xmr.TXsByCategory["CashOut"] = append(xmr.TXsByCategory["CashOut"], t) 96 | } else { 97 | xmr.TXsByCategory["Withdrawals"] = append(xmr.TXsByCategory["Withdrawals"], t) 98 | } 99 | } else { 100 | alreadyAsked = wallet.AskForHelp(SOURCE+" : "+tx.Direction, tx, alreadyAsked) 101 | } 102 | } 103 | } 104 | return 105 | } 106 | -------------------------------------------------------------------------------- /coinbasepro/csv_fills.go: -------------------------------------------------------------------------------- 1 | package coinbasepro 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "strings" 8 | "time" 9 | 10 | "github.com/fiscafacile/CryptoFiscaFacile/source" 11 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 12 | "github.com/shopspring/decimal" 13 | ) 14 | 15 | type CsvFillsTX struct { 16 | Portfolio string 17 | TradeID string 18 | ProductLeft string 19 | ProductRight string 20 | Side string 21 | CreatedAt time.Time 22 | Size decimal.Decimal 23 | SizeUnit string 24 | Price decimal.Decimal 25 | Fee decimal.Decimal 26 | Total decimal.Decimal 27 | PriceFeeTotalUnit string 28 | } 29 | 30 | func (cbp *CoinbasePro) ParseFillsCSV(reader io.ReadSeeker, account string) (err error) { 31 | firstTimeUsed := time.Now() 32 | lastTimeUsed := time.Date(2009, time.January, 1, 0, 0, 0, 0, time.UTC) 33 | const SOURCE = "CoinbasePro Fills CSV :" 34 | csvReader := csv.NewReader(reader) 35 | records, err := csvReader.ReadAll() 36 | if err == nil { 37 | alreadyAsked := []string{} 38 | for _, r := range records { 39 | if r[0] != "portfolio" { 40 | tx := CsvFillsTX{} 41 | tx.Portfolio = r[0] 42 | tx.TradeID = r[1] 43 | products := strings.Split(r[2], "-") 44 | tx.ProductLeft = products[0] 45 | tx.ProductRight = products[1] 46 | tx.Side = r[3] 47 | tx.CreatedAt, err = time.Parse("2006-01-02T15:04:05.999Z", r[4]) 48 | if err != nil { 49 | log.Println(SOURCE, "Error Parsing CreatedAt : ", r[4]) 50 | } 51 | tx.Size, err = decimal.NewFromString(r[5]) 52 | if err != nil { 53 | log.Println(SOURCE, "Error Parsing Size : ", r[5]) 54 | } 55 | tx.SizeUnit = r[6] 56 | tx.Price, err = decimal.NewFromString(r[7]) 57 | if err != nil { 58 | log.Println(SOURCE, "Error Parsing Price : ", r[7]) 59 | } 60 | if r[8] != "" { 61 | tx.Fee, err = decimal.NewFromString(r[8]) 62 | if err != nil { 63 | log.Println(SOURCE, "Error Parsing Fee : ", r[8]) 64 | } 65 | } 66 | if r[9] != "" { 67 | tx.Total, err = decimal.NewFromString(r[9]) 68 | if err != nil { 69 | log.Println(SOURCE, "Error Parsing Total : ", r[9]) 70 | } 71 | } 72 | tx.PriceFeeTotalUnit = r[10] 73 | cbp.CsvFillsTXs = append(cbp.CsvFillsTXs, tx) 74 | if tx.CreatedAt.Before(firstTimeUsed) { 75 | firstTimeUsed = tx.CreatedAt 76 | } 77 | if tx.CreatedAt.After(lastTimeUsed) { 78 | lastTimeUsed = tx.CreatedAt 79 | } 80 | // Fill TXsByCategory 81 | if tx.Side == "BUY" { 82 | t := wallet.TX{Timestamp: tx.CreatedAt, ID: tx.TradeID, Note: SOURCE + " portfolio " + tx.Portfolio} 83 | t.Items = make(map[string]wallet.Currencies) 84 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.ProductLeft, Amount: tx.Size}) 85 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.ProductRight, Amount: tx.Size.Mul(tx.Price)}) 86 | if !tx.Fee.IsZero() { 87 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.PriceFeeTotalUnit, Amount: tx.Fee}) 88 | } 89 | cbp.TXsByCategory["Exchanges"] = append(cbp.TXsByCategory["Exchanges"], t) 90 | } else { 91 | alreadyAsked = wallet.AskForHelp(SOURCE+" "+tx.Side, tx, alreadyAsked) 92 | } 93 | } 94 | } 95 | } 96 | if _, ok := cbp.Sources["CoinbasePro"]; ok { 97 | if cbp.Sources["CoinbasePro"].OpeningDate.After(firstTimeUsed) { 98 | src := cbp.Sources["CoinbasePro"] 99 | src.OpeningDate = firstTimeUsed 100 | cbp.Sources["CoinbasePro"] = src 101 | } 102 | if cbp.Sources["CoinbasePro"].ClosingDate.Before(lastTimeUsed) { 103 | src := cbp.Sources["CoinbasePro"] 104 | src.ClosingDate = lastTimeUsed 105 | cbp.Sources["CoinbasePro"] = src 106 | } 107 | } else { 108 | cbp.Sources["CoinbasePro"] = source.Source{ 109 | Crypto: true, 110 | AccountNumber: account, 111 | OpeningDate: firstTimeUsed, 112 | ClosingDate: lastTimeUsed, 113 | LegalName: "Coinbase Europe Limited", 114 | Address: "70 Sir John Rogerson’s Quay,\nDublin D02 R296\nIrlande", 115 | URL: "https://pro.coinbase.com", 116 | } 117 | } 118 | return 119 | } 120 | -------------------------------------------------------------------------------- /etherscan/api_internal.go: -------------------------------------------------------------------------------- 1 | package etherscan 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/nanobox-io/golang-scribble" 8 | "github.com/shopspring/decimal" 9 | ) 10 | 11 | type internalTX struct { 12 | used bool 13 | BlockNumber int 14 | TimeStamp time.Time 15 | Hash string 16 | From string 17 | To string 18 | Value decimal.Decimal 19 | ContractAddress string 20 | Input string 21 | Type string 22 | Gas int 23 | GasUsed decimal.Decimal 24 | TraceID string 25 | IsError int 26 | ErrCode string 27 | } 28 | 29 | func (api *api) getInternalTXs(addresses []string) { 30 | for _, eth := range addresses { 31 | accTXListInternal, err := api.getAccountTXListInternal(eth, false) 32 | if err != nil { 33 | api.doneInt <- err 34 | return 35 | } 36 | for _, intTX := range accTXListInternal.Result { 37 | if intTX.IsError == 0 { 38 | tx := internalTX{ 39 | used: false, 40 | BlockNumber: intTX.BlockNumber, 41 | TimeStamp: time.Unix(intTX.TimeStamp, 0), 42 | Hash: intTX.Hash, 43 | From: intTX.From, 44 | To: intTX.To, 45 | Value: decimal.NewFromBigInt(intTX.Value.Int(), -18), 46 | ContractAddress: intTX.ContractAddress, 47 | Input: intTX.Input, 48 | Type: intTX.Type, 49 | Gas: intTX.Gas, 50 | GasUsed: decimal.NewFromInt(intTX.GasUsed), 51 | TraceID: intTX.TraceID, 52 | IsError: intTX.IsError, 53 | ErrCode: intTX.ErrCode, 54 | } 55 | api.internalTXs = append(api.internalTXs, tx) 56 | } 57 | } 58 | } 59 | api.doneInt <- nil 60 | } 61 | 62 | type ResultTXListInternal struct { 63 | BlockNumber int `json:"blockNumber,string"` 64 | TimeStamp int64 `json:"timeStamp,string"` 65 | Hash string `json:"hash"` 66 | From string `json:"from"` 67 | To string `json:"to"` 68 | Value *bigInt `json:"value"` 69 | ContractAddress string `json:"contractAddress"` 70 | Input string `json:"input"` 71 | Type string `json:"type"` 72 | Gas int `json:"gas,string"` 73 | GasUsed int64 `json:"gasUsed,string"` 74 | TraceID string `json:"traceId"` 75 | IsError int `json:"isError,string"` 76 | ErrCode string `json:"errCode"` 77 | } 78 | 79 | type GetAccountTXListInternalResp struct { 80 | Status string `json:"status"` 81 | Message string `json:"message"` 82 | Result []ResultTXListInternal `json:"result"` 83 | } 84 | 85 | func (api *api) getAccountTXListInternal(address string, desc bool) (accTXListInternal GetAccountTXListInternalResp, err error) { 86 | const SOURCE = "Etherscan API TX List Internal :" 87 | useCache := true 88 | db, err := scribble.New("./Cache", nil) 89 | if err != nil { 90 | useCache = false 91 | } 92 | if useCache { 93 | err = db.Read("Etherscan.io/account/txlistinternal", address, &accTXListInternal) 94 | } 95 | if !useCache || err != nil { 96 | params := map[string]string{ 97 | "module": "account", 98 | "action": "txlistinternal", 99 | "address": address, 100 | "apikey": api.apiKey, 101 | } 102 | if desc { 103 | params["sort"] = "desc" 104 | } else { 105 | params["sort"] = "asc" 106 | } 107 | resp, err := api.clientInt.R(). 108 | SetQueryParams(params). 109 | SetHeader("Accept", "application/json"). 110 | SetResult(&GetAccountTXListInternalResp{}). 111 | Get(api.basePath) 112 | if err != nil { 113 | return accTXListInternal, errors.New(SOURCE + " Error Requesting " + address) 114 | } 115 | accTXListInternal = *resp.Result().(*GetAccountTXListInternalResp) 116 | if useCache { 117 | err = db.Write("Etherscan.io/account/txlistinternal", address, accTXListInternal) 118 | if err != nil { 119 | return accTXListInternal, errors.New(SOURCE + " Error Caching " + address) 120 | } 121 | } 122 | if accTXListInternal.Message == "OK" { 123 | time.Sleep(api.timeBetweenReq) 124 | } 125 | } 126 | return accTXListInternal, nil 127 | } 128 | -------------------------------------------------------------------------------- /cryptocom/api_exchange_spot_trades.go: -------------------------------------------------------------------------------- 1 | package cryptocom 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "time" 9 | 10 | scribble "github.com/nanobox-io/golang-scribble" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | type spotTradeTX struct { 15 | Timestamp time.Time 16 | ID string 17 | Description string 18 | Pair string 19 | Side string 20 | Price decimal.Decimal 21 | Quantity decimal.Decimal 22 | Fee decimal.Decimal 23 | FeeCurrency string 24 | } 25 | 26 | func (api *apiEx) getSpotTradesTXs(loc *time.Location) { 27 | date := api.startTime 28 | for date.Before(time.Now().Add(-24 * time.Hour)) { 29 | fmt.Print(".") 30 | trades, err := api.getTrades(date.Year(), date.Month(), date.Day(), loc) 31 | if err != nil { 32 | // api.doneSpotTra <- err 33 | // return 34 | log.Println(err) 35 | break 36 | } 37 | for _, tra := range trades.Result.TradeList { 38 | tx := spotTradeTX{} 39 | tx.Timestamp = time.Unix(tra.CreateTime/1000, 0) 40 | tx.Description = tra.Side + " " + tra.LiquidityIndicator 41 | tx.ID = tra.OrderID + "-" + tra.TradeID 42 | tx.Pair = tra.InstrumentName 43 | tx.Side = tra.Side 44 | tx.Price = decimal.NewFromFloat(tra.TradedPrice) 45 | tx.Quantity = decimal.NewFromFloat(tra.TradedQuantity) 46 | tx.Fee = decimal.NewFromFloat(tra.Fee) 47 | tx.FeeCurrency = tra.FeeCurrency 48 | api.spotTradeTXs = append(api.spotTradeTXs, tx) 49 | } 50 | date = date.Add(24 * time.Hour) 51 | } 52 | api.doneSpotTra <- nil 53 | } 54 | 55 | type ResultTrade struct { 56 | Side string `json:"side"` 57 | InstrumentName string `json:"instrument_name"` 58 | Fee float64 `json:"fee"` 59 | TradeID string `json:"trade_id"` 60 | CreateTime int64 `json:"create_time"` 61 | TradedPrice float64 `json:"traded_price"` 62 | TradedQuantity float64 `json:"traded_quantity"` 63 | LiquidityIndicator string `json:"liquidity_indicator"` 64 | FeeCurrency string `json:"fee_currency"` 65 | OrderID string `json:"order_id"` 66 | } 67 | 68 | type TradeList struct { 69 | TradeList []ResultTrade `json:"trade_list"` 70 | } 71 | 72 | type GetTradesResp struct { 73 | ID int64 `json:"id"` 74 | Method string `json:"method"` 75 | Code int `json:"code"` 76 | Result TradeList `json:"result"` 77 | } 78 | 79 | func (api *apiEx) getTrades(year int, month time.Month, day int, loc *time.Location) (trades GetTradesResp, err error) { 80 | start_ts := time.Date(year, month, day, 0, 0, 0, 0, loc) 81 | end_ts := start_ts.Add(24 * time.Hour).Add(-time.Millisecond) 82 | period := start_ts.Format("2006-01-02") 83 | now := time.Now() 84 | if start_ts.After(now) { 85 | return // without error 86 | } 87 | if end_ts.After(now) { 88 | end_ts = now 89 | period += "-" + strconv.FormatInt(end_ts.Unix(), 10) 90 | } 91 | useCache := true 92 | db, err := scribble.New("./Cache", nil) 93 | if err != nil { 94 | useCache = false 95 | } 96 | if useCache { 97 | err = db.Read("Crypto.com/Exchange/private/get-trades", period, &trades) 98 | } 99 | if !useCache || err != nil { 100 | method := "private/get-trades" 101 | body := make(map[string]interface{}) 102 | body["method"] = method 103 | body["params"] = map[string]interface{}{ 104 | "start_ts": start_ts.UnixNano() / 1e6, 105 | "end_ts": end_ts.UnixNano() / 1e6, 106 | "page_size": 200, 107 | "page": 0, 108 | } 109 | api.sign(body) 110 | resp, err := api.clientSpotTra.R(). 111 | SetBody(body). 112 | SetResult(&GetTradesResp{}). 113 | SetError(&ErrorResp{}). 114 | Post(api.basePath + method) 115 | if err != nil { 116 | return trades, errors.New("Crypto.com Exchange API Trades : Error Requesting" + period) 117 | } 118 | if resp.StatusCode() > 300 { 119 | return trades, errors.New("Crypto.com Exchange API Trades : Error StatusCode" + strconv.Itoa(resp.StatusCode()) + " for " + period) 120 | } 121 | trades = *resp.Result().(*GetTradesResp) 122 | if useCache { 123 | err = db.Write("Crypto.com/Exchange/private/get-trades", period, trades) 124 | if err != nil { 125 | return trades, errors.New("Crypto.com Exchange API Trades : Error Caching" + period) 126 | } 127 | } 128 | time.Sleep(api.timeBetweenReqSpot) 129 | } 130 | return trades, nil 131 | } 132 | -------------------------------------------------------------------------------- /hitbtc/csv_trades.go: -------------------------------------------------------------------------------- 1 | package hitbtc 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "strings" 8 | "time" 9 | 10 | "github.com/fiscafacile/CryptoFiscaFacile/source" 11 | "github.com/fiscafacile/CryptoFiscaFacile/utils" 12 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 13 | "github.com/shopspring/decimal" 14 | ) 15 | 16 | type csvTradeTX struct { 17 | Email string 18 | Date time.Time 19 | Instrument string 20 | TradeID string 21 | OrderID string 22 | Side string 23 | Quantity decimal.Decimal 24 | Price decimal.Decimal 25 | Volume decimal.Decimal 26 | Fee decimal.Decimal 27 | Rebate string 28 | Total string 29 | Taker string 30 | } 31 | 32 | func (hb *HitBTC) ParseCSVTrades(reader io.Reader) (err error) { 33 | firstTimeUsed := time.Now() 34 | lastTimeUsed := time.Date(2019, time.November, 14, 0, 0, 0, 0, time.UTC) 35 | const SOURCE = "HitBTC CSV Trades :" 36 | csvReader := csv.NewReader(reader) 37 | records, err := csvReader.ReadAll() 38 | if err == nil { 39 | alreadyAsked := []string{} 40 | for _, r := range records { 41 | if r[0] != "Email" { 42 | tx := csvTradeTX{} 43 | tx.Email = r[0] 44 | tx.Date, err = time.Parse("2006-01-02 15:04:05", r[1]) 45 | if err != nil { 46 | log.Println(SOURCE, "Error Parsing Date", r[1]) 47 | } 48 | tx.Instrument = r[2] 49 | tx.TradeID = r[3] 50 | tx.OrderID = r[4] 51 | tx.Side = r[5] 52 | tx.Quantity, err = decimal.NewFromString(r[6]) 53 | if err != nil { 54 | log.Println(SOURCE, "Error Parsing Quantity", r[6]) 55 | } 56 | if r[7] != "" { 57 | tx.Price, err = decimal.NewFromString(r[7]) 58 | if err != nil { 59 | log.Println(SOURCE, "Error Parsing Price", r[7]) 60 | } 61 | } 62 | if r[8] != "" { 63 | tx.Volume, err = decimal.NewFromString(r[8]) 64 | if err != nil { 65 | log.Println(SOURCE, "Error Parsing Volume", r[8]) 66 | } 67 | } 68 | if r[9] != "" { 69 | tx.Fee, err = decimal.NewFromString(r[9]) 70 | if err != nil { 71 | log.Println(SOURCE, "Error Parsing Fee", r[9]) 72 | } 73 | } 74 | tx.Rebate = r[10] 75 | tx.Total = r[11] 76 | tx.Taker = r[12] 77 | hb.csvTradeTXs = append(hb.csvTradeTXs, tx) 78 | hb.emails = utils.AppendUniq(hb.emails, tx.Email) 79 | // Fill TXsByCategory 80 | t := wallet.TX{Timestamp: tx.Date, ID: tx.TradeID, Note: SOURCE + " " + tx.Instrument + " " + tx.OrderID} 81 | t.Items = make(map[string]wallet.Currencies) 82 | curr := strings.Split(tx.Instrument, "_") 83 | for i, c := range curr { 84 | curr[i] = csvCurrencyCure(c) 85 | } 86 | if tx.Volume.IsZero() { 87 | tx.Volume = tx.Quantity.Mul(tx.Price) 88 | } 89 | if !tx.Fee.IsZero() { 90 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: curr[0], Amount: tx.Fee}) 91 | } 92 | if tx.Side == "sell" { 93 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: curr[0], Amount: tx.Quantity}) 94 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: curr[1], Amount: tx.Volume}) 95 | hb.TXsByCategory["Exchanges"] = append(hb.TXsByCategory["Exchanges"], t) 96 | } else if tx.Side == "buy" { 97 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: curr[1], Amount: tx.Volume}) 98 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: curr[0], Amount: tx.Quantity}) 99 | hb.TXsByCategory["Exchanges"] = append(hb.TXsByCategory["Exchanges"], t) 100 | } else { 101 | alreadyAsked = wallet.AskForHelp(SOURCE+" "+tx.Side, tx, alreadyAsked) 102 | } 103 | if tx.Date.Before(firstTimeUsed) { 104 | firstTimeUsed = tx.Date 105 | } 106 | if tx.Date.After(lastTimeUsed) { 107 | lastTimeUsed = tx.Date 108 | } 109 | } 110 | } 111 | } 112 | for _, e := range hb.emails { 113 | if _, ok := hb.Sources["HitBTC_"+e]; !ok { 114 | hb.Sources["HitBTC_"+e] = source.Source{ 115 | Crypto: true, 116 | AccountNumber: e, 117 | OpeningDate: firstTimeUsed, 118 | ClosingDate: lastTimeUsed, 119 | LegalName: "Hit Tech Solutions Development Ltd.", 120 | Address: "Suite 15, Oliaji Trade Centre, Francis Rachel Street,\nVictoria, Mahe,\nSeychelles", 121 | URL: "https://hitbtc.com", 122 | } 123 | } 124 | } 125 | return 126 | } 127 | -------------------------------------------------------------------------------- /hitbtc/api.go: -------------------------------------------------------------------------------- 1 | package hitbtc 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 8 | "github.com/go-resty/resty/v2" 9 | ) 10 | 11 | type api struct { 12 | clientAccTrans *resty.Client 13 | doneAccTrans chan error 14 | clientTrade *resty.Client 15 | doneTrade chan error 16 | basePath string 17 | apiKey string 18 | secretKey string 19 | firstTimeUsed time.Time 20 | lastTimeUsed time.Time 21 | timeBetweenReq time.Duration 22 | accountTXs []accountTX 23 | tradeTXs []tradeTX 24 | txsByCategory wallet.TXsByCategory 25 | } 26 | 27 | type ErrorResp struct { 28 | Error struct { 29 | Code int `json:"code"` 30 | Message string `json:"message"` 31 | } `json:"error"` 32 | } 33 | 34 | func (hb *HitBTC) NewAPI(apiKey, secretKey string, debug bool) { 35 | hb.api.txsByCategory = make(map[string]wallet.TXs) 36 | hb.api.clientAccTrans = resty.New() 37 | hb.api.clientAccTrans.SetRetryCount(3) 38 | hb.api.clientAccTrans.SetDebug(debug) 39 | hb.api.doneAccTrans = make(chan error) 40 | hb.api.clientTrade = resty.New() 41 | hb.api.clientTrade.SetRetryCount(3).SetRetryWaitTime(1 * time.Second) 42 | hb.api.clientTrade.SetDebug(debug) 43 | hb.api.doneTrade = make(chan error) 44 | hb.api.basePath = "https://api.hitbtc.com/api/2/" 45 | hb.api.apiKey = apiKey 46 | hb.api.secretKey = secretKey 47 | hb.api.firstTimeUsed = time.Now() 48 | hb.api.lastTimeUsed = time.Date(2019, time.November, 14, 0, 0, 0, 0, time.UTC) 49 | hb.api.timeBetweenReq = 100 * time.Millisecond 50 | } 51 | 52 | func (api *api) getAllTXs() (err error) { 53 | go api.getAccountTXs() 54 | go api.getTradesTXs() 55 | <-api.doneAccTrans 56 | <-api.doneTrade 57 | api.categorize() 58 | return 59 | } 60 | 61 | func (api *api) categorize() { 62 | const SOURCE = "HitBTC API :" 63 | alreadyAsked := []string{} 64 | for _, tx := range api.accountTXs { 65 | t := wallet.TX{Timestamp: tx.UpdatedAt, ID: tx.ID, Note: SOURCE + " " + tx.Type} 66 | t.Items = make(map[string]wallet.Currencies) 67 | if !tx.Fee.IsZero() { 68 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.Currency, Amount: tx.Fee}) 69 | } 70 | if tx.Type == "deposit" || 71 | tx.Type == "payin" { 72 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount}) 73 | api.txsByCategory["Deposits"] = append(api.txsByCategory["Deposits"], t) 74 | } else if tx.Type == "withdraw" || 75 | tx.Type == "payout" { 76 | if tx.Type == "payout" { 77 | t.Note += " " + tx.Hash + " -> " + tx.Address 78 | } 79 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount}) 80 | api.txsByCategory["Withdrawals"] = append(api.txsByCategory["Withdrawals"], t) 81 | } else if tx.Type == "bankToExchange" || 82 | tx.Type == "exchangeToBank" { 83 | // Ignore Source internal transfer 84 | } else { 85 | alreadyAsked = wallet.AskForHelp(SOURCE+" "+tx.Type, tx, alreadyAsked) 86 | } 87 | if tx.CreatedAt.Before(api.firstTimeUsed) { 88 | api.firstTimeUsed = tx.CreatedAt 89 | } 90 | if tx.UpdatedAt.After(api.lastTimeUsed) { 91 | api.lastTimeUsed = tx.UpdatedAt 92 | } 93 | } 94 | for _, tx := range api.tradeTXs { 95 | t := wallet.TX{Timestamp: tx.Timestamp, ID: strconv.Itoa(tx.ID), Note: SOURCE + " " + tx.ClientOrderID} 96 | t.Items = make(map[string]wallet.Currencies) 97 | curr := []string{tx.Symbol[:3], tx.Symbol[3:]} 98 | if tx.Side == "sell" { 99 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: curr[0], Amount: tx.Quantity}) 100 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: curr[1], Amount: tx.Quantity.Mul(tx.Price)}) 101 | } else { // if tx.Side == "buy" 102 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: curr[1], Amount: tx.Quantity}) 103 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: curr[0], Amount: tx.Quantity.Mul(tx.Price)}) 104 | } 105 | if !tx.Fee.IsZero() { 106 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: curr[0], Amount: tx.Fee}) 107 | } 108 | api.txsByCategory["Exchanges"] = append(api.txsByCategory["Exchanges"], t) 109 | if tx.Timestamp.Before(api.firstTimeUsed) { 110 | api.firstTimeUsed = tx.Timestamp 111 | } 112 | if tx.Timestamp.After(api.lastTimeUsed) { 113 | api.lastTimeUsed = tx.Timestamp 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /binance/api_asset_dividend.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "time" 9 | 10 | scribble "github.com/nanobox-io/golang-scribble" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | type assetDividendTX struct { 15 | Timestamp time.Time 16 | Description string 17 | Asset string 18 | Amount decimal.Decimal 19 | ID string 20 | } 21 | 22 | func (api *api) getAssetDividendTXs(loc *time.Location) { 23 | today := time.Now() 24 | thisYear := today.Year() 25 | for y := 2018; y <= thisYear; y++ { 26 | for t := 1; t < 7; t++ { 27 | fmt.Print(".") 28 | assDiv, err := api.getAssetDividend(y, t, loc) 29 | if err != nil { 30 | // api.doneAssDiv <- err 31 | // return 32 | log.Println(err) 33 | break 34 | } 35 | for _, div := range assDiv.Rows { 36 | tx := assetDividendTX{} 37 | tx.Timestamp = time.Unix(div.DivTime/1e3, 0) 38 | tx.ID = strconv.FormatInt(div.TranID, 10) 39 | tx.Description = div.Info 40 | tx.Amount, _ = decimal.NewFromString(div.Amount) 41 | tx.Asset = div.Asset 42 | api.assetDividendTXs = append(api.assetDividendTXs, tx) 43 | } 44 | } 45 | } 46 | api.doneAssDiv <- nil 47 | } 48 | 49 | type Rows []struct { 50 | Amount string `json:"amount"` 51 | Asset string `json:"asset"` 52 | DivTime int64 `json:"divTime"` 53 | Info string `json:"enInfo"` 54 | TranID int64 `json:"tranId"` 55 | } 56 | 57 | type GetAssetDividendResp struct { 58 | Rows Rows `json:"rows"` 59 | Total int `json:"total"` 60 | } 61 | 62 | func (api *api) getAssetDividend(year, trimester int, loc *time.Location) (assDiv GetAssetDividendResp, err error) { 63 | var start_month time.Month 64 | var end_month time.Month 65 | end_year := year 66 | period := strconv.Itoa(year) + "-T" + strconv.Itoa(trimester) 67 | if trimester == 1 { 68 | start_month = time.January 69 | end_month = time.March 70 | } else if trimester == 2 { 71 | start_month = time.March 72 | end_month = time.May 73 | } else if trimester == 3 { 74 | start_month = time.May 75 | end_month = time.July 76 | } else if trimester == 4 { 77 | start_month = time.July 78 | end_month = time.September 79 | } else if trimester == 5 { 80 | start_month = time.September 81 | end_month = time.November 82 | } else if trimester == 6 { 83 | start_month = time.November 84 | end_month = time.January 85 | end_year = year + 1 86 | } else { 87 | err = errors.New("Binance API Dividends : Invalid trimester" + period) 88 | return 89 | } 90 | start_ts := time.Date(year, start_month, 1, 0, 0, 0, 0, loc) 91 | end_ts := time.Date(end_year, end_month, 1, 0, 0, 0, 0, loc) 92 | now := time.Now() 93 | if start_ts.After(now) { 94 | return 95 | } 96 | if end_ts.After(now) { 97 | end_ts = now 98 | period += "-" + strconv.FormatInt(end_ts.Unix(), 10) 99 | } 100 | useCache := true 101 | db, err := scribble.New("./Cache", nil) 102 | if err != nil { 103 | useCache = false 104 | } 105 | if useCache { 106 | err = db.Read("Binance/sapi/v1/asset/assetDividend/", period, &assDiv) 107 | } 108 | if !useCache || err != nil { 109 | endpoint := "sapi/v1/asset/assetDividend" 110 | queryParams := map[string]string{ 111 | "limit": "500", 112 | "startTime": fmt.Sprintf("%v", start_ts.UTC().UnixNano()/1e6), 113 | "endTime": fmt.Sprintf("%v", end_ts.UTC().UnixNano()/1e6), 114 | "recvWindow": "60000", 115 | "timestamp": fmt.Sprintf("%v", time.Now().UTC().UnixNano()/1e6), 116 | } 117 | api.sign(queryParams) 118 | resp, err := api.clientAssDiv.R(). 119 | SetHeader("X-MBX-APIKEY", api.apiKey). 120 | SetQueryParams(queryParams). 121 | SetResult(&GetAssetDividendResp{}). 122 | SetError(&ErrorResp{}). 123 | Get(api.basePath + endpoint) 124 | if err != nil { 125 | return assDiv, errors.New("Binance API Asset Dividend : Error Requesting " + period) 126 | } 127 | if resp.StatusCode() > 300 { 128 | return assDiv, errors.New("Binance API Asset Dividend : Error StatusCode " + strconv.Itoa(resp.StatusCode()) + " for " + period) 129 | } 130 | assDiv = *resp.Result().(*GetAssetDividendResp) 131 | if useCache { 132 | err = db.Write("Binance/sapi/v1/asset/assetDividend/", period, assDiv) 133 | if err != nil { 134 | return assDiv, errors.New("Binance API Asset Dividend : Error Caching" + period) 135 | } 136 | } 137 | time.Sleep(api.timeBetweenRequests) 138 | } 139 | return assDiv, nil 140 | } 141 | -------------------------------------------------------------------------------- /binance/api_deposits.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "time" 9 | 10 | scribble "github.com/nanobox-io/golang-scribble" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | type depositTX struct { 15 | Timestamp time.Time 16 | ID string 17 | Description string 18 | Currency string 19 | Amount decimal.Decimal 20 | Fee decimal.Decimal 21 | } 22 | 23 | func (api *api) getDepositsTXs(loc *time.Location) { 24 | today := time.Now() 25 | thisYear := today.Year() 26 | for y := thisYear; y > 2017; y-- { 27 | for t := 6; t > 0; t-- { 28 | fmt.Print(".") 29 | depoHist, err := api.getDepositHistory(y, t, loc) 30 | if err != nil { 31 | // api.doneDep <- err 32 | // return 33 | log.Println(err) 34 | break 35 | } 36 | for _, dep := range depoHist { 37 | tx := depositTX{} 38 | tx.Timestamp = time.Unix(dep.Inserttime/1e3, 0) 39 | tx.ID = dep.Txid 40 | tx.Description = "from " + dep.Address 41 | tx.Currency = dep.Coin 42 | tx.Amount, _ = decimal.NewFromString(dep.Amount) 43 | api.depositTXs = append(api.depositTXs, tx) 44 | } 45 | } 46 | } 47 | api.doneDep <- nil 48 | } 49 | 50 | type GetDepositHistoryResp []struct { 51 | Amount string `json:"amount"` 52 | Coin string `json:"coin"` 53 | Network string `json:"network"` 54 | Status int `json:"status"` 55 | Address string `json:"address"` 56 | Addresstag string `json:"addressTag"` 57 | Txid string `json:"txId"` 58 | Inserttime int64 `json:"insertTime"` 59 | Transfertype int `json:"transferType"` 60 | Confirmtimes string `json:"confirmTimes"` 61 | } 62 | 63 | func (api *api) getDepositHistory(year, trimester int, loc *time.Location) (depoHist GetDepositHistoryResp, err error) { 64 | var start_month time.Month 65 | var end_month time.Month 66 | end_year := year 67 | period := strconv.Itoa(year) + "-T" + strconv.Itoa(trimester) 68 | if trimester == 1 { 69 | start_month = time.January 70 | end_month = time.March 71 | } else if trimester == 2 { 72 | start_month = time.March 73 | end_month = time.May 74 | } else if trimester == 3 { 75 | start_month = time.May 76 | end_month = time.July 77 | } else if trimester == 4 { 78 | start_month = time.July 79 | end_month = time.September 80 | } else if trimester == 5 { 81 | start_month = time.September 82 | end_month = time.November 83 | } else if trimester == 6 { 84 | start_month = time.November 85 | end_month = time.January 86 | end_year = year + 1 87 | } else { 88 | err = errors.New("Binance API Deposits : Invalid trimester" + period) 89 | return 90 | } 91 | start_ts := time.Date(year, start_month, 1, 0, 0, 0, 0, loc) 92 | end_ts := time.Date(end_year, end_month, 1, 0, 0, 0, 0, loc) 93 | now := time.Now() 94 | if start_ts.After(now) { 95 | return 96 | } 97 | if end_ts.After(now) { 98 | end_ts = now 99 | period += "-" + strconv.FormatInt(end_ts.Unix(), 10) 100 | } 101 | useCache := true 102 | db, err := scribble.New("./Cache", nil) 103 | if err != nil { 104 | useCache = false 105 | } 106 | if useCache { 107 | err = db.Read("Binance/sapi/v1/capital/deposit/hisrec", period, &depoHist) 108 | } 109 | if !useCache || err != nil { 110 | endpoint := "sapi/v1/capital/deposit/hisrec" 111 | queryParams := map[string]string{ 112 | "status": "1", 113 | "startTime": fmt.Sprintf("%v", start_ts.UTC().UnixNano()/1e6), 114 | "endTime": fmt.Sprintf("%v", end_ts.UTC().UnixNano()/1e6), 115 | "recvWindow": "60000", 116 | "timestamp": fmt.Sprintf("%v", time.Now().UTC().UnixNano()/1e6), 117 | } 118 | api.sign(queryParams) 119 | resp, err := api.clientDep.R(). 120 | SetHeader("X-MBX-APIKEY", api.apiKey). 121 | SetQueryParams(queryParams). 122 | SetResult(&GetDepositHistoryResp{}). 123 | SetError(&ErrorResp{}). 124 | Get(api.basePath + endpoint) 125 | if err != nil { 126 | return depoHist, errors.New("Binance API Deposits : Error Requesting" + period) 127 | } 128 | if resp.StatusCode() > 300 { 129 | return depoHist, errors.New("Binance API Deposits : Error StatusCode" + strconv.Itoa(resp.StatusCode()) + " for " + period) 130 | } 131 | depoHist = *resp.Result().(*GetDepositHistoryResp) 132 | if useCache { 133 | err = db.Write("Binance/sapi/v1/capital/deposit/hisrec", period, depoHist) 134 | if err != nil { 135 | return depoHist, errors.New("Binance API Deposits : Error Caching" + period) 136 | } 137 | } 138 | time.Sleep(api.timeBetweenRequests) 139 | } 140 | return depoHist, nil 141 | } 142 | -------------------------------------------------------------------------------- /kraken/api_trades.go: -------------------------------------------------------------------------------- 1 | package kraken 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | "net/url" 8 | "strconv" 9 | "strings" 10 | "time" 11 | 12 | scribble "github.com/nanobox-io/golang-scribble" 13 | "github.com/shopspring/decimal" 14 | ) 15 | 16 | type ledgerTX struct { 17 | Amount decimal.Decimal 18 | Asset string 19 | Class string 20 | Fee decimal.Decimal 21 | FeeCurrency string 22 | RefId string 23 | SubType string 24 | Time time.Time 25 | TxId string 26 | Type string 27 | } 28 | 29 | func (api *api) getAPISpotTrades() { 30 | const SOURCE = "Kraken API Trades :" 31 | trades, err := api.getTrades() 32 | if err != nil { 33 | api.doneLedgers <- err 34 | return 35 | } 36 | for txID, txData := range trades { 37 | tra := txData.(map[string]interface{}) 38 | tx := ledgerTX{} 39 | tx.Amount, err = decimal.NewFromString(tra["amount"].(string)) 40 | if err != nil { 41 | fmt.Println(SOURCE, "Error while parsing amount", tra["amount"].(string)) 42 | } 43 | if val, ok := api.assets.Result.(map[string]interface{})[tra["asset"].(string)]; ok { 44 | tx.Asset = val.(map[string]interface{})["altname"].(string) 45 | } else { 46 | tx.Asset = tra["asset"].(string) 47 | } 48 | tx.Asset = ReplaceAssets(tx.Asset) 49 | tx.Class = tra["aclass"].(string) 50 | tx.Fee, err = decimal.NewFromString(tra["fee"].(string)) 51 | if err != nil { 52 | fmt.Println(SOURCE, "Error while parsing fee", tra["fee"].(string)) 53 | } 54 | tx.RefId = tra["refid"].(string) 55 | tx.SubType = tra["subtype"].(string) 56 | sec, dec := math.Modf(tra["time"].(float64)) 57 | tx.Time = time.Unix(int64(sec), int64(dec*(1e9))) 58 | tx.TxId = txID 59 | tx.Type = tra["type"].(string) 60 | api.ledgerTX = append(api.ledgerTX, tx) 61 | } 62 | api.doneLedgers <- nil 63 | } 64 | 65 | type TradesHistory struct { 66 | Error []string `json:"error"` 67 | Result interface{} `json:"result"` 68 | } 69 | 70 | func (api *api) getTrades() (fullTradeTx map[string]interface{}, err error) { 71 | const SOURCE = "Kraken API Trades :" 72 | fullTradeTx = make(map[string]interface{}) 73 | useCache := true 74 | db, err := scribble.New("./Cache", nil) 75 | if err != nil { 76 | useCache = false 77 | } 78 | if useCache { 79 | err = db.Read("Kraken/private", "Ledgers", &fullTradeTx) 80 | } 81 | if !useCache || err != nil { 82 | fmt.Println("Début de la récupération des TXs par l'API Kraken, attention ce processus peut être long en fonction du nombre de transactions...") 83 | resource := "/0/private/Ledgers" 84 | headers := make(map[string]string) 85 | headers["API-Key"] = api.apiKey 86 | headers["Content-Type"] = "application/json" 87 | body := url.Values{} 88 | body.Add("trades", "true") 89 | offset := 0 90 | totalTrades := 1000 91 | for offset < totalTrades { 92 | body.Set("nonce", strconv.FormatInt(time.Now().UTC().Unix()*1000, 10)) 93 | body.Set("ofs", fmt.Sprint(offset)) 94 | api.sign(headers, body, resource) 95 | if api.debug { 96 | fmt.Println("Getting trades transactions from", offset, "to", offset+50) 97 | } 98 | resp, err := api.clientLedgers.R(). 99 | SetHeaders(headers). 100 | SetFormDataFromValues(body). 101 | SetResult(&TradesHistory{}). 102 | Post(api.basePath + resource) 103 | if err != nil || len((*resp.Result().(*TradesHistory)).Error) > 0 { 104 | time.Sleep(6 * time.Second) 105 | body.Set("nonce", strconv.FormatInt(time.Now().UTC().Unix()*1000, 10)) 106 | api.sign(headers, body, resource) 107 | resp, err = api.clientLedgers.R(). 108 | SetHeaders(headers). 109 | SetFormDataFromValues(body). 110 | SetResult(&TradesHistory{}). 111 | Post(api.basePath + resource) 112 | if err != nil || len((*resp.Result().(*TradesHistory)).Error) > 0 { 113 | fmt.Println(SOURCE, "Error Requesting TradesHistory "+strings.Join((*resp.Result().(*TradesHistory)).Error, "")) 114 | return fullTradeTx, errors.New(SOURCE + " Error Requesting TradesHistory " + strings.Join((*resp.Result().(*TradesHistory)).Error, "")) 115 | } 116 | } 117 | result := (*resp.Result().(*TradesHistory)).Result.(map[string]interface{}) 118 | totalTrades = int(result["count"].(float64)) 119 | for k, v := range result["ledger"].(map[string]interface{}) { 120 | fullTradeTx[k] = v 121 | } 122 | offset += 50 123 | time.Sleep(time.Second) 124 | } 125 | if useCache { 126 | err = db.Write("Kraken/private", "Ledgers", fullTradeTx) 127 | if err != nil { 128 | return fullTradeTx, errors.New(SOURCE + " Error Caching TradesHistory") 129 | } 130 | } 131 | } 132 | return fullTradeTx, nil 133 | } 134 | -------------------------------------------------------------------------------- /bittrex/api_trades.go: -------------------------------------------------------------------------------- 1 | package bittrex 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "strconv" 7 | "time" 8 | 9 | "github.com/nanobox-io/golang-scribble" 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | type tradeTX struct { 14 | ID string 15 | MarketSymbol string 16 | Direction string 17 | FillQuantity decimal.Decimal 18 | Commission decimal.Decimal 19 | Proceeds decimal.Decimal 20 | Time time.Time 21 | } 22 | 23 | func (api *api) getTradesTXs() { 24 | const SOURCE = "Bittrex API Trades :" 25 | // Request API with a page offset until all the records are retrieved 26 | trades, err := api.getTrades() 27 | if err != nil { 28 | api.doneTrades <- err 29 | return 30 | } 31 | // Process transfer transactions 32 | for _, trd := range trades { 33 | tx := tradeTX{} 34 | tx.ID = trd.ID 35 | tx.MarketSymbol = trd.MarketSymbol 36 | tx.Direction = trd.Direction 37 | tx.FillQuantity, err = decimal.NewFromString(trd.FillQuantity) 38 | if err != nil { 39 | log.Println(SOURCE, "Error Parsing FillQuantity : ", trd.FillQuantity) 40 | } 41 | tx.Commission, err = decimal.NewFromString(trd.Commission) 42 | if err != nil { 43 | log.Println(SOURCE, "Error Parsing Commission : ", trd.Commission) 44 | } 45 | tx.Proceeds, err = decimal.NewFromString(trd.Proceeds) 46 | if err != nil { 47 | log.Println(SOURCE, "Error Parsing Proceeds : ", trd.Proceeds) 48 | } 49 | tx.Time = trd.ClosedAt 50 | api.tradeTXs = append(api.tradeTXs, tx) 51 | } 52 | api.doneTrades <- nil 53 | } 54 | 55 | type GetTradeResponse []struct { 56 | ID string `json:"id"` 57 | MarketSymbol string `json:"marketSymbol"` 58 | Direction string `json:"direction"` 59 | Type string `json:"type"` 60 | Quantity string `json:"quantity"` 61 | Limit string `json:"limit"` 62 | Ceiling string `json:"ceiling"` 63 | TimeInForce string `json:"timeInForce"` 64 | ClientOrderID string `json:"clientOrderId"` 65 | FillQuantity string `json:"fillQuantity"` 66 | Commission string `json:"commission"` 67 | Proceeds string `json:"proceeds"` 68 | Status string `json:"status"` 69 | CreatedAt time.Time `json:"createdAt"` 70 | UpdatedAt time.Time `json:"updatedAt"` 71 | ClosedAt time.Time `json:"closedAt"` 72 | OrderToCancel struct { 73 | Type string `json:"type"` 74 | ID string `json:"id"` 75 | } `json:"orderToCancel"` 76 | } 77 | 78 | func (api *api) getTrades() (tradesResp GetTradeResponse, err error) { 79 | const SOURCE = "Bittrex API Trades :" 80 | useCache := true 81 | db, err := scribble.New("./Cache", nil) 82 | if err != nil { 83 | useCache = false 84 | } 85 | if useCache { 86 | err = db.Read("Bittrex/orders", "closed", &tradesResp) 87 | } 88 | if !useCache || err != nil { 89 | hash := api.hash("") 90 | ressource := "orders/closed" 91 | lastObjectId := "" 92 | lastResponseCount := 200 93 | for lastResponseCount == 200 { 94 | paramsEncoded := "?" 95 | if lastObjectId != "" { 96 | paramsEncoded += "nextPageToken=" + lastObjectId + "&" 97 | } 98 | paramsEncoded += "pageSize=200" 99 | timestamp, signature := api.sign("", ressource, "GET", hash, paramsEncoded) 100 | params := map[string]string{ 101 | "pageSize": "200", 102 | } 103 | if lastObjectId != "" { 104 | params["nextPageToken"] = lastObjectId 105 | } 106 | resp, err := api.clientTrades.R(). 107 | SetQueryParams(params). 108 | SetHeaders(map[string]string{ 109 | "Accept": "application/json", 110 | "Content-Type": "application/json", 111 | "Api-Content-Hash": hash, 112 | "Api-Key": api.apiKey, 113 | "Api-Signature": signature, 114 | "Api-Timestamp": timestamp, 115 | }). 116 | SetResult(&GetTradeResponse{}). 117 | // SetError(&ErrorResp{}). 118 | Get(api.basePath + ressource) 119 | if err != nil { 120 | return tradesResp, errors.New(SOURCE + " Error Requesting") 121 | } 122 | if resp.StatusCode() > 300 { 123 | return tradesResp, errors.New(SOURCE + " Error StatusCode" + strconv.Itoa(resp.StatusCode())) 124 | } 125 | trades := *resp.Result().(*GetTradeResponse) 126 | lastResponseCount = len(trades) 127 | if lastResponseCount > 0 { 128 | lastObjectId = trades[lastResponseCount-1].ID 129 | } 130 | tradesResp = append(tradesResp, trades...) 131 | time.Sleep(api.timeBetweenReq) 132 | } 133 | if useCache { 134 | err = db.Write("Bittrex/orders", "closed", tradesResp) 135 | if err != nil { 136 | return tradesResp, errors.New(SOURCE + " Error Caching") 137 | } 138 | } 139 | } 140 | return tradesResp, nil 141 | } 142 | -------------------------------------------------------------------------------- /etherscan/api_normal.go: -------------------------------------------------------------------------------- 1 | package etherscan 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/nanobox-io/golang-scribble" 8 | "github.com/shopspring/decimal" 9 | ) 10 | 11 | type normalTX struct { 12 | used bool 13 | BlockNumber int 14 | TimeStamp time.Time 15 | Hash string 16 | Nonce int 17 | BlockHash string 18 | TransactionIndex int 19 | From string 20 | To string 21 | Value decimal.Decimal 22 | Gas int 23 | GasPrice decimal.Decimal 24 | IsError int 25 | TxReceiptStatus string 26 | Input string 27 | ContractAddress string 28 | CumulativeGasUsed int 29 | GasUsed decimal.Decimal 30 | Confirmations int 31 | } 32 | 33 | func (api *api) getNormalTXs(addresses []string) { 34 | for _, eth := range addresses { 35 | accTXList, err := api.getAccountTXList(eth, false) 36 | if err != nil { 37 | api.doneNor <- err 38 | return 39 | } 40 | for _, norTX := range accTXList.Result { 41 | tx := normalTX{ 42 | used: false, 43 | BlockNumber: norTX.BlockNumber, 44 | TimeStamp: time.Unix(norTX.TimeStamp, 0), 45 | Hash: norTX.Hash, 46 | Nonce: norTX.Nonce, 47 | BlockHash: norTX.BlockHash, 48 | TransactionIndex: norTX.TransactionIndex, 49 | From: norTX.From, 50 | To: norTX.To, 51 | Value: decimal.NewFromBigInt(norTX.Value.Int(), -18), 52 | Gas: norTX.Gas, 53 | GasPrice: decimal.NewFromBigInt(norTX.GasPrice.Int(), -18), 54 | IsError: norTX.IsError, 55 | TxReceiptStatus: norTX.TxReceiptStatus, 56 | Input: norTX.Input, 57 | ContractAddress: norTX.ContractAddress, 58 | CumulativeGasUsed: norTX.CumulativeGasUsed, 59 | GasUsed: decimal.NewFromInt(norTX.GasUsed), 60 | Confirmations: norTX.Confirmations, 61 | } 62 | api.normalTXs = append(api.normalTXs, tx) 63 | } 64 | } 65 | api.doneNor <- nil 66 | } 67 | 68 | type ResultTXList struct { 69 | BlockNumber int `json:"blockNumber,string"` 70 | TimeStamp int64 `json:"timeStamp,string"` 71 | Hash string `json:"hash"` 72 | Nonce int `json:"nonce,string"` 73 | BlockHash string `json:"blockHash"` 74 | TransactionIndex int `json:"transactionIndex,string"` 75 | From string `json:"from"` 76 | To string `json:"to"` 77 | Value *bigInt `json:"value"` 78 | Gas int `json:"gas,string"` 79 | GasPrice *bigInt `json:"gasPrice"` 80 | IsError int `json:"isError,string"` 81 | TxReceiptStatus string `json:"txreceipt_status"` 82 | Input string `json:"input"` 83 | ContractAddress string `json:"contractAddress"` 84 | CumulativeGasUsed int `json:"cumulativeGasUsed,string"` 85 | GasUsed int64 `json:"gasUsed,string"` 86 | Confirmations int `json:"confirmations,string"` 87 | } 88 | 89 | type GetAccountTXListResp struct { 90 | Status string `json:"status"` 91 | Message string `json:"message"` 92 | Result []ResultTXList `json:"result"` 93 | } 94 | 95 | func (api *api) getAccountTXList(address string, desc bool) (accTXList GetAccountTXListResp, err error) { 96 | const SOURCE = "Etherscan API TX List :" 97 | useCache := true 98 | db, err := scribble.New("./Cache", nil) 99 | if err != nil { 100 | useCache = false 101 | } 102 | if useCache { 103 | err = db.Read("Etherscan.io/account/txlist", address, &accTXList) 104 | } 105 | if !useCache || err != nil { 106 | params := map[string]string{ 107 | "module": "account", 108 | "action": "txlist", 109 | "address": address, 110 | "apikey": api.apiKey, 111 | } 112 | if desc { 113 | params["sort"] = "desc" 114 | } else { 115 | params["sort"] = "asc" 116 | } 117 | resp, err := api.clientNor.R(). 118 | SetQueryParams(params). 119 | SetHeader("Accept", "application/json"). 120 | SetResult(&GetAccountTXListResp{}). 121 | Get(api.basePath) 122 | if err != nil { 123 | return accTXList, errors.New(SOURCE + " Error Requesting " + address) 124 | } 125 | accTXList = *resp.Result().(*GetAccountTXListResp) 126 | if useCache { 127 | err = db.Write("Etherscan.io/account/txlist", address, accTXList) 128 | if err != nil { 129 | return accTXList, errors.New(SOURCE + " Error Caching " + address) 130 | } 131 | } 132 | if accTXList.Message == "OK" { 133 | time.Sleep(api.timeBetweenReq) 134 | } 135 | } 136 | return accTXList, nil 137 | } 138 | -------------------------------------------------------------------------------- /cryptocom/api_exchange_deposits.go: -------------------------------------------------------------------------------- 1 | package cryptocom 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/fiscafacile/CryptoFiscaFacile/utils" 11 | "github.com/nanobox-io/golang-scribble" 12 | "github.com/shopspring/decimal" 13 | ) 14 | 15 | type depositTX struct { 16 | Timestamp time.Time 17 | ID string 18 | Description string 19 | Currency string 20 | Amount decimal.Decimal 21 | Fee decimal.Decimal 22 | } 23 | 24 | func (api *apiEx) getDepositsTXs(loc *time.Location) { 25 | const SOURCE = "Crypto.com Exchange API Deposits :" 26 | for y := 2019; y <= time.Now().Year(); y++ { 27 | for q := 1; q < 5; q++ { 28 | fmt.Print(".") 29 | depoHist, err := api.getDepositHistory(y, q, loc) 30 | if err != nil { 31 | // api.doneDep <- err 32 | // return 33 | log.Println(err) 34 | break 35 | } 36 | for _, dep := range depoHist.Result.DepositList { 37 | tx := depositTX{} 38 | tx.Timestamp = time.Unix(dep.UpdateTime/1000, 0) 39 | tx.ID = utils.GetUniqueID(SOURCE + tx.Timestamp.String()) 40 | tx.Description = "from " + dep.Address 41 | tx.Currency = dep.Currency 42 | tx.Amount = decimal.NewFromFloat(dep.Amount) 43 | tx.Fee = decimal.NewFromFloat(dep.Fee) 44 | api.depositTXs = append(api.depositTXs, tx) 45 | } 46 | } 47 | } 48 | api.doneDep <- nil 49 | } 50 | 51 | type ResultDeposit struct { 52 | Currency string `json:"currency"` 53 | ClientWid string `json:"client_wid"` 54 | Fee float64 `json:"fee"` 55 | CreateTime int64 `json:"create_time"` 56 | ID string `json:"id"` 57 | UpdateTime int64 `json:"update_time"` 58 | Amount float64 `json:"amount"` 59 | Address string `json:"address"` 60 | Status string `json:"status"` 61 | } 62 | 63 | type DepositList struct { 64 | DepositList []ResultDeposit `json:"deposit_list"` 65 | } 66 | 67 | type GetDepositHistoryResp struct { 68 | ID int64 `json:"id"` 69 | Method string `json:"method"` 70 | Code int `json:"code"` 71 | Result DepositList `json:"result"` 72 | } 73 | 74 | func (api *apiEx) getDepositHistory(year, quarter int, loc *time.Location) (depoHist GetDepositHistoryResp, err error) { 75 | const SOURCE = "Crypto.com Exchange API Deposits :" 76 | var start_month time.Month 77 | var end_month time.Month 78 | end_year := year 79 | period := strconv.Itoa(year) + "-Q" + strconv.Itoa(quarter) 80 | if quarter == 1 { 81 | start_month = time.January 82 | end_month = time.April 83 | } else if quarter == 2 { 84 | start_month = time.April 85 | end_month = time.July 86 | } else if quarter == 3 { 87 | start_month = time.July 88 | end_month = time.October 89 | } else if quarter == 4 { 90 | start_month = time.October 91 | end_month = time.January 92 | end_year = year + 1 93 | } else { 94 | err = errors.New(SOURCE + " Invalid Quarter" + period) 95 | return 96 | } 97 | start_ts := time.Date(year, start_month, 1, 0, 0, 0, 0, loc) 98 | end_ts := time.Date(end_year, end_month, 1, 0, 0, 0, 0, loc) 99 | now := time.Now() 100 | if start_ts.After(now) { 101 | return // without error 102 | } 103 | if end_ts.After(now) { 104 | end_ts = now 105 | period += "-" + strconv.FormatInt(end_ts.Unix(), 10) 106 | } 107 | useCache := true 108 | db, err := scribble.New("./Cache", nil) 109 | if err != nil { 110 | useCache = false 111 | } 112 | if useCache { 113 | err = db.Read("Crypto.com/Exchange/private/get-deposit-history", period, &depoHist) 114 | } 115 | if !useCache || err != nil { 116 | method := "private/get-deposit-history" 117 | body := make(map[string]interface{}) 118 | body["method"] = method 119 | body["params"] = map[string]interface{}{ 120 | "start_ts": start_ts.UnixNano() / 1e6, 121 | "end_ts": end_ts.UnixNano() / 1e6, 122 | "page_size": 200, 123 | "page": 0, 124 | "status": "1", 125 | } 126 | api.sign(body) 127 | resp, err := api.clientDep.R(). 128 | SetBody(body). 129 | SetResult(&GetDepositHistoryResp{}). 130 | SetError(&ErrorResp{}). 131 | Post(api.basePath + method) 132 | if err != nil { 133 | return depoHist, errors.New(SOURCE + " Error Requesting" + period) 134 | } 135 | if resp.StatusCode() > 300 { 136 | return depoHist, errors.New(SOURCE + " Error StatusCode" + strconv.Itoa(resp.StatusCode()) + " for " + period) 137 | } 138 | depoHist = *resp.Result().(*GetDepositHistoryResp) 139 | if useCache { 140 | err = db.Write("Crypto.com/Exchange/private/get-deposit-history", period, depoHist) 141 | if err != nil { 142 | return depoHist, errors.New(SOURCE + " Error Caching" + period) 143 | } 144 | } 145 | time.Sleep(api.timeBetweenReq) 146 | } 147 | return depoHist, nil 148 | } 149 | -------------------------------------------------------------------------------- /binance/api_withdrawals.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "time" 9 | 10 | scribble "github.com/nanobox-io/golang-scribble" 11 | "github.com/shopspring/decimal" 12 | ) 13 | 14 | type withdrawalTX struct { 15 | Timestamp time.Time 16 | ID string 17 | Description string 18 | Currency string 19 | Amount decimal.Decimal 20 | Fee decimal.Decimal 21 | } 22 | 23 | func (api *api) getWithdrawalsTXs(loc *time.Location) { 24 | today := time.Now() 25 | thisYear := today.Year() 26 | for y := thisYear; y > 2017; y-- { 27 | for t := 6; t > 0; t-- { 28 | fmt.Print(".") 29 | withHist, err := api.getWithdrawalHistory(y, t, loc) 30 | if err != nil { 31 | // api.doneWit <- err 32 | // return 33 | log.Println(err) 34 | break 35 | } 36 | for _, wit := range withHist { 37 | tx := withdrawalTX{} 38 | tx.Timestamp, err = time.Parse("2006-01-02 15:04:05", wit.ApplyTime) 39 | if err != nil { 40 | log.Println("Error Parsing Time : ", wit.ApplyTime) 41 | } 42 | tx.ID = wit.TxID 43 | tx.Description = "to " + wit.Address 44 | tx.Currency = wit.Coin 45 | tx.Amount, _ = decimal.NewFromString(wit.Amount) 46 | api.withdrawalTXs = append(api.withdrawalTXs, tx) 47 | } 48 | } 49 | } 50 | api.doneWit <- nil 51 | } 52 | 53 | type GetWithdrawalHistoryResp []struct { 54 | ApplyTime string `json:"applyTime"` 55 | Amount string `json:"amount"` 56 | Coin string `json:"coin"` 57 | Network string `json:"network"` 58 | Status int `json:"status"` 59 | Address string `json:"address"` 60 | ID string `json:"id"` 61 | TxID string `json:"txId"` 62 | Transfertype int `json:"transferType"` 63 | WithdrawOrderId string `json:"withdrawOrderId"` 64 | } 65 | 66 | func (api *api) getWithdrawalHistory(year, trimester int, loc *time.Location) (withHist GetWithdrawalHistoryResp, err error) { 67 | var start_month time.Month 68 | var end_month time.Month 69 | end_year := year 70 | period := strconv.Itoa(year) + "-T" + strconv.Itoa(trimester) 71 | if trimester == 1 { 72 | start_month = time.January 73 | end_month = time.March 74 | } else if trimester == 2 { 75 | start_month = time.March 76 | end_month = time.May 77 | } else if trimester == 3 { 78 | start_month = time.May 79 | end_month = time.July 80 | } else if trimester == 4 { 81 | start_month = time.July 82 | end_month = time.September 83 | } else if trimester == 5 { 84 | start_month = time.September 85 | end_month = time.November 86 | } else if trimester == 6 { 87 | start_month = time.November 88 | end_month = time.January 89 | end_year = year + 1 90 | } else { 91 | err = errors.New("Binance API Withdrawals : Invalid trimester" + period) 92 | return 93 | } 94 | start_ts := time.Date(year, start_month, 1, 0, 0, 0, 0, loc) 95 | end_ts := time.Date(end_year, end_month, 1, 0, 0, 0, 0, loc) 96 | now := time.Now() 97 | if start_ts.After(now) { 98 | return 99 | } 100 | if end_ts.After(now) { 101 | end_ts = now 102 | period += "-" + strconv.FormatInt(end_ts.Unix(), 10) 103 | } 104 | useCache := true 105 | db, err := scribble.New("./Cache", nil) 106 | if err != nil { 107 | useCache = false 108 | } 109 | if useCache { 110 | err = db.Read("Binance/sapi/v1/capital/withdraw/history", period, &withHist) 111 | } 112 | if !useCache || err != nil { 113 | endpoint := "sapi/v1/capital/withdraw/history" 114 | queryParams := map[string]string{ 115 | "status": "6", 116 | "startTime": fmt.Sprintf("%v", start_ts.UTC().UnixNano()/1e6), 117 | "endTime": fmt.Sprintf("%v", end_ts.UTC().UnixNano()/1e6), 118 | "recvWindow": "60000", 119 | "timestamp": fmt.Sprintf("%v", time.Now().UTC().UnixNano()/1e6), 120 | } 121 | api.sign(queryParams) 122 | resp, err := api.clientWit.R(). 123 | SetHeader("X-MBX-APIKEY", api.apiKey). 124 | SetQueryParams(queryParams). 125 | SetResult(&GetWithdrawalHistoryResp{}). 126 | SetError(&ErrorResp{}). 127 | Get(api.basePath + endpoint) 128 | if err != nil { 129 | return withHist, errors.New("Binance API Withdrawals : Error Requesting" + period) 130 | } 131 | if resp.StatusCode() > 300 { 132 | return withHist, errors.New("Binance API Withdrawals : Error StatusCode" + strconv.Itoa(resp.StatusCode()) + " for " + period) 133 | } 134 | withHist = *resp.Result().(*GetWithdrawalHistoryResp) 135 | if useCache { 136 | err = db.Write("Binance/sapi/v1/capital/withdraw/history", period, withHist) 137 | if err != nil { 138 | return withHist, errors.New("Binance API Withdrawals : Error Caching" + period) 139 | } 140 | } 141 | time.Sleep(api.timeBetweenRequests) 142 | } 143 | return withHist, nil 144 | } 145 | -------------------------------------------------------------------------------- /cryptocom/api_exchange_withdrawals.go: -------------------------------------------------------------------------------- 1 | package cryptocom 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/fiscafacile/CryptoFiscaFacile/utils" 11 | "github.com/nanobox-io/golang-scribble" 12 | "github.com/shopspring/decimal" 13 | ) 14 | 15 | type withdrawalTX struct { 16 | Timestamp time.Time 17 | ID string 18 | Description string 19 | Currency string 20 | Amount decimal.Decimal 21 | Fee decimal.Decimal 22 | } 23 | 24 | func (api *apiEx) getWithdrawalsTXs(loc *time.Location) { 25 | const SOURCE = "Crypto.com Exchange API Withdrawals :" 26 | for y := 2019; y <= time.Now().Year(); y++ { 27 | for q := 1; q < 5; q++ { 28 | fmt.Print(".") 29 | withHist, err := api.getWithdrawalHistory(y, q, loc) 30 | if err != nil { 31 | // api.doneWit <- err 32 | // return 33 | log.Println(err) 34 | break 35 | } 36 | for _, wit := range withHist.Result.WithdrawalList { 37 | tx := withdrawalTX{} 38 | tx.Timestamp = time.Unix(wit.UpdateTime/1000, 0) 39 | tx.ID = utils.GetUniqueID(SOURCE + tx.Timestamp.String()) 40 | tx.Description = "to " + wit.Address 41 | tx.Currency = wit.Currency 42 | tx.Amount = decimal.NewFromFloat(wit.Amount) 43 | tx.Fee = decimal.NewFromFloat(wit.Fee) 44 | api.withdrawalTXs = append(api.withdrawalTXs, tx) 45 | } 46 | } 47 | } 48 | api.doneWit <- nil 49 | } 50 | 51 | type ResultWithdrawal struct { 52 | Currency string `json:"currency"` 53 | ClientWid string `json:"client_wid"` 54 | Fee float64 `json:"fee"` 55 | CreateTime int64 `json:"create_time"` 56 | ID string `json:"id"` 57 | UpdateTime int64 `json:"update_time"` 58 | Amount float64 `json:"amount"` 59 | Address string `json:"address"` 60 | Status string `json:"status"` 61 | } 62 | 63 | type WithdrawalList struct { 64 | WithdrawalList []ResultWithdrawal `json:"withdrawal_list"` 65 | } 66 | 67 | type GetWithdrawalHistoryResp struct { 68 | ID int64 `json:"id"` 69 | Method string `json:"method"` 70 | Code int `json:"code"` 71 | Result WithdrawalList `json:"result"` 72 | } 73 | 74 | func (api *apiEx) getWithdrawalHistory(year, quarter int, loc *time.Location) (withHist GetWithdrawalHistoryResp, err error) { 75 | const SOURCE = "Crypto.com Exchange API Withdrawals :" 76 | var start_month time.Month 77 | var end_month time.Month 78 | end_year := year 79 | period := strconv.Itoa(year) + "-Q" + strconv.Itoa(quarter) 80 | if quarter == 1 { 81 | start_month = time.January 82 | end_month = time.April 83 | } else if quarter == 2 { 84 | start_month = time.April 85 | end_month = time.July 86 | } else if quarter == 3 { 87 | start_month = time.July 88 | end_month = time.October 89 | } else if quarter == 4 { 90 | start_month = time.October 91 | end_month = time.January 92 | end_year = year + 1 93 | } else { 94 | err = errors.New(SOURCE + " Invalid Quarter" + period) 95 | return 96 | } 97 | start_ts := time.Date(year, start_month, 1, 0, 0, 0, 0, loc) 98 | end_ts := time.Date(end_year, end_month, 1, 0, 0, 0, 0, loc) 99 | now := time.Now() 100 | if start_ts.After(now) { 101 | return // without error 102 | } 103 | if end_ts.After(now) { 104 | end_ts = now 105 | period += "-" + strconv.FormatInt(end_ts.Unix(), 10) 106 | } 107 | useCache := true 108 | db, err := scribble.New("./Cache", nil) 109 | if err != nil { 110 | useCache = false 111 | } 112 | if useCache { 113 | err = db.Read("Crypto.com/Exchange/private/get-withdrawal-history", period, &withHist) 114 | } 115 | if !useCache || err != nil { 116 | method := "private/get-withdrawal-history" 117 | body := make(map[string]interface{}) 118 | body["method"] = method 119 | body["params"] = map[string]interface{}{ 120 | "start_ts": start_ts.UnixNano() / 1e6, 121 | "end_ts": end_ts.UnixNano() / 1e6, 122 | "page_size": 200, 123 | "page": 0, 124 | "status": "5", 125 | } 126 | api.sign(body) 127 | resp, err := api.clientWit.R(). 128 | SetBody(body). 129 | SetResult(&GetWithdrawalHistoryResp{}). 130 | SetError(&ErrorResp{}). 131 | Post(api.basePath + method) 132 | if err != nil { 133 | return withHist, errors.New(SOURCE + " Error Requesting" + period) 134 | } 135 | if resp.StatusCode() > 300 { 136 | return withHist, errors.New(SOURCE + " Error StatusCode" + strconv.Itoa(resp.StatusCode()) + " for " + period) 137 | } 138 | withHist = *resp.Result().(*GetWithdrawalHistoryResp) 139 | if useCache { 140 | err = db.Write("Crypto.com/Exchange/private/get-withdrawal-history", period, withHist) 141 | if err != nil { 142 | return withHist, errors.New(SOURCE + " Error Caching" + period) 143 | } 144 | } 145 | time.Sleep(api.timeBetweenReq) 146 | } 147 | return withHist, nil 148 | } 149 | -------------------------------------------------------------------------------- /poloniex/csv_trades.go: -------------------------------------------------------------------------------- 1 | package poloniex 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "strings" 8 | "time" 9 | 10 | "github.com/fiscafacile/CryptoFiscaFacile/category" 11 | "github.com/fiscafacile/CryptoFiscaFacile/source" 12 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 13 | "github.com/shopspring/decimal" 14 | ) 15 | 16 | type csvTradesTX struct { 17 | Date time.Time 18 | Market string 19 | FirstCurrency string 20 | SecondCurrency string 21 | Category string 22 | Type string 23 | Price decimal.Decimal 24 | Amount decimal.Decimal 25 | Total decimal.Decimal 26 | Fee string 27 | OrderNumber string 28 | BaseTotalLessFee decimal.Decimal 29 | QuoteTotalLessFee decimal.Decimal 30 | FeeCurrency string 31 | FeeTotal decimal.Decimal 32 | } 33 | 34 | func (pl *Poloniex) ParseTradesCSV(reader io.Reader, cat category.Category, account string) (err error) { 35 | firstTimeUsed := time.Now() 36 | lastTimeUsed := time.Date(2009, time.January, 1, 0, 0, 0, 0, time.UTC) 37 | const SOURCE = "Poloniex Trades CSV :" 38 | csvReader := csv.NewReader(reader) 39 | records, err := csvReader.ReadAll() 40 | if err == nil { 41 | alreadyAsked := []string{} 42 | for _, r := range records { 43 | if r[0] != "Date" { 44 | tx := csvTradesTX{} 45 | tx.Date, err = time.Parse("2006-01-02 15:04:05", r[0]) 46 | if err != nil { 47 | log.Println(SOURCE, "Error Parsing Date", r[0]) 48 | } 49 | tx.Market = r[1] 50 | curr := strings.Split(r[1], "/") 51 | tx.FirstCurrency = curr[0] 52 | tx.SecondCurrency = curr[1] 53 | tx.Category = r[2] 54 | tx.Type = r[3] 55 | tx.Price, err = decimal.NewFromString(r[4]) 56 | if err != nil { 57 | log.Println(SOURCE, "Error Parsing Price", r[4]) 58 | } 59 | tx.Amount, err = decimal.NewFromString(r[5]) 60 | if err != nil { 61 | log.Println(SOURCE, "Error Parsing Amount", r[5]) 62 | } 63 | tx.Total, err = decimal.NewFromString(r[6]) 64 | if err != nil { 65 | log.Println(SOURCE, "Error Parsing Total", r[6]) 66 | } 67 | tx.Fee = r[7] 68 | tx.OrderNumber = r[8] 69 | tx.BaseTotalLessFee, err = decimal.NewFromString(r[9]) 70 | if err != nil { 71 | log.Println(SOURCE, "Error Parsing BaseTotalLessFee", r[9]) 72 | } 73 | tx.QuoteTotalLessFee, err = decimal.NewFromString(r[10]) 74 | if err != nil { 75 | log.Println(SOURCE, "Error Parsing QuoteTotalLessFee", r[10]) 76 | } 77 | tx.FeeCurrency = r[11] 78 | tx.FeeTotal, err = decimal.NewFromString(r[12]) 79 | if err != nil { 80 | log.Println(SOURCE, "Error Parsing FeeTotal", r[12]) 81 | } 82 | pl.csvTradesTXs = append(pl.csvTradesTXs, tx) 83 | if tx.Date.Before(firstTimeUsed) { 84 | firstTimeUsed = tx.Date 85 | } 86 | if tx.Date.After(lastTimeUsed) { 87 | lastTimeUsed = tx.Date 88 | } 89 | // Fill TXsByCategory 90 | t := wallet.TX{Timestamp: tx.Date, ID: tx.OrderNumber, Note: SOURCE + " " + tx.Market + " " + tx.Type} 91 | t.Items = make(map[string]wallet.Currencies) 92 | if is, desc, val, curr := cat.IsTxShit(tx.OrderNumber); is { 93 | t.Note += " " + desc 94 | t.Items["Lost"] = append(t.Items["Lost"], wallet.Currency{Code: curr, Amount: val}) 95 | } 96 | if tx.Type == "Buy" { 97 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.FeeCurrency, Amount: tx.Amount.Sub(tx.QuoteTotalLessFee)}) 98 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.FirstCurrency, Amount: tx.Amount}) 99 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.SecondCurrency, Amount: tx.Total}) 100 | pl.TXsByCategory["Exchanges"] = append(pl.TXsByCategory["Exchanges"], t) 101 | } else if tx.Type == "Sell" { 102 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.FeeCurrency, Amount: tx.FeeTotal}) 103 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.FirstCurrency, Amount: tx.Amount}) 104 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.SecondCurrency, Amount: tx.Total}) 105 | pl.TXsByCategory["Exchanges"] = append(pl.TXsByCategory["Exchanges"], t) 106 | } else { 107 | alreadyAsked = wallet.AskForHelp(SOURCE+" "+tx.Type, tx, alreadyAsked) 108 | } 109 | } 110 | } 111 | } 112 | if _, ok := pl.Sources["Poloniex"]; !ok { 113 | pl.Sources["Poloniex"] = source.Source{ 114 | Crypto: true, 115 | AccountNumber: account, 116 | OpeningDate: firstTimeUsed, 117 | ClosingDate: lastTimeUsed, 118 | LegalName: "Polo Digital Assets Ltd", 119 | Address: "F20, 1st Floor, Eden Plaza,\nEden Island,\nSeychelles", 120 | URL: "https://poloniex.com/", 121 | } 122 | } 123 | return 124 | } 125 | -------------------------------------------------------------------------------- /ledgerlive/csv.go: -------------------------------------------------------------------------------- 1 | package ledgerlive 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "github.com/fiscafacile/CryptoFiscaFacile/category" 10 | "github.com/fiscafacile/CryptoFiscaFacile/utils" 11 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 12 | "github.com/shopspring/decimal" 13 | ) 14 | 15 | type CsvTX struct { 16 | Date time.Time 17 | ID string 18 | Currency string 19 | Type string 20 | Amount decimal.Decimal 21 | Fees decimal.Decimal 22 | Hash string 23 | AccountName string 24 | AccountXpub string 25 | } 26 | 27 | func (ll *LedgerLive) ParseCSV(reader io.Reader, cat category.Category) (err error) { 28 | const SOURCE = "LedgerLive CSV" 29 | csvReader := csv.NewReader(reader) 30 | records, err := csvReader.ReadAll() 31 | if err == nil { 32 | alreadyAsked := []string{} 33 | for _, r := range records { 34 | if r[0] != "Operation Date" { 35 | tx := CsvTX{} 36 | tx.Date, err = time.Parse("2006-01-02T15:04:05.000Z", r[0]) 37 | if err != nil { 38 | log.Println(SOURCE, ": Error Parsing Date", r[0]) 39 | } 40 | tx.ID = utils.GetUniqueID(SOURCE + tx.Date.String()) 41 | tx.Currency = r[1] 42 | tx.Type = r[2] 43 | tx.Amount, err = decimal.NewFromString(r[3]) 44 | if err != nil { 45 | log.Println(SOURCE, ": Error Parsing Amount", r[3]) 46 | } 47 | if r[4] != "" { 48 | tx.Fees, err = decimal.NewFromString(r[4]) 49 | if err != nil { 50 | log.Println(SOURCE, ": Error Parsing Fees", r[4]) 51 | } 52 | } 53 | tx.Hash = r[5] 54 | tx.AccountName = r[6] 55 | tx.AccountXpub = r[7] 56 | ll.CsvTXs = append(ll.CsvTXs, tx) 57 | } 58 | } 59 | for _, tx := range ll.CsvTXs { 60 | // Fill TXsByCategory 61 | if tx.Type == "IN" || 62 | tx.Type == "REWARD_PAYOUT" || 63 | tx.Type == "REWARD" { 64 | t := wallet.TX{Timestamp: tx.Date, ID: tx.ID, Note: SOURCE + " " + tx.AccountName + " : " + tx.Hash + " -> " + tx.AccountXpub} 65 | t.Items = make(map[string]wallet.Currencies) 66 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount}) 67 | if !tx.Fees.IsZero() { 68 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.Currency, Amount: tx.Fees}) 69 | } 70 | if is, desc, val, curr := cat.IsTxCashIn(tx.Hash); is { 71 | t.Note += " crypto_purchase " + desc 72 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: curr, Amount: val}) 73 | ll.TXsByCategory["CashIn"] = append(ll.TXsByCategory["CashIn"], t) 74 | } else { 75 | if tx.Type == "REWARD_PAYOUT" || 76 | tx.Type == "REWARD" { 77 | ll.TXsByCategory["Minings"] = append(ll.TXsByCategory["Minings"], t) 78 | } else { 79 | ll.TXsByCategory["Deposits"] = append(ll.TXsByCategory["Deposits"], t) 80 | } 81 | } 82 | } else if tx.Type == "OUT" { 83 | if !tx.Fees.Equal(tx.Amount) { // ignore Fee associated to other OUT, will be found later 84 | t := wallet.TX{Timestamp: tx.Date, ID: tx.ID, Note: SOURCE + " " + tx.AccountName + " : " + tx.AccountXpub + " -> " + tx.Hash} 85 | t.Items = make(map[string]wallet.Currencies) 86 | if !tx.Fees.IsZero() { 87 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.Currency, Amount: tx.Fees}) 88 | } 89 | for _, tx2 := range ll.CsvTXs { 90 | if tx2.Hash == tx.Hash && 91 | tx2.Type == "OUT" && 92 | tx2.Fees.Equal(tx2.Amount) { 93 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx2.Currency, Amount: tx2.Fees}) 94 | } 95 | } 96 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.Currency, Amount: tx.Amount.Sub(tx.Fees)}) 97 | if is, desc, val, curr := cat.IsTxCashOut(tx.Hash); is { 98 | t.Note += " crypto_payment " + desc 99 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: curr, Amount: val}) 100 | ll.TXsByCategory["CashOut"] = append(ll.TXsByCategory["CashOut"], t) 101 | } else { 102 | ll.TXsByCategory["Withdrawals"] = append(ll.TXsByCategory["Withdrawals"], t) 103 | } 104 | } 105 | } else if tx.Type == "FEES" || 106 | tx.Type == "NOMINATE" || 107 | tx.Type == "BOND" || 108 | tx.Type == "DELEGATE" || 109 | tx.Type == "REVEAL" || 110 | tx.Type == "VOTE" || 111 | tx.Type == "FREEZE" { 112 | if !tx.Fees.IsZero() { 113 | t := wallet.TX{Timestamp: tx.Date, ID: tx.ID, Note: SOURCE + " " + tx.AccountName + " : " + tx.AccountXpub + " -> " + tx.Hash} 114 | t.Items = make(map[string]wallet.Currencies) 115 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.Currency, Amount: tx.Fees}) 116 | ll.TXsByCategory["Fees"] = append(ll.TXsByCategory["Fees"], t) 117 | } 118 | } else { 119 | alreadyAsked = wallet.AskForHelp(SOURCE+" : "+tx.Type, tx, alreadyAsked) 120 | } 121 | } 122 | } 123 | return 124 | } 125 | -------------------------------------------------------------------------------- /etherscan/api_nft.go: -------------------------------------------------------------------------------- 1 | package etherscan 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/nanobox-io/golang-scribble" 8 | "github.com/shopspring/decimal" 9 | ) 10 | 11 | type nftTX struct { 12 | used bool 13 | BlockNumber int 14 | TimeStamp time.Time 15 | Hash string 16 | Nonce int 17 | BlockHash string 18 | From string 19 | ContractAddress string 20 | To string 21 | TokenID string 22 | TokenName string 23 | TokenSymbol string 24 | TokenDecimal uint8 25 | TransactionIndex int 26 | Gas int 27 | GasPrice decimal.Decimal 28 | GasUsed decimal.Decimal 29 | CumulativeGasUsed int 30 | Input string 31 | Confirmations int 32 | } 33 | 34 | func (api *api) getNftTXs(addresses []string) { 35 | for _, eth := range addresses { 36 | accNftTX, err := api.getAccountNftTX(eth, "", false) 37 | if err != nil { 38 | api.doneNft <- err 39 | return 40 | } 41 | for _, nfTX := range accNftTX.Result { 42 | tx := nftTX{ 43 | used: false, 44 | BlockNumber: nfTX.BlockNumber, 45 | TimeStamp: time.Unix(nfTX.TimeStamp, 0), 46 | Hash: nfTX.Hash, 47 | Nonce: nfTX.Nonce, 48 | BlockHash: nfTX.BlockHash, 49 | From: nfTX.From, 50 | ContractAddress: nfTX.ContractAddress, 51 | To: nfTX.To, 52 | TokenID: nfTX.TokenID, 53 | TokenName: nfTX.TokenName, 54 | TokenSymbol: nfTX.TokenSymbol, 55 | TokenDecimal: nfTX.TokenDecimal, 56 | TransactionIndex: nfTX.TransactionIndex, 57 | Gas: nfTX.Gas, 58 | GasPrice: decimal.NewFromBigInt(nfTX.GasPrice.Int(), -18), 59 | GasUsed: decimal.NewFromInt(nfTX.GasUsed), 60 | CumulativeGasUsed: nfTX.CumulativeGasUsed, 61 | Input: nfTX.Input, 62 | Confirmations: nfTX.Confirmations, 63 | } 64 | api.nftTXs = append(api.nftTXs, tx) 65 | } 66 | } 67 | api.doneNft <- nil 68 | } 69 | 70 | type ResultNftTX struct { 71 | BlockNumber int `json:"blockNumber,string"` 72 | TimeStamp int64 `json:"timeStamp,string"` 73 | Hash string `json:"hash"` 74 | Nonce int `json:"nonce,string"` 75 | BlockHash string `json:"blockHash"` 76 | From string `json:"from"` 77 | ContractAddress string `json:"contractAddress"` 78 | To string `json:"to"` 79 | TokenID string `json:"tokenID"` 80 | TokenName string `json:"tokenName"` 81 | TokenSymbol string `json:"tokenSymbol"` 82 | TokenDecimal uint8 `json:"tokenDecimal,string"` 83 | TransactionIndex int `json:"transactionIndex,string"` 84 | Gas int `json:"gas,string"` 85 | GasPrice *bigInt `json:"gasPrice"` 86 | GasUsed int64 `json:"gasUsed,string"` 87 | CumulativeGasUsed int `json:"cumulativeGasUsed,string"` 88 | Input string `json:"input"` 89 | Confirmations int `json:"confirmations,string"` 90 | } 91 | 92 | type GetAccountNftTXResp struct { 93 | Status string `json:"status"` 94 | Message string `json:"message"` 95 | Result []ResultNftTX `json:"result"` 96 | } 97 | 98 | func (api *api) getAccountNftTX(address, contractAddress string, desc bool) (accNftTX GetAccountNftTXResp, err error) { 99 | const SOURCE = "Etherscan API Nft TX :" 100 | ident := "a" + address + "-c" + contractAddress 101 | useCache := true 102 | db, err := scribble.New("./Cache", nil) 103 | if err != nil { 104 | useCache = false 105 | } 106 | if useCache { 107 | err = db.Read("Etherscan.io/account/tokennfttx", ident, &accNftTX) 108 | } 109 | if !useCache || err != nil { 110 | params := map[string]string{ 111 | "module": "account", 112 | "action": "tokennfttx", 113 | "apikey": api.apiKey, 114 | } 115 | if address != "" { 116 | params["address"] = address 117 | } 118 | if contractAddress != "" { 119 | params["contractaddress"] = contractAddress 120 | } 121 | if desc { 122 | params["sort"] = "desc" 123 | } else { 124 | params["sort"] = "asc" 125 | } 126 | resp, err := api.clientNft.R(). 127 | SetQueryParams(params). 128 | SetHeader("Accept", "application/json"). 129 | SetResult(&GetAccountNftTXResp{}). 130 | Get(api.basePath) 131 | if err != nil { 132 | return accNftTX, errors.New(SOURCE + " Error Requesting " + ident) 133 | } 134 | accNftTX = *resp.Result().(*GetAccountNftTXResp) 135 | if useCache { 136 | err = db.Write("Etherscan.io/account/tokennfttx", ident, accNftTX) 137 | if err != nil { 138 | return accNftTX, errors.New(SOURCE + " Error Caching " + ident) 139 | } 140 | } 141 | if accNftTX.Message == "OK" { 142 | time.Sleep(api.timeBetweenReq) 143 | } 144 | } 145 | return accNftTX, nil 146 | } 147 | -------------------------------------------------------------------------------- /binance/api_assets_info.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "time" 7 | 8 | scribble "github.com/nanobox-io/golang-scribble" 9 | ) 10 | 11 | type Symbols struct { 12 | Symbol string `json:"symbol"` 13 | Status string `json:"status"` 14 | Baseasset string `json:"baseAsset"` 15 | Baseassetprecision int `json:"baseAssetPrecision"` 16 | Quoteasset string `json:"quoteAsset"` 17 | Quoteprecision int `json:"quotePrecision"` 18 | Quoteassetprecision int `json:"quoteAssetPrecision"` 19 | Basecommissionprecision int `json:"baseCommissionPrecision"` 20 | Quotecommissionprecision int `json:"quoteCommissionPrecision"` 21 | Ordertypes []string `json:"orderTypes"` 22 | Icebergallowed bool `json:"icebergAllowed"` 23 | Ocoallowed bool `json:"ocoAllowed"` 24 | Quoteorderqtymarketallowed bool `json:"quoteOrderQtyMarketAllowed"` 25 | Isspottradingallowed bool `json:"isSpotTradingAllowed"` 26 | Ismargintradingallowed bool `json:"isMarginTradingAllowed"` 27 | Filters []struct { 28 | Filtertype string `json:"filterType"` 29 | Minprice string `json:"minPrice,omitempty"` 30 | Maxprice string `json:"maxPrice,omitempty"` 31 | Ticksize string `json:"tickSize,omitempty"` 32 | Multiplierup string `json:"multiplierUp,omitempty"` 33 | Multiplierdown string `json:"multiplierDown,omitempty"` 34 | Avgpricemins int `json:"avgPriceMins,omitempty"` 35 | Minqty string `json:"minQty,omitempty"` 36 | Maxqty string `json:"maxQty,omitempty"` 37 | Stepsize string `json:"stepSize,omitempty"` 38 | Minnotional string `json:"minNotional,omitempty"` 39 | Applytomarket bool `json:"applyToMarket,omitempty"` 40 | Limit int `json:"limit,omitempty"` 41 | Maxnumorders int `json:"maxNumOrders,omitempty"` 42 | Maxnumalgoorders int `json:"maxNumAlgoOrders,omitempty"` 43 | } `json:"filters"` 44 | Permissions []string `json:"permissions"` 45 | } 46 | 47 | type Ratelimits struct { 48 | Ratelimittype string `json:"rateLimitType"` 49 | Interval string `json:"interval"` 50 | Intervalnum int `json:"intervalNum"` 51 | Limit int `json:"limit"` 52 | } 53 | 54 | type GetExchangeInfoResp struct { 55 | Timezone string `json:"timezone"` 56 | Servertime int64 `json:"serverTime"` 57 | Ratelimits []Ratelimits `json:"rateLimits"` 58 | Exchangefilters []interface{} `json:"exchangeFilters"` 59 | Symbols []Symbols `json:"symbols"` 60 | } 61 | 62 | func (api *api) getExchangeInfo() (exchangeInfo GetExchangeInfoResp, err error) { 63 | useCache := true 64 | db, err := scribble.New("./Cache", nil) 65 | if err != nil { 66 | useCache = false 67 | } 68 | if useCache { 69 | err = db.Read("Binance/api/v3", "exchangeInfo", &exchangeInfo) 70 | } 71 | if !useCache || err != nil { 72 | method := "api/v3/exchangeInfo" 73 | resp, err := api.clientExInfo.R(). 74 | SetResult(&GetExchangeInfoResp{}). 75 | SetError(&ErrorResp{}). 76 | Get(api.basePath + method) 77 | if err != nil { 78 | return exchangeInfo, errors.New("Binance API ExchangeInfo : Error Requesting") 79 | } 80 | if resp.StatusCode() > 300 { 81 | return exchangeInfo, errors.New("Binance API ExchangeInfo : Error StatusCode " + strconv.Itoa(resp.StatusCode())) 82 | } 83 | exchangeInfo = *resp.Result().(*GetExchangeInfoResp) 84 | if useCache { 85 | err = db.Write("Binance/api/v3", "exchangeInfo", exchangeInfo) 86 | if err != nil { 87 | return exchangeInfo, errors.New("Binance API ExchangeInfo : Error Caching") 88 | } 89 | } 90 | } 91 | api.symbols = exchangeInfo.Symbols 92 | for _, r := range exchangeInfo.Ratelimits { 93 | if r.Ratelimittype == "REQUEST_WEIGHT" { 94 | api.reqWeightInterval = r.Interval 95 | api.reqWeightIntervalNum = r.Intervalnum 96 | api.reqWeightlimit = r.Limit 97 | switch { 98 | case r.Interval == "SECOND": 99 | api.reqWeightTimeToWait = time.Duration(r.Intervalnum * int(time.Second)) 100 | case r.Interval == "MINUTE": 101 | api.reqWeightTimeToWait = time.Duration(r.Intervalnum * int(time.Minute)) 102 | case r.Interval == "HOUR": 103 | api.reqWeightTimeToWait = time.Duration(r.Intervalnum * int(time.Hour)) 104 | } 105 | } else if r.Ratelimittype == "ORDERS" { 106 | switch { 107 | case r.Interval == "SECOND": 108 | api.timeBetweenRequests = time.Duration(r.Intervalnum * int(time.Second) / r.Limit) 109 | case r.Interval == "MINUTE": 110 | api.timeBetweenRequests = time.Duration(r.Intervalnum * int(time.Minute) / r.Limit) 111 | case r.Interval == "HOUR": 112 | api.timeBetweenRequests = time.Duration(r.Intervalnum * int(time.Hour) / r.Limit) 113 | } 114 | } 115 | } 116 | return exchangeInfo, nil 117 | } 118 | -------------------------------------------------------------------------------- /etherscan/api_token.go: -------------------------------------------------------------------------------- 1 | package etherscan 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | 7 | "github.com/nanobox-io/golang-scribble" 8 | "github.com/shopspring/decimal" 9 | ) 10 | 11 | type tokenTX struct { 12 | used bool 13 | BlockNumber int 14 | TimeStamp time.Time 15 | Hash string 16 | Nonce int 17 | BlockHash string 18 | From string 19 | ContractAddress string 20 | To string 21 | Value decimal.Decimal 22 | TokenName string 23 | TokenSymbol string 24 | TokenDecimal uint8 25 | TransactionIndex int 26 | Gas int 27 | GasPrice decimal.Decimal 28 | GasUsed decimal.Decimal 29 | CumulativeGasUsed int 30 | Input string 31 | Confirmations int 32 | } 33 | 34 | func (api *api) getTokenTXs(addresses []string) { 35 | for _, eth := range addresses { 36 | accTokTX, err := api.getAccountTokenTX(eth, "", false) 37 | if err != nil { 38 | api.doneTok <- err 39 | return 40 | } 41 | for _, tokTX := range accTokTX.Result { 42 | tx := tokenTX{ 43 | used: false, 44 | BlockNumber: tokTX.BlockNumber, 45 | TimeStamp: time.Unix(tokTX.TimeStamp, 0), 46 | Hash: tokTX.Hash, 47 | Nonce: tokTX.Nonce, 48 | BlockHash: tokTX.BlockHash, 49 | From: tokTX.From, 50 | ContractAddress: tokTX.ContractAddress, 51 | To: tokTX.To, 52 | Value: decimal.NewFromBigInt(tokTX.Value.Int(), -int32(tokTX.TokenDecimal)), 53 | TokenName: tokTX.TokenName, 54 | TokenSymbol: tokTX.TokenSymbol, 55 | TokenDecimal: tokTX.TokenDecimal, 56 | TransactionIndex: tokTX.TransactionIndex, 57 | Gas: tokTX.Gas, 58 | GasPrice: decimal.NewFromBigInt(tokTX.GasPrice.Int(), -18), 59 | GasUsed: decimal.NewFromInt(tokTX.GasUsed), 60 | CumulativeGasUsed: tokTX.CumulativeGasUsed, 61 | Input: tokTX.Input, 62 | Confirmations: tokTX.Confirmations, 63 | } 64 | api.tokenTXs = append(api.tokenTXs, tx) 65 | } 66 | } 67 | api.doneTok <- nil 68 | } 69 | 70 | type ResultTokenTX struct { 71 | BlockNumber int `json:"blockNumber,string"` 72 | TimeStamp int64 `json:"timeStamp,string"` 73 | Hash string `json:"hash"` 74 | Nonce int `json:"nonce,string"` 75 | BlockHash string `json:"blockHash"` 76 | From string `json:"from"` 77 | ContractAddress string `json:"contractAddress"` 78 | To string `json:"to"` 79 | Value *bigInt `json:"value"` 80 | TokenName string `json:"tokenName"` 81 | TokenSymbol string `json:"tokenSymbol"` 82 | TokenDecimal uint8 `json:"tokenDecimal,string"` 83 | TransactionIndex int `json:"transactionIndex,string"` 84 | Gas int `json:"gas,string"` 85 | GasPrice *bigInt `json:"gasPrice"` 86 | GasUsed int64 `json:"gasUsed,string"` 87 | CumulativeGasUsed int `json:"cumulativeGasUsed,string"` 88 | Input string `json:"input"` 89 | Confirmations int `json:"confirmations,string"` 90 | } 91 | 92 | type GetAccountTokenTXResp struct { 93 | Status string `json:"status"` 94 | Message string `json:"message"` 95 | Result []ResultTokenTX `json:"result"` 96 | } 97 | 98 | func (api *api) getAccountTokenTX(address, contractAddress string, desc bool) (accTokTX GetAccountTokenTXResp, err error) { 99 | const SOURCE = "Etherscan API TokenTX :" 100 | ident := "a" + address + "-c" + contractAddress 101 | useCache := true 102 | db, err := scribble.New("./Cache", nil) 103 | if err != nil { 104 | useCache = false 105 | } 106 | if useCache { 107 | err = db.Read("Etherscan.io/account/tokentx", ident, &accTokTX) 108 | } 109 | if !useCache || err != nil { 110 | params := map[string]string{ 111 | "module": "account", 112 | "action": "tokentx", 113 | "apikey": api.apiKey, 114 | } 115 | if address != "" { 116 | params["address"] = address 117 | } 118 | if contractAddress != "" { 119 | params["contractaddress"] = contractAddress 120 | } 121 | if desc { 122 | params["sort"] = "desc" 123 | } else { 124 | params["sort"] = "asc" 125 | } 126 | resp, err := api.clientTok.R(). 127 | SetQueryParams(params). 128 | SetHeader("Accept", "application/json"). 129 | SetResult(&GetAccountTokenTXResp{}). 130 | Get(api.basePath) 131 | if err != nil { 132 | return accTokTX, errors.New(SOURCE + " Error Requesting " + ident) 133 | } 134 | accTokTX = *resp.Result().(*GetAccountTokenTXResp) 135 | if useCache { 136 | err = db.Write("Etherscan.io/account/tokentx", ident, accTokTX) 137 | if err != nil { 138 | return accTokTX, errors.New(SOURCE + " Error Caching " + ident) 139 | } 140 | } 141 | if accTokTX.Message == "OK" { 142 | time.Sleep(api.timeBetweenReq) 143 | } 144 | } 145 | return accTokTX, nil 146 | } 147 | -------------------------------------------------------------------------------- /binance/api_spot_trades.go: -------------------------------------------------------------------------------- 1 | package binance 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | scribble "github.com/nanobox-io/golang-scribble" 10 | "github.com/shopspring/decimal" 11 | ) 12 | 13 | type spotTradeTX struct { 14 | Timestamp time.Time 15 | ID string 16 | Description string 17 | BaseAsset string 18 | QuoteAsset string 19 | Side string 20 | Price decimal.Decimal 21 | Qty decimal.Decimal 22 | QuoteQty decimal.Decimal 23 | Fee decimal.Decimal 24 | FeeCurrency string 25 | } 26 | 27 | func (api *api) getSpotTradesTXs() { 28 | limit := 10 29 | totalSymbols := len(api.symbols) 30 | for i, symbol := range api.symbols { 31 | orderId := 0 32 | part := 0 33 | if api.debug { 34 | fmt.Printf("[%v/%v] Récupération des trades du symbole %v\n", i+1, totalSymbols, symbol.Symbol) 35 | } 36 | for { 37 | fmt.Print(".") 38 | trades, err := api.getTrades(symbol.Symbol, limit, orderId+1, part) 39 | if err != nil { 40 | api.doneSpotTra <- err 41 | return 42 | } 43 | for _, tra := range trades { 44 | tx := spotTradeTX{} 45 | tx.Timestamp = time.Unix(tra.Time/1e3, 0) 46 | tx.ID = strconv.Itoa(tra.OrderID) 47 | tx.Description = fmt.Sprintf("Order ID: %v, Trade ID: %v", tra.OrderID, tra.ID) 48 | tx.BaseAsset = symbol.Baseasset 49 | tx.QuoteAsset = symbol.Quoteasset 50 | if tra.IsBuyer { 51 | tx.Side = "BUY" 52 | } else { 53 | tx.Side = "SELL" 54 | } 55 | tx.Price, _ = decimal.NewFromString(tra.Price) 56 | tx.Qty, _ = decimal.NewFromString(tra.Qty) 57 | tx.QuoteQty, _ = decimal.NewFromString(tra.QuoteQty) 58 | tx.Fee, _ = decimal.NewFromString(tra.Commission) 59 | tx.FeeCurrency = tra.CommissionAsset 60 | api.spotTradeTXs = append(api.spotTradeTXs, tx) 61 | } 62 | if len(trades) == limit { 63 | part += 1 64 | orderId = trades[len(trades)-1].ID 65 | } else { 66 | break 67 | } 68 | } 69 | } 70 | api.doneSpotTra <- nil 71 | } 72 | 73 | type GetTradesResp []struct { 74 | Symbol string `json:"symbol"` 75 | ID int `json:"id"` 76 | OrderID int `json:"orderId"` 77 | OrderListID int `json:"orderListId"` 78 | Price string `json:"price"` 79 | Qty string `json:"qty"` 80 | QuoteQty string `json:"quoteQty"` 81 | Commission string `json:"commission"` 82 | CommissionAsset string `json:"commissionAsset"` 83 | Time int64 `json:"time"` 84 | IsBuyer bool `json:"isBuyer"` 85 | IsMaker bool `json:"isMaker"` 86 | IsBestMatch bool `json:"isBestMatch"` 87 | } 88 | 89 | func (api *api) getTrades(symbol string, limit int, orderId int, part int) (trades GetTradesResp, err error) { 90 | useCache := true 91 | db, err := scribble.New("./Cache", nil) 92 | if err != nil { 93 | useCache = false 94 | } 95 | if useCache { 96 | err = db.Read("Binance/api/v3/myTrades", fmt.Sprintf("%v_%v-%v", symbol, part*limit, part*limit+limit), &trades) 97 | // if err == nil && len(trades) != limit { // If cached data is incomplete 98 | // useCache = false 99 | // } 100 | } 101 | if !useCache || err != nil { 102 | endpoint := "api/v3/myTrades" 103 | queryParams := map[string]string{ 104 | "symbol": symbol, 105 | "fromId": fmt.Sprintf("%v", orderId), 106 | "limit": fmt.Sprintf("%v", limit), 107 | "recvWindow": "60000", 108 | "timestamp": fmt.Sprintf("%v", time.Now().UTC().UnixNano()/1e6), 109 | } 110 | api.sign(queryParams) 111 | resp, err := api.clientSpotTra.R(). 112 | SetHeader("X-MBX-APIKEY", api.apiKey). 113 | SetQueryParams(queryParams). 114 | SetResult(&GetTradesResp{}). 115 | SetError(&ErrorResp{}). 116 | Get(api.basePath + endpoint) 117 | if err != nil { 118 | return trades, errors.New("Binance API Trades : Error Requesting " + symbol) 119 | } 120 | if resp.StatusCode() > 300 { 121 | return trades, errors.New("Binance API Trades : Error StatusCode " + strconv.Itoa(resp.StatusCode()) + " for " + symbol) 122 | } 123 | trades = *resp.Result().(*GetTradesResp) 124 | if useCache { 125 | err = db.Write("Binance/api/v3/myTrades/", fmt.Sprintf("%v_%v-%v", symbol, part*limit, part*limit+limit), trades) 126 | if err != nil { 127 | return trades, errors.New("Binance API Trades : Error Caching" + fmt.Sprintf("%v_%v-%v", symbol, part*limit, part*limit+limit)) 128 | } 129 | } 130 | weightHeader := fmt.Sprintf("X-MBX-USED-WEIGHT-%v%v", api.reqWeightIntervalNum, string(api.reqWeightInterval[0])) 131 | usedWeight, _ := strconv.Atoi(resp.Header().Get(weightHeader)) 132 | if usedWeight >= api.reqWeightlimit-20 { 133 | if api.debug { 134 | fmt.Println(usedWeight, "/", api.reqWeightlimit, "Weight utilisé --> pause pendant", api.reqWeightTimeToWait) 135 | } 136 | time.Sleep(api.reqWeightTimeToWait) 137 | } else { 138 | if api.debug { 139 | fmt.Println(usedWeight, "/", api.reqWeightlimit, "Weight utilisé --> pause pendant", api.timeBetweenRequests) 140 | } 141 | time.Sleep(api.timeBetweenRequests) 142 | } 143 | } 144 | return trades, nil 145 | } 146 | -------------------------------------------------------------------------------- /uphold/csv.go: -------------------------------------------------------------------------------- 1 | package uphold 2 | 3 | import ( 4 | "encoding/csv" 5 | "io" 6 | "log" 7 | "time" 8 | 9 | "github.com/fiscafacile/CryptoFiscaFacile/category" 10 | "github.com/fiscafacile/CryptoFiscaFacile/source" 11 | "github.com/fiscafacile/CryptoFiscaFacile/wallet" 12 | "github.com/shopspring/decimal" 13 | ) 14 | 15 | type CsvTX struct { 16 | Date time.Time 17 | Destination string 18 | DestinationAmount decimal.Decimal 19 | DestinationCurrency string 20 | FeeAmount decimal.Decimal 21 | FeeCurrency string 22 | ID string 23 | Origin string 24 | OriginAmount decimal.Decimal 25 | OriginCurrency string 26 | Status string 27 | Type string 28 | } 29 | 30 | func (uh *Uphold) ParseCSV(reader io.Reader, cat category.Category, account string) (err error) { 31 | firstTimeUsed := time.Now() 32 | lastTimeUsed := time.Date(2009, time.January, 1, 0, 0, 0, 0, time.UTC) 33 | const SOURCE = "Uphold CSV :" 34 | csvReader := csv.NewReader(reader) 35 | records, err := csvReader.ReadAll() 36 | if err == nil { 37 | alreadyAsked := []string{} 38 | for _, r := range records { 39 | if r[0] != "Date" { 40 | tx := CsvTX{} 41 | tx.Date, err = time.Parse("Mon Jan 02 2006 15:04:05 GMT-0700", r[0]) 42 | if err != nil { 43 | log.Println(SOURCE, "Error Parsing Date :", r[0]) 44 | } 45 | tx.Destination = r[1] 46 | tx.DestinationAmount, err = decimal.NewFromString(r[2]) 47 | if err != nil { 48 | log.Println(SOURCE, "Error Parsing DestinationAmount :", r[2]) 49 | } 50 | tx.DestinationCurrency = r[3] 51 | if r[4] != "" { 52 | tx.FeeAmount, err = decimal.NewFromString(r[4]) 53 | if err != nil { 54 | log.Println(SOURCE, "Error Parsing FeeAmount :", r[4]) 55 | } 56 | } 57 | tx.FeeCurrency = r[5] 58 | tx.ID = r[6] 59 | tx.Origin = r[7] 60 | tx.OriginAmount, err = decimal.NewFromString(r[8]) 61 | if err != nil { 62 | log.Println(SOURCE, "Error Parsing OriginAmount :", r[8]) 63 | } 64 | tx.OriginCurrency = r[9] 65 | tx.Status = r[10] 66 | tx.Type = r[11] 67 | uh.CsvTXs = append(uh.CsvTXs, tx) 68 | if tx.Date.Before(firstTimeUsed) { 69 | firstTimeUsed = tx.Date 70 | } 71 | if tx.Date.After(lastTimeUsed) { 72 | lastTimeUsed = tx.Date 73 | } 74 | // Fill TXsByCategory 75 | if tx.OriginCurrency != tx.DestinationCurrency { 76 | t := wallet.TX{Timestamp: tx.Date, ID: "e" + tx.ID, Note: SOURCE + " exchange"} 77 | t.Items = make(map[string]wallet.Currencies) 78 | if tx.FeeCurrency == tx.DestinationCurrency && 79 | !tx.FeeAmount.IsZero() { 80 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.DestinationCurrency, Amount: tx.DestinationAmount.Add(tx.FeeAmount)}) 81 | } else { 82 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.DestinationCurrency, Amount: tx.DestinationAmount}) 83 | } 84 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.OriginCurrency, Amount: tx.OriginAmount}) 85 | uh.TXsByCategory["Exchanges"] = append(uh.TXsByCategory["Exchanges"], t) 86 | } 87 | if tx.Type == "in" { 88 | t := wallet.TX{Timestamp: tx.Date, ID: tx.ID, Note: SOURCE + " " + tx.Type + " " + tx.Status} 89 | t.Items = make(map[string]wallet.Currencies) 90 | t.Items["To"] = append(t.Items["To"], wallet.Currency{Code: tx.DestinationCurrency, Amount: tx.DestinationAmount}) 91 | if !tx.FeeAmount.IsZero() { 92 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.FeeCurrency, Amount: tx.FeeAmount}) 93 | } 94 | if is, desc := cat.IsTxInterest(t.ID); is { 95 | t.Note += " interest " + desc 96 | uh.TXsByCategory["Interests"] = append(uh.TXsByCategory["Interests"], t) 97 | } else { 98 | uh.TXsByCategory["Deposits"] = append(uh.TXsByCategory["Deposits"], t) 99 | } 100 | } else if tx.Type == "out" { 101 | t := wallet.TX{Timestamp: tx.Date, ID: tx.ID, Note: SOURCE + " " + tx.Type + " " + tx.Status} 102 | t.Items = make(map[string]wallet.Currencies) 103 | t.Items["From"] = append(t.Items["From"], wallet.Currency{Code: tx.DestinationCurrency, Amount: tx.DestinationAmount}) 104 | if !tx.FeeAmount.IsZero() { 105 | t.Items["Fee"] = append(t.Items["Fee"], wallet.Currency{Code: tx.FeeCurrency, Amount: tx.FeeAmount}) 106 | } 107 | if is, desc := cat.IsTxGift(t.ID); is { 108 | t.Note += " gift " + desc 109 | uh.TXsByCategory["Gifts"] = append(uh.TXsByCategory["Gifts"], t) 110 | } else { 111 | uh.TXsByCategory["Withdrawals"] = append(uh.TXsByCategory["Withdrawals"], t) 112 | } 113 | } else { 114 | alreadyAsked = wallet.AskForHelp(SOURCE+" "+tx.Type, tx, alreadyAsked) 115 | } 116 | } 117 | } 118 | } 119 | uh.Sources["Uphold"] = source.Source{ 120 | Crypto: true, 121 | AccountNumber: account, 122 | OpeningDate: firstTimeUsed, 123 | ClosingDate: lastTimeUsed, 124 | LegalName: "Uphold Europe Limited", 125 | Address: "Suite A, 6 Honduras Street, London, England, EC1Y 0TH\nRoyaume-Uni", 126 | URL: "https://uphold.com", 127 | } 128 | return 129 | } 130 | --------------------------------------------------------------------------------