As the Chainlink Virtual Hackathon’s Grand Prize winners, smart contract developers Harry Papacharissiou and Matt Durkin used a Chainlink External Adapter to connect a Tesla Vehicle API to a Chainlink oracle for a peer-to-peer vehicle rental app. Their Tesla smart contract is a great example of how Chainlink can be used to connect off-chain APIs to smart contracts and enable completely new business models. In this post, Harry and Matt walk through how they created the implementation.


By Harry Papacharissiou and Matt Durkin

Chainlink’s External Adapter feature makes it easy to connect smart contracts to any API, enabling a variety of use cases for smart contracts to trigger off-chain events and bring tamper-proof digital agreements to external systems.

Tesla, Inc. produces a range of innovative electric vehicles equipped with technologically advanced functionality and features. One of these features is a rich API that can provide an authenticated client with a wealth of vehicle data, as well as access to perform various state-changing functions on vehicles remotely.

Consuming this API through an External Adapter and Chainlink node allows our Tesla smart contract to fully integrate with a Tesla vehicle, which opens up several unique use cases.

In this technical article, we’ll go through:

  • How to use the Tesla External Adapter to interact with the Tesla API
  • How to write a smart contract to use the Tesla External Adapter via a Chainlink node to get vehicle data and modify the state of the vehicle

The Tesla API

The official Tesla mobile app gives Tesla vehicle owners access to data such as vehicle location, odometer reading, the vehicle's battery charge state, and more, as shown in the vehicle data API. The mobile app also allows users to perform various remote commands, such as locking and unlocking the vehicle, remotely starting it, opening and closing the charge port, setting speed limits, plus many more outlined in the Tesla remote commands list.

This mobile app uses a REST API to connect to Tesla’s servers, which, in turn, communicate with each vehicle. At the time of writing, Tesla has not published any official documentation for their Vehicle Owners API, but community developers have produced unofficial documentation by way of reverse engineering it. The community has now implemented the APIs in several useful third-party applications, such as this self-hosted data logger.

The Tesla API uses the OAuth standard for authentication, whereby the API grants access tokens following a successful request to the authentication endpoint. Successive requests to the API which require authentication may include the authentication token in a request header, provided it has not expired or been revoked. The Tesla API-generated access token has a relatively long 45 day expiry time and generates a longer-lived refresh token with each authentication request. We can also request a new access token if it is close to expiry or expired.

Before you can communicate with a Tesla vehicle, you must first successfully obtain one of these authentication tokens with an HTTP POST request to the authentication API endpoint. Use the parameters in the request body below to accomplish this. Set the email and password to the Tesla vehicle account owner’s login on the Tesla website.

{
   "grant_type": "password",
   "client_id": "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384",
   "client_secret": "c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3",
   "email": "teslaowner@gmail.com",
   "password": "password"
}

You will then receive a response containing the access token:

{
    "access_token": "bc031af9351deb7a33e92f689be9eaad4b840e98b49f050a5e951347f140493d",
    "token_type": "bearer",
    "expires_in": 3888000,
    "refresh_token": "77bfff0afe006b7093d7ee23e85d3c667d36c23181e1e938a049237c35aba19c",
    "created_at": 1598934492
}

Once you have a valid authentication token, you then need to find out your vehicle ID by passing this authentication token in the header to the required API endpoint:

curl -X POST https://owner-api.teslamotors.com/api/1/vehicles -H "content-type:application/json" -H "Authorization: Bearer bc031af9351deb7a33e92f689be9eaad4b840e98b49f050a5e951347f140493d"

The vehicle ID required will be returned in the responses ‘id_s’ element. This is the vehicle ID that the Tesla servers will successfully validate against. The other ‘id’ and ‘vehicle_id’ fields are used for other purposes and not applicable for web service requests.

{
    "response": [
        {
            "id": 42555797050350370,
            "vehicle_id": 1832501921,
            "vin": "5YJ3F7EB1KF447777",
            "display_name": "BASED",
            "option_codes": "AD15,MDL3,PBSB,RENA,BT37,ID3W,RF3G,S3PB,DRLH,DV2W,W39B,APF0,COUS,BC3B,CH07,PC30,FC3P,FG31,GLFR,HL31,HM31,IL31,LTPB,MR31,FM3B,RS3H,SA3P,STCP,SC04,SU3C,T3CA,TW00,TM00,UT3P,WR00,AU3P,APH3,AF00,ZCST,MI00,CDM0",
            "color": null,
            "access_type": "OWNER",
            "tokens": [
                "7e6c201b322e9a43",
                "3487a30d7e9ff6ec"
            ],
            "state": "asleep",
            "in_service": false,
            "id_s": "42555797050350366",
            "calendar_enabled": true,
            "api_version": 10,
            "backseat_token": null,
            "backseat_token_updated_at": null,
            "vehicle_config": null
        }
    ],
    "count": 1
}

In this example above, both the authentication token bc031af9351deb7a33e92f689be9eaad4b840e98b49f050a5e951347f140493d, and the vehicle ID 42555797050350366 will  be used in subsequent API calls to the vehicle.

The Tesla External Adapter

As part of the Chainlink Virtual Hackathon 2020 winning submission Link My Ride, we created an External Adapter to connect Chainlink nodes to specific endpoints of the Tesla API to facilitate a peer-to-peer vehicle rental agreement between a vehicle owner and a renter.

This External Adapter has now been listed on the Chainlink Market and is available for other developers to use, modify, or extend.

Once you’ve downloaded the code from Github for the External Adapter and followed the instructions to get it running, you can add your External Adapter to your Chainlink Node and then create a Job Specification that uses it. If you need help setting up a Chainlink Node, you can check out this guide.

This example Job Specification looks for incoming requests from a specific oracle contract address, passes the request to the External Adapter, then returns the result to the smart contract.

{
  "initiators": [
    {
      "type": "runlog",
      "params": {
        "address": "0x05c8fadf1798437c143683e665800d58a42b6e19"
      }
    }
  ],
  "tasks": [
    {
      "type": "tesla-external-adapter",
      "confirmations": null,
      "params": {
      }
    },
    {
      "type": "ethbytes32",
      "confirmations": null,
      "params": {
      }
    },
    {
      "type": "ethtx",
      "confirmations": null,
      "params": {
      }
    }
  ],
  "startAt": null,
  "endAt": null
}

If you don’t have access to a Tesla vehicle but still want to play around with the External Adapter, you can use the one deployed as a serverless function at the following location. This one currently points to a Mock Tesla Server that imitates the endpoints and responses from the real Tesla servers:

https://australia-southeast1-link-my-ride.cloudfunctions.net/Tesla-External-Adapter

Storing Vehicle Authentication Tokens

As stated above, authentication tokens authenticate requests to the vehicle. Exposing these tokens on-chain is a security risk because they control access to the vehicle and can be used to pinpoint its exact location. Thus, we need a solution to ensure the authentication token can be kept and used but never exposed on-chain where other eyes can see it.

If you only need to integrate one vehicle to your smart contract, then the easiest solution is to store the authentication token as an environment variable on the host where the adapter runs. You can find a demonstration in this Building an External Adapter Guide. However, what if multiple authentication tokens need to be stored and accessed for multiple vehicles?

In scenarios like these, the External Adapter needs to store and retrieve multiple key/value pairs. The key is the vehicle ID or some unique identifier for each vehicle, and the value is the authentication token. There are many solutions to storing and using multiple key/value pairs in External Adapters. One of the most innovative solutions is to use cloud Serverless NoSQL data storage for the authentication tokens. If you also run the External Adapter as a serverless function on your preferred cloud provider, your External Adapter becomes a truly serverless, highly available and scalable hybrid blockchain/cloud function.

This External Adapter uses Google Cloud’s Firestore NoSQL Document Database to support storing & retrieving multiple vehicle authentication tokens. To set up a Firestore Database, follow this guide. If you don’t have a Google Cloud account, you can sign up for a free-tier account.

Once your Firestore database is setup, you can then set the required environment variables for the external adapter and then start it by following the instructions in the external adapter documentation.

Once the External Adapter and Firestore database are running, the last step before moving onto the smart contract is to authenticate the vehicle. The authentication process is when the Adapter takes specific information about a vehicle, connects to the Tesla servers with this information, then stores the given Vehicle ID and authentication token as a new key/value pair in the Firestore database, and finally returns a success message. From this point on, any requests for the given Vehicle ID do not need the authentication token. The External Adapter will obtain it from the Firestore database when needed.

To perform this step, make an HTTP POST request to the External Adapter URL in the format below. In this example, the job ID is 534ea675a9524e8e834585b00368b178; we will use the vehicle ID and apiToken fields in the request to the Tesla servers. The authenticate action tells the Adapter to authenticate the given vehicle details, and then it stores the vehicle details in the Firestore database if the credentials are valid.

curl -X POST -H "content-type:application/json" "http://localhost:8080/" --data '{ "id": 534ea675a9524e8e834585b00368b178, "data": { "apiToken": "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384", "vehicleId": "42555797050350777", "action": "authenticate" } }'

We can make this request manually with a REST client, directly via a web-app, or if Adapter security only allows connection from specific Chainlink nodes, then you can initiate it via a web initiated job specification as demonstrated below. In this example, the authentication request comes into the Chainlink node, which forwards it onto the External Adapter (along with all the parameters), which then sends the results to an on-chain function in a smart contract:

{
  "initiators": [
    {
      "type": "web",
      "params": {
      }
    }
  ],
  "tasks": [
    {
      "type": "tesla-external-adapter",
      "confirmations": null,
      "params": {
      }
    },
    {
      "type": "ethtx",
      "confirmations": 0,
      "params": {
        "address": "0x1EF45689050F47BAc80Df38A10a199bb26af0f6b",
        "functionSelector": "approveVehicle(address)"
      }
    }
  ],
  "startAt": null,
  "endAt": null
}

Once the External Adapter is running and has authenticated a vehicle, we need to take appropriate steps to secure access to the Adapter. We can build extra security in and around the Adapter to ensure that only authorized Chainlink nodes or processes have access to calling the External Adapter. You can do this within the Adapter itself via Whitelisting. If the Adapter is running as a serverless function in a cloud environment, you can configure security and role access there.

Creating the Smart Contract

Now we’re running an External Adapter, we’ve added it to a Chainlink node job specification, and we also have a vehicle’s validated authentication token stored securely. Next, we can create a smart contract to perform an action on the vehicle and, at the same time, obtain data about the vehicle's location, odometer, and charge level.

The first step is to create a new API Consumer Contract, setting all the required parameters for your preferred Ethereum network.

You should create two functions in your contract: ‘unlockVehicle’ and ‘unlockVehicleCallback’, as per the examples below. Call the unlockVehicle function to interact with the Tesla vehicle.

The unlockVehicle function takes in the vehicle ID and job ID as parameters. This should be the ID of the first Job Specification mentioned earlier in the Tesla External Adapter section. We set the LINK payment amount to 0.1 LINK. Here is our Solidity example making an HTTP POST request through our Chainlink oracle.

function unlockVehicle(string _vehicleId, bytes32 _jobId) external {
         Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.unlockVehicleCallback.selector);
         req.add("apiToken", "");
         req.add("vehicleId", _vehicleId);
         req.add("action", "unlock");
         sendChainlinkRequestTo(chainlinkOracleAddress(), req, 0.1 * 1 ether);
         
     }

If the call to the Tesla servers is successful, the vehicle will unlock its doors and return a success message and a JSON object containing the vehicle odometer, charge level percent, and location coordinates:

{
    "jobRunID": 134ea675a9524e8e231585b00368b178,
    "data": "{50000,55,-35008518,138575206}",
    "result": null,
    "statusCode": 200
}

This response data will be returned to the unlockVehicleCallback function, where we can manually extract each value for on-chain storage:

function unlockVehicleCallback(bytes32 _requestId, bytes32 _vehicleData) public recordChainlinkFulfillment(_requestId) {
        //first split the results into individual strings based on the delimiter
        var s = bytes32ToString(_vehicleData).toSlice();
        var delim = ",".toSlice();
       
        //store each string in an array
        string[] memory splitResults = new string[](s.count(delim)+ 1);                  
        for (uint i = 0; i < splitResults.length; i++) {                              
           splitResults[i] = s.split(delim).toString();                              
        }                                                        
       
        //Now for each one, convert to uint
        odometer = stringToUint(splitResults[0]);
        chargeState = stringToUint(splitResults[1]);
        tmpLongitude = stringToUint(splitResults[2]);
        tmpLatitude = stringToUint(splitResults[3]);
        
        //Now store location coordinates in signed variables. Will always be positive, but will check in the next step if need to make negative
        vehicleLongitude =  int(tmpLongitude);
        vehicleLatitude =  int(tmpLatitude);

        //Finally, check first bye in the string for the location variables. If it was a '-', then multiply location coordinate by -1
        //first get the first byte of each location coordinate string
        longitudeBytes = bytes(splitResults[2]);
        latitudeBytes = bytes(splitResults[3]);
        
        
        //First check longitude
        if (uint(longitudeBytes[0]) == 0x2d) {
            //first byte was a '-', multiply result by -1
            vehicleLongitude = vehicleLongitude * -1;
        }
        
        //Now check latitude
        if (uint(latitudeBytes[0]) == 0x2d) {
            //first byte was a '-', multiply result by -1
            vehicleLatitude = vehicleLatitude * -1;
        }
        
        //Emit an event with the vehicle data
        emit vehicleDetails(odometer,chargeState,vehicleLongitude,vehicleLatitude);
}

A complete working version of the contract above is available on GitHub, or you can use the easy-to-deploy Remix link. This implementation is currently connected to a mock Tesla server for development and testing purposes. To modify it to a production environment and connect to an actual Tesla vehicle, we need to update the job specification to one running on a Tesla External Adapter pointing to the live Tesla production servers.

Summary

Using the Chainlink Network and its versatile External Adapter functionality, we’ve demonstrated how to integrate a smart contract with a Tesla vehicle. The integration gives the smart contract full access to Tesla’s rich set of vehicle data and the ability to execute all of the various actions that can be performed on the vehicle remotely.

This demonstration opens up many exciting potential use cases for smart contracts and vehicle integrations, such as peer-to-peer vehicle renting, as demonstrated by our Chainlink Virtual Hackathon 2020 winning submission Link My Ride. Other use cases could include short-term per-use vehicle registration or data-driven vehicle insurance that adapts to the driver’s behavior in real time. As we rapidly move towards a world with autonomous vehicles, it becomes easier to imagine booking and taking a trip in a driverless vehicle, with a highly secure, deterministic smart contract managing the agreement and transaction between the vehicle owner and the customer.

Learn More

If you’re a developer and want to connect your smart contract to existing data and infrastructure outside the underlying blockchain, reach out here or visit the developer documentation.

Website | Twitter | Discord | Reddit | YouTube | Telegram | Events | GitHub | Price Feeds | DeFi