Read and write AtomicAssets NFTs from JavaScript or TypeScript.
AtomicAssets is the NFT standard used on WAX and other Antelope chains. Its on-chain data is not plain JSON: attributes are packed into a binary format against a per-collection schema, and ids exceed what a JavaScript number can hold safely. This SDK handles that for you, so fetching someone's NFTs is one call that returns typed objects with the attributes already decoded.
If you are building a wallet, a marketplace, a game inventory, or anything that shows or moves NFTs on these chains, this is the client library for it.
npm install @atomichub/atomicassetsOn Node.js the package requires version 20 or newer. Browsers and bundlers are supported through the ESM and IIFE builds. The package has zero runtime dependencies and ships CJS, ESM, and a browser IIFE bundle (build/atomicassets.global.js, global atomicassets).
Show the NFTs an account owns:
import { explorerApiForNetwork } from '@atomichub/atomicassets';
const api = explorerApiForNetwork('wax');
const assets = await api.getAssets({ owner: 'someaccount1' }, 1, 20);
for (const asset of assets) {
console.log(asset.name, asset.collection.collection_name, asset.data['img']);
}There is nothing to configure first. explorerApiForNetwork points at AtomicHub's public endpoint for the network you name, and asset.data already holds the merged, decoded attributes.
Examples below continue from this one and reuse api. They use top-level await, which needs an ES module; under CommonJS, wrap them in an async function.
Responses are typed, so asset.name and asset.collection autocomplete and are checked at build time. asset.data is the exception: its keys come from whatever schema the collection defined, so it stays an open map you index by name.
Two ways to read the same data:
| Client | Use it when | Backed by |
|---|---|---|
ExplorerApi |
You want to filter, sort, paginate, or search. This is the usual choice. | A hosted atomicassets-api indexer |
RpcApi |
You want to read contract tables directly, with no indexer in the path. | A plain nodeos node |
ExplorerApi can answer questions the chain itself cannot, such as "the 20 most recently minted assets in this collection", because an indexer has already organized the data. RpcApi trades that away for reading straight from a node.
import { AssetsSort, OrderParam } from '@atomichub/atomicassets';
// One asset by id
const asset = await api.getAsset('1099511627786');
// A collection's assets, newest mint first
const newest = await api.getAssets(
{ collection_name: 'mycollection', sort: AssetsSort.Minted, order: OrderParam.Desc },
1,
50
);
// Templates, collections, and an account summary
const templates = await api.getTemplates({ collection_name: 'mycollection' });
const collection = await api.getCollection('mycollection');
const account = await api.getAccount('someaccount1');countAssets, countTemplates, and the other count* methods return totals for the same filters, which is what you want for pagination. Transfers, offers, burns, and per-asset logs have their own methods on the same client.
wax, wax-testnet, vaulta, xpr, xpr-testnet, jungle4.
Self-hosted or custom deployments use the constructors instead:
import { ExplorerApi, RpcApi } from '@atomichub/atomicassets';
const api = new ExplorerApi('https://wax.api.atomicassets.io', 'atomicassets', {});
const rpc = new RpcApi('https://wax.greymass.com', 'atomicassets', {});Reading needs no signing. When you want to mint, transfer, or burn, this SDK builds the action objects and hands them to whatever signing library you already use. It does not sign or broadcast anything itself.
import { ActionBuilder } from '@atomichub/atomicassets';
const builder = new ActionBuilder('atomicassets');
const send = builder.transfer(
'someaccount1', // from
'otheraccount', // to
['1099511627786'], // asset_ids
'gg' // memo
);That plugs straight into a signing library such as WharfKit:
await session.transact({
actions: [{
...send,
authorization: [{ actor: 'someaccount1', permission: 'active' }]
}]
});ActionBuilder is synchronous and returns authorization-free {account, name, data} objects, one method per contract action. If you would rather have authorization attached for you, ActionGenerator wraps the same builders and returns fully-authorized EosioActionObject arrays.
This package targets AtomicAssets v2, and the chains are migrating to it. v2 is a superset of v1, so every action v1 already had is spelled identically and works either way. The difference shows up only at the edges of the surface, where a chain that has not migrated yet will not accept all of it.
Read the version from the contract's own tokenconfigs table to know where a chain stands. The wax and jungle4 testnets serve v2 and its 47 actions, while the mainnets are still on v1's 35: wax and eos report 1.2.3, xpr reports 1.3.1. Of the networks listed above, that puts wax-testnet and jungle4 on v2, and wax, vaulta, xpr and xpr-testnet on v1 until they migrate.
Ten builders target actions v2 introduced: acceptauswap, createauswap, rejectauswap, createtempl2, deltemplate, redtemplmax, settempldata, setschematyp, setlastpayer and setrampayer. A chain that has not migrated has no such action and rejects the transaction as unknown, so check the version before sending one of these at a mainnet endpoint.
Native token backing runs the other way, as a removal rather than an addition. v2 disables it, so backasset is deprecated and aborts there, and mintasset aborts on a non-empty tokens_to_back. Both still execute on a chain that has not migrated, which means a call that works is a chain that has not arrived yet, not a supported path to build on.
init, admincoledit, setversion and addconftoken are contract administration, each guarded by require_auth(get_self()). The builders emit them for a caller that holds that authority, but no ordinary account can execute them.
setcoldata, setassetdata, settempldata and setschematyp each take the map you pass as the complete new value. The contract serializes exactly what it is handed over the existing row, so any attribute you leave out is dropped rather than kept, and settempldata with an empty map erases the row. No contract check compares your map against what was stored, so a read-modify-write that forgets to merge destroys the omitted attributes and still reports success. Read the current data, merge your change into it, and send the whole map.
Minting takes more arguments because the contract does, and attribute data has to be typed on the way in:
import { ActionBuilder, createAttributeMap } from '@atomichub/atomicassets';
const builder = new ActionBuilder('atomicassets');
const immutableData = createAttributeMap(
{ name: 'Dragon', level: 12 },
{ name: 'string', level: 'uint16' }
);
const mint = builder.mintasset(
'creatoracct1', // authorized_minter
'mycollection', // collection_name
'myschema', // schema_name
-1, // template_id (-1 for none)
'receiveracct', // new_asset_owner
immutableData, // immutable_data
[], // mutable_data
[] // tokens_to_back
);createAttributeMap is what keeps you from hand-building the contract's attribute pairs and getting the types wrong.
tokens_to_back is empty above deliberately. Native backing is deprecated, and v2 aborts a mint that supplies any; see Contract versions.
Almost nothing, deliberately. A builder emits the action data you hand it, and the chain is what decides whether a name exists, an account is authorized, or a fee is allowed; its errors say which.
The exception is the numeric parameters, whose bad values are the only input that neither throws nor survives the trip. Action data reaches a signing library as JSON, and NaN and Infinity have no JSON form, so max_supply: NaN arrives as "max_supply": null with the mistake already erased. Each numeric parameter is therefore checked against the ABI type of the field it fills, and the error names that field: template_id must be an int32, which keeps -1 available as the "no template" sentinel; max_supply and new_max_supply must be uint32, so a fractional or negative supply is refused here rather than on chain; market_fee is a float64, so only its finiteness is checkable.
What the contract additionally requires of a value, such as the market fee a collection may charge, stays the chain's to enforce, and it returns a legible error for it. Bound anything else you read from a response before you trust it.
Most code never needs this. The API clients decode attributes for you, and asset.data is the result. The codec is exported for the cases where you are handling raw contract data yourself:
import { ObjectSchema, serialize, deserialize } from '@atomichub/atomicassets';
const schema = ObjectSchema([
{ name: 'name', type: 'string' },
{ name: 'level', type: 'uint16' },
{ name: 'tags', type: 'string[]' }
]);
const encoded = serialize({ name: 'Dragon', level: 12, tags: ['fire'] }, schema);
const decoded = deserialize(encoded, schema);
// decoded deep-equals the input objectRejects a path segment that would send a read to the wrong route.
- An empty id,
., or..passed to an Explorer path method throws before the request goes out. Percent-encoding leaves a dot segment intact, so the URL parser collapsed it and pointed the read at a different route on the same origin, and an empty id turned a single-item lookup into the list route above it. The guard covers the sixteen methods that take an asset id, collection, schema, template, offer, or account name in the path, and none of the three values is ever a valid one. (#20)
Validates the builders' numeric parameters and deprecates native asset backing.
- Numeric parameters on
ActionBuilderandActionGeneratorare checked against the ABI type of the field they fill and throw naming that field. ANaNorInfinityused to reach the action data asnull, and a fractional or negativemax_supplyused to reach the chain intact.template_idstill accepts-1, the contract's no-template sentinel, because the ABI type is signed. (#16)
backassetis deprecated on both the builder and the generator, andmintassetcarries the same deprecation on itstokens_to_backparameter. AtomicAssets v2 disables native backing behind acheck()guard, so both abort there while still executing on a chain that has not migrated. (#16)
- Value ranges the contract itself enforces stay unchecked, so no guard is stricter than the chain. (#16)
transfernames its first two parametersfromandto, after the ABI fields they become. The parameters are positional, so no call changes. (#16)- Ten builders that target actions AtomicAssets v2 introduced, and the four that require the contract's own authority, are marked in the README. (#16)
- The ESM build tree-shakes. Importing only
ActionBuilderno longer drags in the base58 coder, the parser table, or the action-name map, taking a browser bundle that imports only the builder from 14,663 to 9,272 minified bytes. (#16)
- The Explorer client percent-encodes caller-supplied path ids and custom data-filter keys, across the sixteen path-building methods and the query-string builder. An asset id, collection, schema, template, offer, or account name carrying
/,?,#,&, or=used to escape its own segment and reshape the request, on the GET path and the long-query POST fallback alike. Hostile input now stays a value. (#14)
- Typed data filters travel as
data%3Anumber.fieldrather thandata:number.field. The API's query parser percent-decodes keys before matching, so the server sees the same key. The plaindata.fieldform is unchanged. (#14)
- Constructing
ExplorerApiorRpcApino longer starts a network request, and neither does the action generator thatExplorerApi.actionresolves to. The contract config is fetched on first use, shared between concurrent callers, and refetched on the next use after a failure, so an outage while these clients are constructed cannot crash a Node process with an unhandled rejection or leave a client permanently stuck on a failed fetch. Retries are not rate limited by the SDK, so a caller polling through an outage owns its own backoff. (#13)
ExplorerApi.actionis now a read-only getter with the samePromisetype. Assigning to it throws instead of silently overwriting, and it no longer appears inObject.keysor in a spread of the instance. Rejection values are unchanged, includingRpcApi's raw-string invalid config rejection. (#13)- The RPC row objects (
assets,templates,schemas,collections,offers) are outside this change and still fetch eagerly when constructed. (#13)
- The exported table row type
Templates2TableRowis renamedMutableTemplatesTableRow, named for what the row carries (a template's mutable data) rather than the on-chain table suffix. The shape is unchanged, so only imports of the old name need updating. The old name is removed rather than aliased.ad18bb0
A documentation release that carries the reworked README to the npm package page, with no code changes.
- npm publishing runs through GitHub's trusted publisher rather than a stored token, so releases are tag-driven, environment-gated, and carry provenance with no long-lived publish secret. (#9)
- The README leads with what the library is for and a zero-config quickstart, separates the choice between the indexed and RPC clients from the method reference, and leads the transaction section with
transferrather thanmintasset. It states that the SDK builds actions and never signs or broadcasts. Both contract links now point at the maintained repositories rather than the archived ones. (#10)
Publishes the AtomicHub fork of atomicassets-js as @atomichub/atomicassets, updated for the v2 AtomicAssets contract.
- The package name is
@atomichub/atomicassets. Install it under that name and change imports from'atomicassets'. (#1) - Deep imports such as
atomicassets/build/API/Explorer/Paramsare replaced by root exports, for exampleimport { AssetsApiParams } from '@atomichub/atomicassets'. (#1) max_supplyandtemplate_idare numbers where the contract ABI defines them as numeric. The 64-bit id fields, asset ids and offer ids, remain strings. (#1)AttributeMapentries are strictly typed as{ key, value: [type, value] }. Build them withcreateAttributeMaportoAttributeMaprather than by hand. Decoding also accepts{first, second}: no contract version emits that shape, but CDT 4.1 and newer abigen does for the pair struct before the release build patches it back tokey/value. (#1)- Node.js 20 or newer is required. (#1)
- Zero runtime dependencies. Native
BigIntreplaces bn.js and the built-infetchreplaces node-fetch, and a customfetchcan still be injected. (#1) - Ships dual CJS and ESM output with bundled type declarations, plus a browser IIFE build. (#1)
- Covers the v2 contract surface: schema-field media types (
setschematyp) and mutable template data (createtempl2,settempldata). (#1) - Adds
ActionBuilder, a synchronous builder, alongside the authorizedActionGenerator. (#1) - Exports typed table rows, action data payloads and action names from the package root. (#1)
- Adds a serialization codec with strict bounds checking and explicit error classes. Codec output is byte-for-byte equivalent to the v1 big-integer implementation. (#1)
- Explorer query-parameter, enum, and response-object types exported from the root, so no deep
build/imports are needed. (#1)
IApiSchemacarries an optionaltypesarray, which/v1/schemasand/v1/schemas/{collection}/{schema}always send. Consumers ofgetSchemasandgetSchemano longer cast past a type that denied the field existed. It is optional because a schema nested in another response never carries it, and a 1.x server does not send it at all. (#6)
sideEffects: falseinpackage.json, so consumers of this pure library get tree-shaking. (#5)- A root
NOTICEfile carries the Apache-2.0 attribution for the vendored IEEE 754 float parser, whose license header esbuild strips on the way intobuild/. It is added to the files whitelist, since npm does not includeNOTICEby default. (#5)
- Package name:
npm install @atomichub/atomicassetsand change imports from'atomicassets'to'@atomichub/atomicassets'. - Deep imports such as
atomicassets/build/API/Explorer/Paramsare replaced by root exports:import { AssetsApiParams } from '@atomichub/atomicassets'. max_supplyandtemplate_idare numbers where the contract ABI defines them as numeric; 64-bit id fields (asset ids, offer ids) remain strings.AttributeMapentries are strictly typed as{ key, value: [type, value] }; usecreateAttributeMaportoAttributeMapinstead of hand-building entries. Decoding also accepts{first, second}, which is not a contract version's shape but what CDT 4.1 and newer abigen emits for the pair struct before the release build patches it back tokey/value.- Node.js 20 or newer is required.
Fork of atomicassets-js by pink.network, updated for the v2 AtomicAssets contract; see Contract versions for where each chain stands in the migration to it. Maintained by AtomicHub.
The market-side companion package is @atomichub/atomicmarket.
MIT licensed; see LICENSE for the full text including the original pink.network copyright.