Skip to content

Extending the Haveno API

This guide is for contributors who add or change calls in Haveno's API. It covers how the API is built, how to run its end-to-end tests, and how to add a new call.

Just want to use the API?

To build a program on top of Haveno, see Haveno API. The calls are documented in the API reference.

How it works

The API spans two repositories: haveno, which serves it from the daemon, and haveno-ts, the TypeScript client and its end-to-end tests. A call passes through these layers:

HavenoClient (haveno-ts)  →  Envoy  →  Grpc*Service  →  CoreApi  →  Core*Service
Layer Location Role
Protobuf definitions proto/.../grpc.proto Services, requests, and replies
gRPC services daemon/.../grpc/ Convert between protobuf and core types, handle errors, set rate limits
Core API core/.../api/CoreApi.java Single entry point to the core
Core services core/.../api/ Implement each call against wallets, offers, trades, and the network
Client src/HavenoClient.ts Typed method for each call
Tests src/HavenoClient.test.ts End-to-end tests against live daemons

Envoy translates the browser-compatible gRPC-Web used by haveno-ts into gRPC. The daemon checks the API password and rate limits before a call reaches its service.

For example, getOffers is served by GrpcOffersService.getOffers, which calls CoreApi.getOffers, then CoreOffersService.getOffers.

Set up a test network

The tests run against a local Monero testnet and Haveno network on your machine.

Requirements

Start the network

  1. Clone both repositories into the same parent directory, since haveno-ts builds against ../haveno, and build Haveno:

    git clone https://github.com/haveno-dex/haveno.git
    git clone https://github.com/haveno-dex/haveno-ts.git
    cd haveno
    make skip-tests
    
  2. From haveno, start each of the following in its own terminal:

    make monerod1-local       # Monero node
    make monerod2-local       # second Monero node
    make seednode-local       # Haveno seed node
    make funding-wallet-local # wallet that funds the tests
    
  3. From haveno-ts, start Envoy in another terminal:

    envoy -c config/envoy.local.yaml
    
  4. From haveno-ts, install dependencies. This also generates the client's protobuf code from ../haveno/proto:

    npm install
    

The tests start the arbitrator, user1, and user2 daemons if they are not already running, and handle mining, funding, and arbitrator registration. To follow a daemon's output, or to debug it, start it yourself first with make arbitrator-daemon-local, make user1-daemon-local, or make user2-daemon-local. The tests connect to running daemons instead of starting new ones.

Windows

Run the commands from Git Bash, or MSYS2 MINGW64 for the make targets. Envoy runs in WSL2, or download Envoy v1.28.0 and add it to your PATH. Install the JavaScript plugin with npm install --global protoc-gen-js. See the haveno-ts test notes for details.

Docker

To work on haveno-ts alone, start the whole network with docker compose up from haveno-ts/docker instead. It builds Haveno's master branch, so it does not include your local changes to Haveno.

Run the tests

From haveno-ts, run a test by name:

npm run test -- --baseCurrencyNetwork=XMR_LOCAL -t "Can get the version"

Omit -t to run the full suite. To run the subset that runs in continuous integration, select the tests tagged CI:

npm run test -- --baseCurrencyNetwork=XMR_LOCAL -t "(?:^|\W)CI(?:$|\W)"

The daemons log errors as the tests exercise invalid inputs, so errors in their output are expected.

Tip

Daemons started by the tests run the current build of Haveno. A daemon you started yourself keeps running the old code, so restart it after rebuilding.

Add an API call

  1. Define the call. Add the rpc and its request and reply messages to grpc.proto. Give new fields new numbers, and never renumber or reuse an existing field, so existing clients keep working.

  2. Implement it in the core. Add the logic to the matching Core*Service in core/.../api/, and expose it through CoreApi.

  3. Serve it from the daemon. Override the generated method in the matching Grpc*Service, following its existing methods:

    @Override
    public void getOffers(GetOffersRequest req, StreamObserver<GetOffersReply> responseObserver) {
        try {
            List<OfferInfo> result = coreApi.getOffers(req.getDirection(), req.getCurrencyCode())
                    .stream().map(OfferInfo::toOfferInfo)
                    .collect(Collectors.toList());
            var reply = GetOffersReply.newBuilder()
                    .addAllOffers(result.stream().map(OfferInfo::toProtoMessage).collect(Collectors.toList()))
                    .build();
            responseObserver.onNext(reply);
            responseObserver.onCompleted();
        } catch (Throwable cause) {
            exceptionHandler.handleException(log, cause, responseObserver);
        }
    }
    

    Then add a rate limit for the call in the service's rateMeteringInterceptor(). A call without one is not rate limited.

  4. Rebuild Haveno from haveno:

    make clean && make skip-tests
    
  5. Regenerate the client's protobuf code from haveno-ts:

    npm install
    
  6. Add a client method to HavenoClient.ts. Its doc comment becomes the API reference, so describe each parameter and the return value:

    /**
     * Get available offers to buy or sell XMR.
     *
     * @param {string} assetCode - traded asset code
     * @param {OfferDirection|undefined} direction - "buy" or "sell" (default all)
     * @return {OfferInfo[]} the available offers
     */
    async getOffers(assetCode: string, direction?: OfferDirection): Promise<OfferInfo[]> {
      try {
        ...
      } catch (e: any) {
        throw new HavenoError(e.message, e.code);
      }
    }
    
  7. Add tests to HavenoClient.test.ts, following the existing tests. Include CI in a test's name tags, e.g. "Can get offers (Test, CI)", to run it in continuous integration.

  8. Run the tests, first your new test by name and then the full suite, as in Run the tests.

  9. Optionally add it to the CLI. Haveno CLI commands are defined in cli/, in Method.java, GrpcClient.java, and CliMain.java.

  10. Open pull requests to haveno and haveno-ts. The haveno-ts tests build Haveno's master branch, so they pass once the haveno pull request is merged.

Further reading

Questions?

For help, join the Matrix room.