Sol ana I DL t o A PI generator.
Table of Contents generated with DocToc
- How does it Work?
- Shank + Solita Example (Recommended)
- Anchor + Solita Example (Recommended)
- Type Aliases
- Custom De/Serializers
- 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 viacargo add shank
- annotate your Rust program as outlined here
- add
solita
to the dev dependencies of your SDK package viayarn 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
.
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 viayarn 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
.
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'
}
};
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',
},
};
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
viacargo 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)
})
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)
})
Find more solita, shank and anchor examples inside the metaplex-program-library.
Apache-2.0