Generate fully typed contract interfaces, functions, and React hooks for Clarity smart contracts.
// Usage with `@stacks/transactions`
import { mega } from './generated/contracts'
import { fetchCallReadOnlyFunction, makeContractCall } from '@stacks/transactions'
await makeContractCall({
...mega.callback({
sender: "SPKPXQ0X3A4D1KZ4XTP1GABJX1N36VW10D02TK9X",
memo: "Hello world",
}),
network: 'mainnet',
})
await fetchCallReadOnlyFunction({
...mega.getBalance(),
network: 'mainnet'
})
// Read helpers
const balance = await mega.read.getBalance() // {type: 'uint', value: 42000000n}
// Write helpers
const result = await mega.write.transfer(
{
amount: 10000n,
recipient: "SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335",
},
{
senderKey: "b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01",
}
);
import { useBnsV2Transfer } from './generated/hooks'
function App() {
const { transfer, isRequestPending } = useBnsV2Transfer()
return (
<button
onClick={() => transfer({
id: 1n,
owner: 'SP...',
recipient: 'SP...'
})}
disabled={isRequestPending}
>
Transfer
</button>
)
}
Note: This package is not yet published to npm. For now, you can install it locally:
bun add -g @secondlayer/cli
To create a stacks.config.ts
file, run secondlayer init
in your Clarinet project:
secondlayer init
// stacks.config.ts
import { defineConfig } from '@secondlayer/cli'
import { clarinet } from '@secondlayer/cli/plugins'
export default defineConfig({
out: 'src/generated.ts',
plugins: [clarinet()],
})
Run secondlayer generate
to create fully type-safe interfaces for your contracts.
secondlayer generate
β Generation complete for 2 contracts
π ./src/generated/contracts.ts
import { defineConfig } from '@secondlayer/cli'
import { clarinet, actions, react, hiro } from '@secondlayer/cli/plugins'
export default defineConfig({
out: 'src/generated.ts',
plugins: [
clarinet(), // Generate contract interfaces from local Clarinet project
actions(), // Add read/write helper functions
react(), // Generate React hooks
hiro({ // Generate contract interfaces using the Hiro API
apiKey: process.env.HIRO_API_KEY!,
network: 'mainnet',
contracts: [
'SP466FNC0P7JWTNM2R9T199QRZN1MYEDTAR0KP27.miamicoin-core-v1',
],
}),
],
})
Currently, Clarinet JS SDK users must manually convert JS values to Clarity types, recall contract signatures, and write boilerplate for common tests.
Solution: Enable zero-config Clarinet detection and auto-run code generation within the dev workflow.
import { describe, it, expect, beforeAll } from 'vitest';
import { initSimnet } from '@hirosystems/clarinet-sdk';
import { tokenContract } from './generated/helpers';
describe('Token Contract', () => {
let simnet;
beforeAll(async () => {
simnet = await initSimnet();
});
it('transfers tokens', async () => {
// Clean, focused API for testing
const result = await tokenContract.transfer({
amount: 1000n,
sender: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',
recipient: 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG'
}, 'wallet_1');
expect(result).toBeOk();
});
});
Currently, when calling read-only functions or receiving blockchain responses, developers must manually extract values from Clarity value objects that include type metadata (e.g., { type: "uint", value: 42000n }
). This requires repetitive boilerplate code to access the actual values.
Solution: Provide automatic Clarity value conversion with full TypeScript type inference, extracting raw values while preserving complete type safety.
import { daoContract } from './generated/contracts';
// Before
const result = await daoContract.read.getProposal(proposalId);
// Returns complex nested structure:
// {
// type: "tuple",
// value: {
// id: { type: "uint", value: 1n },
// proposer: { type: "principal", value: "SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7" },
// title: { type: "string-utf8", value: "Increase Treasury Allocation" },
// votesFor: { type: "uint", value: 150000n },
// votesAgainst: { type: "uint", value: 50000n },
// startBlock: { type: "uint", value: 120500n },
// endBlock: { type: "uint", value: 125500n },
// executed: { type: "bool", value: false }
// }
// }
// Tedious extraction needed:
return {
id: result.value.id.value,
proposer: result.value.proposer.value,
title: result.value.title.value,
votesFor: result.value.votesFor.value,
votesAgainst: result.value.votesAgainst.value,
startBlock: result.value.startBlock.value,
endBlock: result.value.endBlock.value,
executed: result.value.executed.value
};
// After
const proposal = await daoContract.read.getProposal(proposalId);
// Returns clean TypeScript object directly:
// {
// id: 1n,
// proposer: "SP2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKNRV9EJ7",
// title: "Increase Treasury Allocation",
// votesFor: 150000n,
// votesAgainst: 50000n,
// startBlock: 120500n,
// endBlock: 125500n,
// executed: false
// }
// Full type safety and IntelliSense:
console.log(proposal.endBlock); // β
TypeScript knows this is bigint
console.log(proposal.title); // β
TypeScript knows this is string
console.log(proposal.executed); // β
TypeScript knows this is boolean
return proposal;
MIT