Got finance problems? Have some fine_ants to help.
$ 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")
# }]
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.
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
# 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']
})