Generates SDK API from solana contract IDL.
npm install @metaplex-foundation/solitaSol ana I DL t o A PI generator.
Table of Contents generated with DocToc
- How does it Work?
- Shank + Solita Example (Recommended)
- Full Example: Token Metadata Solita + Shank Setup
- Anchor + Solita Example (Recommended)
- Full Example: MPL Candy Machine Solita + Anchor Setup
- Type Aliases
- Custom De/Serializers
- Hooking into IDL Creation
- Advanced Shank + Solita Example
- Advanced Anchor + Solita Example
- Solita in the Wild
- LICENSE
_Solita_ generates a low level TypeScript SDK for your _Solana_ Rust programs from the IDL extracted by
anchor or
shank.
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
``js
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.
- annotated instructions
- annotated accounts
- generated TypeScript
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.solitarc.js
- add a config similar to the below into in your SDK package root
`js
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.
NOTE: that for _anchor_ generated IDL an optional anchorRemainingAccounts property isanchorRemainingAccounts: false
added to each set of instruction accounts. If your programs are not using those you can
specifically turn that off by setting .
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.
`js`
module.exports = {
idlGenerator: 'anchor',
[ .. ]
typeAliases: {
UnixTimestamp: 'i64'
}
};
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:
`ts
export function deserialize(buf: Buffer, offset = 0): [
[..]
}
export function serialize(instance:
[..]
}
`
Then provide them as serializers to Solita or via the solita config:
`js`
module.exports = {
idlGenerator: 'shank',
[ .. ]
serializers: {
Metadata: './src/custom/metadata-deserializer.ts',
},
};
It is possible to modify the IDL generated by _anchor_ or _shank_ _before_ it is passed to the
_solita_ code generator.
Just provide an _idlHook_ of the type (idl: Idl) => Idl via the solita config.
This hook takes the current idl as an input and returns the modified version. It is ok to
modify the idl parameter in place if that is more convenient.
Please refer to the definition of the Idl
type
for more details.
Example:
`js`
module.exports = {
idlGenerator: 'anchor',
[ .. ]
idlHook: (idl) => {
return { ...idl, hola: 'mundo' }
}
};
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
js
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)
})
`
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
`js
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',${PROGRAM_NAME}.json
);
}
process.exit(1);
})
.on('exit', () => {
console.log('IDL written to: %s', path.join(generatedIdlDir, ));
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)
})
``
Find more _solita_, _shank_ and _anchor_ examples inside the metaplex-program-library.
Apache-2.0