bitTrack Icon


Release Date: 2014

Language Used:


Software Used:





bitTrack is an app all about Bitcoin, or more specifically, monitoring Bitcoin addresses. Since there is no official Bitcoin app, and since most other monitoring apps were either visually unappealing or didn't provide enough features, I decided to code my own.

The app pulls Bitcoin data from, one of the most popular websites for getting Bitcoin blockchian data. The website has a free JSON API, among others, for accessing history for a given address. bitTrack also pulls current pricing data from the website using the same API. This data is then stored in the app's Core Data storage, Apple's built-in data management system.

This is the first app I encounted difficulties with getting on the iOS App Store, due to Apple's stance on Bitcoin. Although it was eventually published, it took a few extra weeks and phone calls with Apple, explaining that the app can't actually perform transactions, just view histories on Bitcoin addresses.

Code Samples

Blockchain Download

This code pulls data from using their JSON API.
- (void)refreshAddressContainerWebDataForAllAddresses {
    // if we don't have any addresses, no work to do, bail out
    if (addressContainerArray.count <= 0) {
        isDownloading = NO;
        [[NSNotificationCenter defaultCenter] postNotificationName:@"refreshAddressContainerWebDataForAllAddressesDidFinishNotification" object:addressContainerArray];
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    isDownloading = YES;
    __block NSInteger outstandingRequests = [addressContainerArray count];
    for (int i = 0; i < addressContainerArray.count; i++) {
        AddressContainer *currentAddressContainer = [addressContainerArray objectAtIndex:i];
        NSString *blockChainString = [NSString stringWithFormat:@"", currentAddressContainer.address];
        NSURL *blockChainURL = [NSURL URLWithString:blockChainString];
        NSURLRequest *request = [NSURLRequest requestWithURL:blockChainURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15.0];
        // create a download queue to be used in this loop
        dispatch_queue_t downloadQueue = dispatch_queue_create("downloader", NULL);
        // run the download process in our newly created background queue
        dispatch_async(downloadQueue, ^{
            NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
            // if the data succedded, 
            if (data) {
                NSDictionary *addressDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
                [currentAddressContainer setData:addressDict];
            // when there are no more requests, save the database, post the delegate callback
            if (outstandingRequests <= 0) {
                [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
                [self saveDatabase];
                [self refreshAddressContainerArray];
                isDownloading = NO;
                // post the finished notification
                [[NSNotificationCenter defaultCenter] postNotificationName:@"refreshAddressContainerWebDataForAllAddressesDidFinishNotification" object:addressContainerArray];

Bitcoin URI Parsing

Bitcoin uses a special Uniform Resource Identifier (URI) to store things like the Bitcoin address, its name (also called its label), a transaction amount and a message. bitTrack only recognizes the first two, since the app doesn't manage Bitcoin transactions. This code parses a Bitcoin URI for the address and its name.
- (void)parseBitcoinURI:(NSString *)URI {
    self.URI = URI;
    NSRange bitcoinRange = [self.URI rangeOfString:@"bitcoin:"];
    if (bitcoinRange.location == NSNotFound) {
        self.address = self.URI;
    } else {
        NSRange questionMarkRange = [self.URI rangeOfString:@"?"];
        NSRange ampersandRange = [self.URI rangeOfString:@"&"];
        // if the ? or & exists, extract the address from just after bitcoin: to just before the & or ?
        // if neither exists, extract the address from just after bitcoin: to the end of the string
        if (questionMarkRange.location != NSNotFound && ampersandRange.location != NSNotFound) {
            NSRange firstCharacterRange;
            if (questionMarkRange.location < ampersandRange.location) {
                firstCharacterRange = questionMarkRange;
            } else {
                firstCharacterRange = ampersandRange;
            self.address = [self.URI substringWithRange:NSMakeRange(bitcoinRange.length, firstCharacterRange.location - bitcoinRange.length)];
        } else if (questionMarkRange.location != NSNotFound) {
            self.address = [self.URI substringWithRange:NSMakeRange(bitcoinRange.length, questionMarkRange.location - bitcoinRange.length)];
        } else if (ampersandRange.location != NSNotFound) {
            self.address = [self.URI substringWithRange:NSMakeRange(bitcoinRange.length, ampersandRange.location - bitcoinRange.length)];
        } else {
            self.address = [self.URI substringFromIndex:bitcoinRange.length];
        NSRange labelRange = [self.URI rangeOfString:@"label="];
        // if label= exists...
        if (labelRange.location != NSNotFound) {
            // extract everything after label= to the end of the string
            NSString *afterLabelEqualsToEnd = [self.URI substringFromIndex:labelRange.location + labelRange.length];
            // now search this string for ? or & again. if either exists, extract the label from just after label= to just before & or ?
            // if neither exists, extract the label from just after label= to the end of the string
            questionMarkRange = [afterLabelEqualsToEnd rangeOfString:@"?"];
            ampersandRange = [afterLabelEqualsToEnd rangeOfString:@"&"];
            // also refresh labelRange, since it was using URI instead of labelEqualsToEnd before
            //labelRange = [labelEqualsToEnd rangeOfString:@"label="];
            if (questionMarkRange.location != NSNotFound) {
                //self.label = [labelEqualsToEnd substringWithRange:NSMakeRange(labelRange.length, (labelEqualsToEnd.length - questionMarkRange.location) + 2)];
                self.label = [afterLabelEqualsToEnd substringToIndex:questionMarkRange.location];
            } else if (ampersandRange.location != NSNotFound) {
                //self.label = [labelEqualsToEnd substringWithRange:NSMakeRange(labelRange.length, (labelEqualsToEnd.length - ampersandRange.location) + 2)];
                self.label = [afterLabelEqualsToEnd substringToIndex:ampersandRange.location];
            } else {
                self.label = afterLabelEqualsToEnd;