@nosana/scenario
v2.2.5
Published
Nosana scenario test helpers: network-aware client setup for localnet, devnet, and mainnet
Readme
@nosana/scenario
Network-aware scenario test helpers for Nosana. Run the same tests against localnet, devnet, or mainnet — controlled entirely via environment variables.
Uses @nosana/localnet for Docker infrastructure and @nosana/kit for the Nosana SDK.
Quick Start
npm install --save-dev @nosana/scenarioWrite Tests
import { describe, it, expect } from 'vitest';
import { getScenarioClient, createMarket, listJob } from '@nosana/scenario';
describe('scenario: basic', () => {
it('works with the Nosana SDK', async () => {
const client = await getScenarioClient();
const balance = await client.solana.getBalance();
expect(balance).toBeGreaterThan(0);
});
it('creates a market and lists a job', async () => {
const marketAddress = await createMarket();
const jobAddress = await listJob({ market: marketAddress });
expect(jobAddress).toBeDefined();
});
});Multiple Clients
Use the key option to create independently funded clients — useful for multi-party scenarios:
const deployer = await getScenarioClient(); // cached as 'default'
const node = await getScenarioClient({ key: 'node' }); // separate wallet
const node2 = await getScenarioClient({ key: 'node2' }); // another separate walletEach unique key gets its own keypair, SOL airdrop, and NOS mint on localnet.
Run Against Different Networks
No code changes needed — just set environment variables:
# Localnet (default) — starts a local Solana validator via Docker
npm run test:scenario:localnet
# Devnet — requires a funded wallet
NOSANA_NETWORK=devnet NOSANA_WALLET=~/.config/solana/id.json npm run test:scenario
# Mainnet
NOSANA_NETWORK=mainnet NOSANA_WALLET=/path/to/keypair.json npm run test:scenarioEnvironment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| NOSANA_NETWORK | localnet | Target network: localnet, devnet, or mainnet |
| NOSANA_WALLET | — | Path to a Solana keypair JSON file (required for devnet/mainnet) |
Vitest Integration
// vitest.scenario.config.ts
import { defineScenarioVitestConfig } from '@nosana/scenario';
export default defineScenarioVitestConfig({
test: {
include: ['tests/scenarios/**/*.test.ts'],
},
});npm Scripts
These scripts are already included in @nosana/scenario's package.json:
| Script | Description |
|--------|-------------|
| test:scenario | Run scenario tests with vitest |
| test:scenario:localnet | Start localnet + run scenario tests |
| localnet:up | Start the Docker-based Solana validator |
| localnet:down | Stop the validator and clean up |
From the workspace root:
# Run everything
pnpm --filter @nosana/scenario run test:scenario:localnet
# Or step by step
pnpm run localnet:up
pnpm --filter @nosana/scenario run test:scenario
pnpm run localnet:downAPI Reference
getScenarioClient(options?): Promise<NosanaClient>
Returns a cached NosanaClient for the target network. Each unique key gets its own independently funded client instance.
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| key | string | 'default' | Cache key — each unique key creates a separate client with its own wallet |
| network | 'localnet' \| 'devnet' \| 'mainnet' | NOSANA_NETWORK env or 'localnet' | Target network |
| wallet | Wallet | NOSANA_WALLET env or auto-generated (localnet) | Wallet signer |
| airdropAmount | bigint | 2_000_000_000n | SOL airdrop amount (localnet only) |
| mintAmount | bigint | 1_000_000_000n | NOS mint amount (localnet only) |
| config | Partial<NosanaClientConfig> | — | Override client config |
Localnet (default): generates a random keypair, airdrops SOL, and mints NOS tokens.
Devnet / Mainnet: connects with the provided wallet (via option or NOSANA_WALLET env var). No airdrop or minting is performed.
getLocalnetClient(options?): Promise<NosanaClient>
Returns a cached NosanaClient connected to localnet. Equivalent to getScenarioClient({ network: 'localnet' }).
Helper Functions
All helper functions include built-in vitest expect assertions and accept an optional clientOverride parameter. When omitted, they use the default scenario client (getScenarioClient()).
Jobs
createMarket(params?, clientOverride?): Promise<Address>
Create a market on-chain and return its address.
const marketAddress = await createMarket();
const marketAddress = await createMarket({ jobPrice: 1000n });closeMarket(marketAddress, clientOverride?): Promise<void>
Close an existing market.
await closeMarket(marketAddress.toString());listJob(params, clientOverride?): Promise<Address>
List (submit) a job to a market and return the job address. timeout defaults to 3600 and ipfsHash defaults to a test hash — only market is required.
const jobAddress = await listJob({ market: marketAddress });
const jobAddress = await listJob({ market: marketAddress, timeout: 7200 });joinMarketQueue(marketAddress, options?, clientOverride?): Promise<void>
Join a market's node queue. Verifies the node is queued by default.
const nodeClient = await getScenarioClient({ key: 'node' });
await joinMarketQueue(marketAddress.toString(), {}, nodeClient);
// Disable queue verification
await joinMarketQueue(marketAddress.toString(), { verifyQueued: false }, nodeClient);waitForJobState(jobAddress, expectedState, clientOverride?): Promise<void>
Poll until a job reaches the expected state using vitest's expect.poll.
import { JobState } from '@nosana/scenario';
await waitForJobState(jobAddress.toString(), JobState.RUNNING);Nodes
finishJob(jobAddress, clientOverride?): Promise<void>
Finish (complete) a job as a node, submitting a default IPFS results hash.
const nodeClient = await getScenarioClient({ key: 'node' });
await finishJob(jobAddress.toString(), nodeClient);verifyJobAssignedToNode(jobAddress, options?, clientOverride?): Promise<void>
Assert that a job is assigned to the client's wallet. Optionally verify the job state.
const nodeClient = await getScenarioClient({ key: 'node' });
await verifyJobAssignedToNode(jobAddress.toString(), { expectedState: 1 }, nodeClient);Utilities
mintNosTo(client, recipient, amount): Promise<void>
Mint NOS tokens to any address on the localnet.
ensureLocalnetMint(client): Promise<{ mintAuthority, mintAddress }>
Ensures the NOS mint exists on localnet. Creates it if it doesn't exist.
executeInstructionPlan(client, plan): Promise<void>
Execute a Solana instruction plan using the client's wallet as fee payer.
defineScenarioVitestConfig(overrides?): object
Returns a Vitest config object with the scenario setup file pre-configured.
Re-exports from @nosana/localnet
startLocalnet,stopLocalnetLOCALNET_RPC_ENDPOINT,LOCALNET_WS_ENDPOINT
Re-exports from @nosana/kit
NosanaNetwork,JobState,type NosanaClient,type Wallet,type Address
