@atomichub/atomicassets
v2.2.0
Published
JavaScript/TypeScript SDK for the AtomicAssets NFT standard on Antelope chains: read NFT data, serialize attribute data, and build v2 contract actions
Downloads
1,995
Maintainers
Readme
@atomichub/atomicassets
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.
Install
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).
Quickstart
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.
Picking a client
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.
Reading NFT data
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.
Supported networks
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', {});Sending transactions
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.
Contract versions
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.
Editing data replaces it
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
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.
What the builders validate
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.
Working with attribute data directly
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 objectconvertAttributeMapToObject handles the other raw shape, the attribute map that contract action data carries. It turns one, in either entry shape, into a plain key/value object, and it settles the JSON type of every value it copies over:
| Attribute type | Value in the returned object |
| --- | --- |
| float32, float64 (schema float, double) | A number. A value that arrives as a string is read back into a number, and a float32 rounds to the nearest float32: the chain value when the string kept full precision, and for a float32 below 1 handed over as a seven-decimal string the nearest float32 of that string, which can differ from the chain value by a few float32 steps. |
| FLOAT_VEC, DOUBLE_VEC (schema float[], double[]) | An array of those numbers, element by element. |
| uint64, int64, UINT64_VEC, INT64_VEC (schema uint64, int64, fixed64 and their vector forms) | A decimal string, so a value too large for a JavaScript number keeps every digit. |
| Every other type | What the codec decoded, unchanged. |
A string that does not read as a finite number stays the string it is, under a float type too, and so does an empty or whitespace-only string, or a string outside the range its type represents, which is the range the Postgres real and double precision casts accept.
What's new in 2.2.0
Returns float attribute values as numbers when a decoder hands them over as strings.
Bug fixes
convertAttributeMapToObjectreturns a number for afloat32orfloat64value that arrives as a string, and for the elements of aFLOAT_VECorDOUBLE_VEC. A map objectified by@wharfkit/antelopecarries those values as strings, so a consumer that passed one through published a string where the schema says a number; afloat32rounds to the nearest float32 first, which is the chain value when the string kept full precision and the nearest float32 of a seven-decimal string otherwise. A string that does not read as a finite number, an empty string, and a string outside the range its type represents pass through unchanged. (#23)
Other changes
- The README states the JSON type the helper returns for each attribute type: a number for
floatanddoubleand their vectors, a decimal string for the 64-bit integers and their vectors, and the decoded value for everything else. (#23)
What's new in 2.1.1
Rejects a path segment that would send a read to the wrong route.
Bug fixes
- 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)
What's new in 2.1.0
Validates the builders' numeric parameters and deprecates native asset backing.
Breaking changes
- 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)
Deprecations
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)
Other changes
- 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)
What's new in 2.0.4
Bug fixes
- 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)
Other changes
- 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)
What's new in 2.0.3
Bug fixes
- 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)
Other changes
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)
What's new in 2.0.2
Breaking changes
- 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
What's new in 2.0.1
A documentation release that carries the reworked README to the npm package page, with no code changes.
Other 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)
What's new in 2.0.0
Publishes the AtomicHub fork of atomicassets-js as @atomichub/atomicassets, updated for the v2 AtomicAssets contract.
Breaking changes
- 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)
Features
- 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)
Bug fixes
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)
Other changes
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)
Migrating from atomicassets 1.x
- 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.
Credits and license
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.
