fine_ants 🐜

Got finance problems? Have some fine_ants to help.

Usage

$ gem install fine_ants

And then:

accounts = FineAnts.download(:vanguard, {
  :user => "janelastname",
  :password => ENV['VANGUARD_PASSWORD']
})

puts accounts
#=> [{
#      :adapter => :vanguard,
#      :user => "janelastname",
#      :id => "234567890",
#      :amount => BigDecimal.new("1234.56")
# }]

What?

I wrote this, because nearly every service and app that offers multi-institution financial dashboarding stores your passwords in a way that can be decrypted by the service (by design, since they need your credentials to scrape the banks' sites). This means if these rando services get hacked, the passwords to all of your financial accounts can be compromised at once.

The FDIC and SIPC are pretty great protections from the dissolution of banks, but it doesn't protect you from their web sites being compromised, much less the web sites of services that scrape them just to give you a pretty dashboard.

Handing all your financial credentials to anyone seems foolish, so I started the fine_ants gem to build adapters for the various financial institutions I use. It uses capybara to automate a browser and scrape your account totals from your bank's webapp. It even supports 2FA. Since it's designed to be run locally, it simply uses gets to read SMS, e-mail, and TOTP tokens from stdin when a login process requires a 2FA challenge.

Adapters

Right now, FineAnts ships with adapters for:

Name Adapter Name
Vanguard Personal Investment :vanguard
PNC Personal Banking :pnc
Betterment :betterment
E*Trade :etrade
Chase :chase
American Express :amex
Simple (BBVA) :simple
Simple (Bancorp) :simple_bancorp
Target REDcard :target
Purdue Federal Credit Union :purduefed
Ohio State Teacher Retirement System (STRS) :strs
Zillow (Zestimate, user is the "zpid") :zillow

You can also implement your own adapter and pass it to FineAnts.download. The expected public contract of an adapter is:

require "bigdecimal"

class MyAdapter
  def initialize(credentials)
    # `credentials` should be a hash with (at least) :user and :password entries.
  end

  def 
    # Login to the system
    # return true if login is successful
    # return false if a 2FA response is needed (see #two_factor_response)
    # if credentials fail, then raise FineAnts::LoginFailedError.new
  end

  def two_factor_response(answer)
    # This method is optional and useful if the service supports 2FA
    # If defined, the user will be prompted (via `gets`) to type in a 2FA
    # challenge response, which will be passed here. With the answer in hand,
    # automate entering the 2FA token and submitting.
  end

  def download
    # Download all the user's accounts and total values.
    # FineAnts expects this method to return data shaped like:
    [
      {
        :adapter => :your_adapter_name,
        :user => "theloginbeingused",
        :id => "id-of-the-account",
        :amount => BigDecimal.new("1234.56")
      }
    ]
  end
end

You can pass your own adapter class:

accounts = FineAnts.download(MyAdapter, {
  :user => "randojones",
  :password => ENV['MY_PASSWORD']
})