/anchor-programs

Solana program clients generated from Anchor IDLs

Primary LanguageJavaGNU General Public License v3.0GPL-3.0

Anchor Programs Build Release

Generated programs can be found in the root source package directory. For each project generated code is under the anchor package, and manually written code is directly under the project package.

Code is generated using sava-software/anchor-src-gen, see that project for more context on which features are provided.

Requirements

  • The latest generally available JDK. This project will continue to move to the latest and will not maintain versions released against previous JDK's.

Add Dependency

Create a GitHub user access token with read access to GitHub Packages.

Then add the following to your Gradle build script.

repositories {
  maven {
    url = "https://maven.pkg.github.com/sava-software/sava"
    credentials {
      username = GITHUB_USERNAME
      password = GITHUB_PERSONAL_ACCESS_TOKEN
    }
  }
  maven {
    url = "https://maven.pkg.github.com/sava-software/solana-programs"
  }
  maven {
    url = "https://maven.pkg.github.com/sava-software/anchor-src-gen"
  }
  maven {
    url = "https://maven.pkg.github.com/sava-software/anchor-programs"
  }
}

dependencies {
  implementation "software.sava:sava-core:$VERSION"
  implementation "software.sava:sava-rpc:$VERSION"
  implementation "software.sava:solana-programs:$VERSION"
  implementation "software.sava:anchor-src-gen:$VERSION"
  implementation "software.sava:anchor-programs:$VERSION"
}

Contribution

Unit tests are needed and welcomed. Otherwise, please open an issue or send an email before working on a pull request.

Warning

Young project, under active development, breaking changes and bugs are to be expected.

Examples

Prints the following at the end:

Limit Long 0.1 @ 111 on SOL-PERP [reduceOnly=false] [postOnly=MustPostOnly]

try (final var httpClient = HttpClient.newHttpClient()) {
  final var rpcClient = SolanaRpcClient.createClient(SolanaNetwork.MAIN_NET.getEndpoint(), httpClient);

  // Fetch a Drift placeOrders Transaction
  final var txFuture = rpcClient.getTransaction(
          "36Wnn99Y49mJ5GKiNiT3ja2q8gzSvMNrN5A3Bcn2YfCyrwY7kgQGVAu9VNzXqmWSbgzX76oUGxYNuPGM7tpPoJJS"
  );
  final var tx = txFuture.join();
  final byte[] txData = tx.data();

  final var skeleton = TransactionSkeleton.deserializeSkeleton(txData);

  final Instruction[] instructions;
  if (skeleton.isLegacy()) {
    instructions = skeleton.parseInstructions(skeleton.parseAccounts());
  } else {
    // Fetch Lookup tables to allow parsing of versioned transactions.
    final int txVersion = skeleton.version();
    if (txVersion == 0) {
      final var tableAccountInfos = rpcClient.getMultipleAccounts(
          Arrays.asList(skeleton.lookupTableAccounts()),
          AddressLookupTable.FACTORY
      ).join();

      final var lookupTables = tableAccountInfos.stream()
          .map(AccountInfo::data)
          .collect(Collectors.toUnmodifiableMap(AddressLookupTable::address, Function.identity()));

      instructions = skeleton.parseInstructions(skeleton.parseAccounts(lookupTables));
    } else {
      throw new IllegalStateException("Unhandled tx version " + txVersion);
    }
  }

  // instructions[0]; // Compute Budget Limit
  // instructions[1]; // Compute Unit Price
  // instructions[2]; // Drift Place Orders
  final var placeOrdersIxData = Arrays.stream(instructions)
      .filter(DriftProgram.PLACE_ORDERS_DISCRIMINATOR)
      .map(DriftProgram.PlaceOrdersIxData::read)
      .findFirst().orElseThrow();
  
  final OrderParams[] orderParams = placeOrdersIxData.params();
  final OrderParams order = orderParams[0];

  // Fetch token contexts to make use of convenient scaled value conversions.
  final var jupiterClient = JupiterClient.createClient(httpClient);
  final var verifiedTokens = jupiterClient.verifiedTokenMap().join();

  // Create Drift Client to map market indexes from the order to its configuration.
  final var nativeProgramClient = NativeProgramClient.createClient();
  final var nativeProgramAccountClient = nativeProgramClient
      .createAccountClient(AccountMeta.createFeePayer(PublicKey.NONE));
  final var driftClient = DriftProgramClient.createClient(nativeProgramAccountClient);

  final var driftAccounts = driftClient.accounts();
  final MarketConfig marketConfig;
  final TokenContext baseTokenContext;
  if (order.marketType() == MarketType.Perp) {
    final var perpMarketConfig = driftAccounts.perpMarketConfig(order.marketIndex());
    final var spotConfig = driftClient.spotMarket(perpMarketConfig.baseAssetSymbol());
    baseTokenContext = verifiedTokens.get(spotConfig.mint());
    marketConfig = perpMarketConfig;
  } else {
    final var spotConfig = driftAccounts.spotMarketConfig(order.marketIndex());
    baseTokenContext = verifiedTokens.get(spotConfig.mint());
    marketConfig = spotConfig;
  }

  // Assume all Drift markets are priced in USDC
  final var usdcTokenMint = driftClient.spotMarket(DriftAsset.USDC).mint();
  final var usdcTokenContext = verifiedTokens.get(usdcTokenMint);

  // Limit Long 0.1 @ 111 on SOL-PERP [reduceOnly=false] [postOnly=MustPostOnly]
  System.out.format("""
          %s %s %s @ %s on %s [reduceOnly=%b] [postOnly=%s]
          """,
      order.orderType(),
      order.direction(),
      baseTokenContext.toDecimal(order.baseAssetAmount()).toPlainString(),
      usdcTokenContext.toDecimal(order.price()).toPlainString(),
      marketConfig.symbol(),
      order.reduceOnly(),
      order.postOnly()
  );
}