Git Product home page Git Product logo

solita's Introduction

solita Build Lint and Test Solita

Sol ana I DL t o A PI generator.

solita-logo

Table of Contents generated with DocToc

How does it Work?

Solita generates a low level TypeScript SDK for your Solana Rust programs from the IDL extracted by anchor or shank.

Shank + Solita Example (Recommended)

In order to use solita with shank do the following:

  • add the shank library to your Rust project via cargo add shank
  • annotate your Rust program as outlined here
  • add solita to the dev dependencies of your SDK package via yarn add -D @metaplex-foundation/solita
  • add a config similar to the below into .solitarc.js in your SDK package root
const path = require('path');
const programDir = path.join(__dirname, '..', 'program');
const idlDir = path.join(__dirname, 'idl');
const sdkDir = path.join(__dirname, 'src', 'generated');
const binaryInstallDir = path.join(__dirname, '.crates');

module.exports = {
  idlGenerator: 'shank',
  programName: 'mpl_token_vault',
  idlDir,
  sdkDir,
  binaryInstallDir,
  programDir,
};

Now running yarn solita from the same folder will take care of installing the matching shank binary and generating the IDL and SDK.

Run it each time you make a change to your program to generate the TypeScript SDK.

Since we're writing the shank binary to .crates/ you should add that folder to your .gitignore.

Full Example: Token Metadata Solita + Shank Setup

Anchor + Solita Example (Recommended)

In order to use solita with anchor do the following:

  • annotate your Rust program with anchor attributes
  • add solita to the dev dependencies of your SDK package via yarn add -D @metaplex-foundation/solita
  • add a config similar to the below into .solitarc.js in your SDK package root
const path = require('path');
const programDir = path.join(__dirname, '..', 'program');
const idlDir = path.join(__dirname, 'idl');
const sdkDir = path.join(__dirname, 'src', 'generated');
const binaryInstallDir = path.join(__dirname, '.crates');

module.exports = {
  idlGenerator: 'anchor',
  programName: 'auction_house',
  programId: 'hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk',
  idlDir,
  sdkDir,
  binaryInstallDir,
  programDir,
};

Now running yarn solita from the same folder will take care of installing the matching anchor binary and generating the IDL and SDK.

Run it each time you make a change to your program to generate the TypeScript SDK.

Since we're writing the anchor binary to .crates/ you should add that folder to your .gitignore.

Full Example: MPL Candy Machine Solita + Anchor Setup

Type Aliases

In order to have Solita resolve specific types to a Rust builtin type please provide a type alias map as in the below config. Solita then will treat those as if they were the aliased type.

module.exports = {
  idlGenerator: 'anchor',
  [ .. ]
  typeAliases: {
    UnixTimestamp: 'i64'
  }
};

Custom De/Serializers

For some accounts the generated de/serializers don't work. In those cases a custom de/serializer can be specified.

This is as simple as adding a module to your project which exports a either or both of the below functions:

export function deserialize(buf: Buffer, offset = 0): [<Account>, number] {
  [..]
}

export function serialize(instance: <Account>Args, byteSize?: number): [Buffer, number]
  [..]
}

Then provide them as serializers to Solita or via the solita config:

module.exports = {
  idlGenerator: 'shank',
  [ .. ]
  serializers: {
    Metadata: './src/custom/metadata-deserializer.ts',
  },
};

Advanced Shank + Solita Example

If you need more control you can also add a script. However you're on your own to ensure that the globally installed shank binary matches the version of its library you're using.

  • globally install shank via cargo install shank-cli
  • add a script similar to the below to your SDK package and
const path = require('path');
const { Solita } = require('@metaplex-foundation/solita');
const {
  rustbinMatch,
  confirmAutoMessageConsole,
} = require('@metaplex-foundation/rustbin')
const { spawn } = require('child_process');

const programDir = path.join(__dirname, '..', '..', 'program');
const cargoToml = path.join(programDir, 'Cargo.toml')
const generatedIdlDir = path.join(__dirname, '..', 'idl');
const generatedSDKDir = path.join(__dirname, '..', 'src', 'generated');
const rootDir = path.join(__dirname, '..', '.crates')

const PROGRAM_NAME = 'mpl_token_metadata';
const rustbinConfig = {
  rootDir,
  binaryName: 'shank',
  binaryCrateName: 'shank-cli',
  libName: 'shank',
  dryRun: false,
  cargoToml,
}

async function main() {
  const { fullPathToBinary: shankExecutable } = await rustbinMatch(
    rustbinConfig,
    confirmAutoMessageConsole
  )
  const shank = spawn(shankExecutable, ['idl', '--out-dir', generatedIdlDir, '--crate-root', programDir])
    .on('error', (err) => {
      console.error(err);
      if (err.code === 'ENOENT') {
        console.error(
          'Ensure that `shank` is installed and in your path, see:\n  https://github.com/metaplex-foundation/shank\n',
        );
      }
      process.exit(1);
    })
    .on('exit', () => {
      generateTypeScriptSDK();
    });

  shank.stdout.on('data', (buf) => console.log(buf.toString('utf8')));
  shank.stderr.on('data', (buf) => console.error(buf.toString('utf8')));
}

async function generateTypeScriptSDK() {
  console.error('Generating TypeScript SDK to %s', generatedSDKDir);
  const generatedIdlPath = path.join(generatedIdlDir, `${PROGRAM_NAME}.json`);

  const idl = require(generatedIdlPath);
  const gen = new Solita(idl, { formatCode: true });
  await gen.renderAndWriteTo(generatedSDKDir);

  console.error('Success!');

  process.exit(0);
}

main().catch((err) => {
  console.error(err)
  process.exit(1)
})

Advanced Anchor + Solita Example

If you need more control you can also add a script. However you're on your own to ensure that the globally installed anchor binary matches the version of its library you're using.

  • globally install anchor
  • add a script similar to the below to your SDK package
const path = require('path');
const {
  rustbinMatch,
  confirmAutoMessageConsole,
} = require('@metaplex-foundation/rustbin')
const { spawn } = require('child_process');
const { Solita } = require('@metaplex-foundation/solita');
const { writeFile } = require('fs/promises');

const PROGRAM_NAME = 'candy_machine';
const PROGRAM_ID = 'cndy3Z4yapfJBmL3ShUp5exZKqR3z33thTzeNMm2gRZ';

const programDir = path.join(__dirname, '..', '..', 'program');
const cargoToml = path.join(programDir, 'Cargo.toml')
const generatedIdlDir = path.join(__dirname, '..', 'idl');
const generatedSDKDir = path.join(__dirname, '..', 'src', 'generated');
const rootDir = path.join(__dirname, '..', '.crates')

async function main() {
  const { fullPathToBinary: anchorExecutable } = await rustbinMatch(
    rustbinConfig,
    confirmAutoMessageConsole
  )
  const anchor = spawn(anchorExecutable, ['build', '--idl', generatedIdlDir], { cwd: programDir })
    .on('error', (err) => {
      console.error(err);
      // @ts-ignore this err does have a code
      if (err.code === 'ENOENT') {
        console.error(
          'Ensure that `anchor` is installed and in your path, see:\n  https://project-serum.github.io/anchor/getting-started/installation.html#install-anchor\n',
        );
      }
      process.exit(1);
    })
    .on('exit', () => {
      console.log('IDL written to: %s', path.join(generatedIdlDir, `${PROGRAM_NAME}.json`));
      generateTypeScriptSDK();
    });

  anchor.stdout.on('data', (buf) => console.log(buf.toString('utf8')));
  anchor.stderr.on('data', (buf) => console.error(buf.toString('utf8')));
}

async function generateTypeScriptSDK() {
  console.error('Generating TypeScript SDK to %s', generatedSDKDir);
  const generatedIdlPath = path.join(generatedIdlDir, `${PROGRAM_NAME}.json`);

  const idl = require(generatedIdlPath);
  if (idl.metadata?.address == null) {
    idl.metadata = { ...idl.metadata, address: PROGRAM_ID };
    await writeFile(generatedIdlPath, JSON.stringify(idl, null, 2));
  }
  const gen = new Solita(idl, { formatCode: true });
  await gen.renderAndWriteTo(generatedSDKDir);

  console.error('Success!');

  process.exit(0);
}

main().catch((err) => {
  console.error(err)
  process.exit(1)
})

Solita in the Wild

Find more solita, shank and anchor examples inside the metaplex-program-library.

LICENSE

Apache-2.0

solita's People

Contributors

thlorenz avatar ricardocr987 avatar luigiretzkowski avatar mhhukiewitz avatar stegabob avatar exromany avatar

solita's Issues

Generate graphql.schema based on IDL

It should be possible to generate a graphql.schema file (like seen here: https://www.graphql-code-generator.com/) from an Anchor IDL. This will allow Solita users to generate GraphQL APIs for their Anchor programs instantly, for use-cases such as querying program accounts.

While some data is static and given by the IDL itself, other data is dynamic and relies on resolvers. Already existing code generators utilizing GraphQL Schemas usually generate types only, but can also generate type signatures for Resolver functions to let the coder fill in the gaps.

Refactor for module standard

Refactor the project, such that the schema directory is integrated into the solita library and able to be exported for use in other projects.

Research customizable and standardized code patterns in Indexer Framework

As the IDL provides a lot of necessary information for us to standardize access to the given on-chain program, there still remains some research to be done on what code can be further generated with the IDL and which code has to be generated given a user's input.

The basic structure of an indexer looks like this:

  • dal: Contains configurations for the Data Access Layer. Some of it is simple boilerplate for each project, but some of it requires consideration by the user
    • Which data objects to be stored
    • Which indexes to choose, on which to access the stored objects in
  • domain: Contains domain-specific code
    • A subclass of SolanaPools<P>, which governs the discovery and aggregation of P = SolanaPool<PI, PS> objects
    • A subclass of SolanaPool<PI, PS> defined by their PI = PoolInfo = { name, address, programId } and some PS = PoolStats object, governing which statistics are to be stored and saved over the processed events.
    • (optional) A processor
    • These require some extensive effort of customization on the user side.
  • graphql: All code for setting up the GraphQL server
    • A schema, which specifies the GraphQL API, which can be subdivided into
      • GraphQL types and
      • Resolvers, which depend on the implementation of the server and its DB itself
  • indexer: The binding piece for all the parts of the indexer.
    • Contains at least one subclass of TransactionFetcher, which will query transactions, which contain certain addresses.
  • layouts: Structures needed to parse the byte data directly from on-chain
  • parsers: Classes to parse transactions (with help of layouts)
    • A class that takes in an InstructionContext and translates it into an internal representation of that instruction (Event)
    • Boilerplate to initialize a parser, tying together layouts and parsers

A possible route to take with Anchor-based programs, is to generate a GraphQL schema from the IDL, let the user modify the schema, and feed both into a generator for an indexer.

The GraphQL schema allows us to know:

  • What the user wants to index and what to filter
  • What kind of resolvers are going to be needed
  • If the user expects some additional data and will have to resolve it themselves

Image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.