Read NFT market data from the AtomicMarket contract in JavaScript or TypeScript.
AtomicMarket is the marketplace contract that sits on top of the AtomicAssets NFT standard on WAX and other Antelope chains. It is where listings, auctions, buyoffers, and collection royalties live. This SDK gives you typed access to all of it, so asking what is currently for sale in a collection, and for how much, is one call that comes back with prices, token symbols, and the assets themselves already resolved.
If you are building a marketplace front end, a price tracker, a bot that posts sales, or anything that reports on NFT trading on these chains, this is the client library for it.
For the NFTs themselves rather than the market around them, use the companion package @atomichub/atomicassets.
npm install @atomichub/atomicmarketOn Node.js the package requires version 20 or newer. Browsers and bundlers are supported through the ESM and IIFE builds. The package ships CJS, ESM, and a browser IIFE bundle (build/atomicmarket.global.js, global atomicmarket). Its only dependency is the sibling @atomichub/atomicassets.
List what is currently for sale in a collection:
import { marketApiForNetwork, SaleState } from '@atomichub/atomicmarket';
const api = marketApiForNetwork('wax');
const sales = await api.getSales(
{ collection_name: 'mycollection', state: SaleState.Listed },
1,
20
);
for (const sale of sales) {
const { amount, token_symbol, token_precision } = sale.price;
// amount is an integer string in the token's smallest unit, so scale it
// with BigInt. Dividing through a JS number loses digits once the value
// passes 2^53.
const unit = 10n ** BigInt(token_precision);
const whole = BigInt(amount) / unit;
const fraction = (BigInt(amount) % unit).toString().padStart(token_precision, '0');
const price = token_precision > 0 ? `${whole}.${fraction}` : `${whole}`;
console.log(sale.sale_id, sale.seller, `${price} ${token_symbol}`);
}There is nothing to configure first. marketApiForNetwork points at AtomicHub's public endpoint for the network you name.
Examples here use top-level await, which needs an ES module. Under CommonJS, wrap them in an async function.
Prices arrive as integer strings with a separate precision, which is how the chain stores them. Scale with BigInt rather than dividing through a JS number: past 2^53 a number drops digits, so 123456789012345678 at precision 8 renders as 1234567890.12345672 instead of ...78. Keep the raw string for comparing and storing, and convert only to display.
The three listing types have parallel methods, so what you learn on one carries to the others:
const sales = await api.getSales({ collection_name: 'mycollection' });
const auctions = await api.getAuctions({ collection_name: 'mycollection' });
const buyoffers = await api.getBuyoffers({ collection_name: 'mycollection' });
// One listing by id
const sale = await api.getSale('100');
// Totals for the same filters, which is what pagination needs
const total = await api.countSales({ collection_name: 'mycollection' });
// The history behind a listing
const logs = await api.getSaleLogs('100');SaleState says where a listing stands: Waiting, Listed, Canceled, Sold, or Invalid. Filter on SaleState.Listed for "currently buyable", because the unfiltered call returns every sale the contract has ever recorded.
Each sale carries its assets, so rendering a listing usually needs no second call to the AtomicAssets API.
getSalesV2 and countSalesV2 query the /v2/sales route, a newer materialized index with the same row shape as /v1/sales. Note that /v2/sales omits Waiting sales, so state filters including Waiting belong on the v1 route.
wax, wax-testnet, vaulta, xpr, xpr-testnet, jungle4.
Behind those endpoints is a hosted atomicassets-api indexer. Pass the constructor your own host if you run one, or a public host for a network without a preset:
import { AtomicMarketApi } from '@atomichub/atomicmarket';
const api = new AtomicMarketApi('https://my-indexer.example.com', 'atomicmarket', {});The built-in fetch is used unless you pass your own in the options argument.
AtomicMarket v2 moved royalty configuration on chain, so a collection can split its fee between founders, specific templates, and assets matching an attribute. Those splits are readable:
// null when the collection has not configured royalties
const config = await api.getRoyaltyConfig('mycollection');
const templateRules = await api.getRoyaltyTemplateRules('mycollection');
const attributeRules = await api.getRoyaltyAttributeRules('mycollection');What the collection has actually paid is a separate read. The indexer keeps one ledger row for every royalty the contract settled, and aggregates one account's rows per token:
const payouts = await api.getRoyaltyPayouts({
collection_name: 'mycollection',
recipient: 'founderacct1',
category: 'template'
}, 1, 100);
const settled = await api.countRoyaltyPayouts({collection_name: 'mycollection'});
// one row per token symbol the account has been paid in
const earned = await api.getRoyaltyAccount('founderacct1');amount is in raw token units, so read it against the token_precision of the same row: 5000000 at precision 8 is 0.05000000 WAX. The same holds for the amount on a getRoyaltyAccount row, which sums the payouts the filters admit, and its payout_count is a decimal string rather than a number.
category names the rule that paid, one of founders, template, attribute, or dust, and it tells you which linkage the row carries: a template payout sets template_id, an attribute payout sets rule_id, and a founders or dust payout sets neither. A dust row is the rounding remainder plus the author fallback, paid to the collection author, and it names no asset either. listing_id is null when listing_type is unresolved, which is the row the indexer keeps when it cannot trace a settlement back to the listing that triggered it. A row whose stored value falls outside the vocabulary this SDK serves reads null for both listing_type and category.
The ledger pages like the listing routes. It sorts newest first by default, takes sort of created or amount, order of asc or desc, a limit up to 100, and lower_bound, upper_bound, or ids over log_global_sequence. RoyaltyListingType, RoyaltyPayoutCategory, and RoyaltyPayoutSort are exported for the filter values.
On a chain still running AtomicMarket v1 the contract logs no payouts and configures no royalties, so the ledger reads empty, the count is zero, and every collection answers getRoyaltyConfig with null. An indexer built before the royalty routes existed is a different case: it answers 404, which arrives as an ApiError with status 404 from every one of these methods. Only the HTTP 416 that getRoyaltyConfig receives for a collection with no royalty config becomes null; the ledger and count routes return empty results instead, never null.
Reading needs no signing. To change a collection's royalty split, this SDK builds the action objects and hands them to whatever signing library you already use. It does not sign or broadcast anything itself.
The six royalty actions arrived with v2. A chain still running AtomicMarket v1 carries none of them in its ABI, so on such a chain a signing library cannot serialize what these builders return, let alone submit it.
import { MarketActionBuilder } from '@atomichub/atomicmarket';
const builder = new MarketActionBuilder('atomicmarket');
const actions = builder.setroyalconf('mycollection', {
founders: [{ recipient: 'founderacct1', weight: 1 }],
attribute_mode: 0,
split_founders: 5000,
split_templates: 2500,
split_attributes: 2500
});Those three numbers are relative weights, not basis points and not percentages. At settlement each asset is weighed against only the categories that have a payee for it, and the weights are renormalized across those, so that no share is stranded. The 5000/2500/2500 above pays founders two thirds and the template one third on an asset that has a template royalty row and matches no attribute rule, and pays founders the entire fee on an asset with neither. Where no category has a payee, the whole share goes to the collection author.
The result plugs into a signing library such as WharfKit:
await session.transact({
actions: actions.map((action) => ({
...action,
authorization: [{ actor: 'authoracct11', permission: 'active' }]
}))
});MarketActionBuilder is synchronous and returns authorization-free {account, name, data} objects. If you would rather have authorization attached for you, MarketActionGenerator wraps the same builders.
The sale, auction, buyoffer, and template-buyoffer actions build the same way: unsigned action objects for your signing library. uint64 fields (the sale, auction, and buyoffer ids, the asset and template ids, and intended_delphi_median) are strings so 64-bit values pass through without precision loss. Price fields use chain notation: '100.00000000 WAX' for an asset, '8,WAX' for a symbol.
import { MarketActionBuilder } from '@atomichub/atomicmarket';
const builder = new MarketActionBuilder('atomicmarket');
// Take a listing down
const cancel = builder.cancelsale('42');The RAM payment actions move the RAM cost of a listing's table row onto the payer; the row itself is unchanged. Any account may pay, signing as the payer; no authority over the listing is needed. Marketplaces run them to sponsor their sellers' RAM.
const actions = builder.paysaleram('payeracct111', '42');
const auctionRam = builder.payauctram('payeracct111', '42');
const buyofferRam = builder.paybuyoram('payeracct111', '7');Several of these flows are not single actions, though, and in each case the action order, the memo literals, and which contract each action belongs to are rules of the contracts rather than choices. Those flows come as composed helpers, so that knowledge lives here instead of in every integration. The actions they compose stay on the builder as well, for anything that needs to assemble its own transaction shape, with two exceptions noted below where building the action alone is not safe.
announceSaleActions returns the pair a listing takes: announcesale on the market contract, then AtomicAssets' createoffer handing the assets over to it with memo 'sale'.
const listing = builder.announceSaleActions({
seller: 'selleracct11',
asset_ids: ['1099511627776'],
listing_price: '100.00000000 WAX',
settlement_symbol: '8,WAX',
maker_marketplace: '', // '' for none, or your registered marketplace account
assets_contract: 'atomicassets'
});Announcing alone lists nothing and offering alone leaves the assets in an offer nobody accepts, so the two belong in one transaction.
A v2 listing holds exactly one asset. announcesale rejects any other size and asks for one sale per asset instead, which one transaction can announce several of. A chain still running v1 takes bundle listings, and this helper builds whatever it is handed either way, since a refused listing is a rejected transaction and nothing is lost to it.
Nothing about the symbols is checked here. announcesale takes a listing whose settlement_symbol is its price's own symbol if that symbol is a supported token, and one whose settlement symbol is anything else if the two are a registered pair, so both readings are legitimate and which one your listing gets is chain state this helper is not handed.
purchaseSaleActions returns the purchase triple: assertsale pinning the terms you expect to buy, a transfer with memo deposit crediting the market contract, then purchasesale spending that credit. Only the purchase's place is fixed, since it spends the deposited balance and erases the sale row the assertion reads; the assertion writes nothing, so a transaction assembling itself from the raw builders may equally deposit first.
const purchase = builder.purchaseSaleActions({
buyer: 'buyeracct111',
sale_id: '42',
asset_ids: ['1099511627776'],
listing_price: '100.00000000 WAX',
settlement_symbol: '8,WAX',
intended_delphi_median: '0',
token_contract: 'eosio.token', // the settlement token's own contract, from IMarketToken.token_contract
taker_marketplace: '' // '' for none, or your registered marketplace account
});The deposit is a token transfer rather than an AtomicMarket action, which is why the helper needs token_contract. assertsale is what makes the triple safe against a sale that changed between reading it and the transaction landing: if the ids, the price, or the settlement symbol have moved, the assertion fails and nothing does.
It does not cover a sale of several assets. purchasesale on v2 returns early for such a row, declining the offer and erasing the listing before it reaches a balance, while assertsale passes on the very ids that made it return and the deposit has already credited the buyer. The transaction commits, and the buyer has paid for nothing and must withdraw to get the tokens back. So purchaseSaleActions throws on more than one asset_ids entry. Bundles are ordinary listings on a chain still running v1, where they purchase correctly, and allow_v1_bundle_sale: true is the opt-out for buying one there.
intended_delphi_median is '0' for a sale listed directly in its settlement token, and that is the whole story for most sales. The contract settles such a sale at its listing price, so settlement_quantity may be omitted; supplying it is fine, and common, but it must then equal listing_price exactly, since the deposit is what funds the purchase and the purchase spends the listed amount whatever you deposited.
A sale is that one exactly when settlement_symbol is listing_price's own symbol, which means the precision as well as the code: '100.00000000 WAX' settles '8,WAX', and the same price against '4,WAX' names two different symbols and settles through the oracle like any other pair.
A sale can be listed in one currency and settled in another at the delphioracle rate, which is what a settlement_symbol other than the listing price's own symbol means, and what a non-zero intended_delphi_median accompanies. Its two price fields then describe two different symbols: listing_price is what the seller asked in the listing currency, and the buyer deposits what that converts to at the median. assertsale pins the listing terms only, so no on-chain check stands behind the deposit amount, which makes deriving it the step worth getting right.
getConfig carries the pair, deriveSettlementAmount converts, and formatQuantity renders the result as the quantity string the transfer takes:
import { deriveSettlementAmount, formatQuantity, marketApiForNetwork } from '@atomichub/atomicmarket';
const api = marketApiForNetwork('wax');
const sale = await api.getSale('42');
const config = await api.getConfig();
const pair = config.supported_pairs.find(
(candidate) => candidate.listing_symbol === sale.listing_symbol
&& candidate.settlement_symbol === sale.price.token_symbol
);
if (!pair || !sale.price.median) {
throw new Error(`sale ${sale.sale_id} is not a delphi sale this pair set covers`);
}
// Bound what the API served before converting it. A median past 2^53 has
// already lost precision at JSON parse, and a non-integer listing_price
// would surface as an opaque BigInt error instead of a named one.
if (!Number.isSafeInteger(sale.price.median) || sale.price.median <= 0) {
throw new Error(`sale ${sale.sale_id}: median ${sale.price.median} is not a positive safe integer`);
}
if (!/^\d+$/.test(sale.listing_price)) {
throw new Error(`sale ${sale.sale_id}: listing_price ${sale.listing_price} is not a raw integer amount`);
}
// The listing symbol sits on one side of the price feed or the other, and
// takes that side's precision.
const listingPrecision = sale.listing_symbol === pair.data.base_symbol
? pair.data.base_precision
: pair.data.quote_precision;
const settlement = deriveSettlementAmount(BigInt(sale.listing_price), BigInt(sale.price.median), {
median_precision: pair.data.median_precision,
base_precision: pair.data.base_precision,
quote_precision: pair.data.quote_precision,
invert_delphi_pair: pair.invert_delphi_pair
});
// Whoever serves getConfig also controls the numbers deriveSettlementAmount
// works from, so the amount it derives and the amount this same response
// separately reports should agree before either is trusted. An indexer
// converts in its own arithmetic rather than the contract's, and the two can
// drift by the last place of the contract's double: one raw unit at ordinary
// magnitudes, proportionally more past 2^53. Anything wider than that is a
// disagreement rather than rounding, and a reason to stop.
const reported = /^\d+$/.test(sale.price.amount) ? BigInt(sale.price.amount) : undefined;
const drift = reported === undefined ? undefined
: settlement > reported ? settlement - reported : reported - settlement;
// One raw unit, widened by the spacing of doubles at this magnitude.
const tolerance = 1n + settlement / 2n ** 52n;
if (drift === undefined || drift > tolerance) {
throw new Error(`sale ${sale.sale_id}: price.amount ${sale.price.amount} does not match the median-derived expectation ${settlement.toString()} for listing_price ${sale.listing_price} ${sale.listing_symbol} at median ${sale.price.median}`);
}
const purchase = builder.purchaseSaleActions({
buyer: 'buyeracct111',
sale_id: sale.sale_id,
asset_ids: sale.assets.map((asset) => asset.asset_id),
listing_price: formatQuantity(BigInt(sale.listing_price), listingPrecision, sale.listing_symbol),
settlement_symbol: `${sale.price.token_precision},${sale.price.token_symbol}`,
settlement_quantity: formatQuantity(settlement, sale.price.token_precision, sale.price.token_symbol),
intended_delphi_median: String(sale.price.median),
token_contract: sale.price.token_contract,
taker_marketplace: ''
});Amounts on the wire are raw integers in their symbol's smallest unit, and deriveSettlementAmount returns the integer the contract charges rather than the exact quotient of those integers. The two are not always the same one. The contract divides and scales in double-precision floating point and truncates the result, so on the WAX/USD pair it lands one raw unit above the exact floor on a small fraction of listing amounts from about $12,124 up, and a deposit derived from the exact floor is then a unit short of what the purchase spends. The derivation reproduces the contract's arithmetic operation for operation so that the amount you deposit is the amount that is taken.
Two cases have no amount to reproduce and throw instead. A pair whose exponent works out negative, meaning the settlement symbol carries fewer decimals than the median and listing symbols together call for, is one the contract cannot convert at all: it computes that exponent in unsigned arithmetic, where a negative one becomes an enormous positive one and the conversion overflows. And a result at or past 2^64 has no integer for the contract to charge. Past 2^53 the derivation keeps returning values, with the caveat that the contract's own double no longer represents the quotient exactly there, so the amount charged can sit some way off it in either direction; that is the contract's arithmetic, and matching it is the point.
invert_delphi_pair says which way the underlying price feed is oriented, and it is the reason the listing precision is read off the matching side of the feed rather than assumed.
settlement_quantity is required whenever settlement_symbol is not the listing price's own symbol, and it must be denominated in that settlement symbol. Without it the helper would deposit the listing price, which on such a sale is an amount in the wrong currency; with a quantity in some third symbol the deposit credits a balance the purchase never spends, while the real settlement amount is drawn from whatever the buyer already holds in the right one. Both throw.
An auction is announced and then activated. announceauct writes the row, and the auction becomes biddable only once the seller transfers the assets into the market contract's custody with memo 'auction'. announceAuctionActions returns that pair.
const auction = builder.announceAuctionActions({
seller: 'selleracct11',
asset_ids: ['1099511627776'],
starting_bid: '10.00000000 WAX',
duration: 86400, // seconds
maker_marketplace: '', // '' for none, or your registered marketplace account
assets_contract: 'atomicassets'
});The order is the contract's rather than the helper's: the transfer's handler looks an announced auction up by its assets and its seller, so a transfer arriving first has nothing to activate and fails. Where a sale takes an AtomicAssets offer, an auction takes a transfer, and the assets sit in the market contract's custody for the auction's whole duration rather than in an offer until someone buys.
duration is the one field here read as a number, and it is checked for being a whole number inside the uint32 range before it is packed, so a NaN cannot reach your signing library as a null. Whether it also falls inside the contract config's minimum and maximum, whether the starting bid's symbol is supported, and whether the marketplace is registered are chain state, and each refusal is a rejected transaction.
The rest of the lifecycle is plain builders:
// Pair an assertion with a bid the way assertsale pairs with a purchase
const assertion = builder.assertauct('42', ['1099511627776']);
const bid = builder.auctionbid('bidderacct11', '42', '11.00000000 WAX', '');
const claimedByBuyer = builder.auctclaimbuy('42'); // the winner takes the assets
const claimedBySeller = builder.auctclaimsel('42'); // the seller takes the bid, less fees
const cancelled = builder.cancelauct('42'); // only before a bid landsA bid is spent from the bidder's balance inside the market contract rather than from their wallet, so fund it first with a transfer carrying memo deposit. An outbid bid returns to its bidder's balance the same way, and withdraw is what moves a balance back out.
An auction row holding more than one asset predates v2's removal of bundle listings and can no longer be bid on or claimed. Bidding on one, claiming one, or cancelling one dissolves it instead: the standing bid returns to the bidder's balance, the assets return to the seller, the row is erased, and the transaction commits. Nothing guards against that, since these actions are handed an auction id and this SDK cannot see how many assets the row holds, and a bundle row is an ordinary auction on a chain still running v1. Nothing is stranded either way, which is what keeps it on the documented side of the line.
A buyoffer is an unsolicited bid on an asset somebody else owns. The buyer escrows the price with createbuyo, and the recipient accepts or declines it.
const wanted = builder.createbuyo(
'buyeracct111', 'holderacct11', '100.00000000 WAX', ['1099511627776'], 'please sell', ''
);
const withdrawn = builder.cancelbuyo('7'); // the buyer changes their mind
const refused = builder.declinebuyo('7', 'not for sale'); // the recipient says nocreatebuyo spends the price from the buyer's market balance, the way a bid does, so a deposit transfer usually comes first. Cancelling and declining both return it.
Accepting is a composed flow, because the contract takes no offer id. acceptbuyo reads the globally last created row of the AtomicAssets offers table and checks it against the buyoffer, so the offer has to be created in the same transaction, immediately before it:
const accepted = builder.acceptBuyofferActions({
recipient: 'holderacct11', // signs the transaction, and sends the offer
buyoffer_id: '7',
asset_ids: ['1099511627776'],
expected_price: '100.00000000 WAX',
taker_marketplace: '',
assets_contract: 'atomicassets'
});No other createoffer may run between this helper's createoffer and the market action: the contract would read that one instead, and every check it makes against the offer would fail against it. Appending further actions after the market action, including another accept flow, is safe, the inline acceptoffer having consumed the row by then. Do not accept the offer yourself either, since the market contract sends that acceptoffer, and an offer already accepted is gone from the table before the contract can find it. That is why acceptbuyo is reachable only through this helper.
acceptBuyofferActions throws on more than one entry in asset_ids. It is the one place in these families where a mistake commits and leaves damage behind rather than reverting: acceptbuyo on v2 returns early for a buyoffer row holding several assets, refunding the buyer and erasing the row before it reads the offers table at all, so the transaction lands with nothing sold and the offer this flow created still in the offers table on the recipient's RAM, neither accepted nor declined, until they cancel it. Bundle buyoffers accept correctly on a chain still running v1, and allow_v1_bundle_buyoffer: true is the opt-out for one there.
A template buyoffer is a standing bid on any asset of a template rather than on one particular asset, so any holder of that template can fill it.
const wanted = builder.createtbuyo('buyeracct111', '100.00000000 WAX', 'mycollection', '1234', '');
const withdrawn = builder.canceltbuyo('9');template_id is a uint64 on these actions and passes through as a string, unlike the int32 template_id the royalty builders take. They are two different ABI types on different actions.
Filling one reads the offers table exactly as accepting a buyoffer does, with memo 'tbuyoffer', and fulfillTemplateBuyofferActions composes that pair:
const fulfilled = builder.fulfillTemplateBuyofferActions({
seller: 'selleracct11', // signs the transaction, and sends the offer
buyoffer_id: '9',
asset_id: '1099511627776', // must carry the template the buyoffer names
expected_price: '100.00000000 WAX',
taker_marketplace: '',
assets_contract: 'atomicassets'
});The same-transaction rule and the no-other-createoffer rule hold unchanged, and fulfilltbuyo is likewise reachable only through this helper. This one carries no bundle guard, a template buyoffer naming a single asset by construction.
Almost nothing, deliberately. These are composition helpers over values you already trust: they emit what you hand them, and checking a sale you read from an API is your side of that line. The exceptions all share one property, that their failure is a wrong payment rather than a rejected transaction, and each names the offending values in the error:
purchaseSaleActionsthrows on more than one entry inasset_ids, unlessallow_v1_bundle_salesays the chain is still on v1.purchaseSaleActionsrequiressettlement_quantity, denominated insettlement_symbol, when that symbol is not the listing price's own; and when it is, requires a supplied one to equallisting_priceandintended_delphi_medianto be'0'.acceptBuyofferActionsthrows on more than one entry inasset_ids, unlessallow_v1_bundle_buyoffersays the chain is still on v1.announceauctrefuses adurationthat is not a whole number inside the uint32 range, which is a serialization bound rather than a chain rule: the configured minimum and maximum are chain state and go unchecked.- The delphi utilities reject a non-positive median, a precision outside the 0 to 18 the chain allows, and a pair the contract's own conversion cannot compute.
The symbol checks turn on the discriminator the contract itself uses, whether the sale's two symbol fields name a single symbol, precision and code both.
Two of them do foreclose a purchase the chain would have taken, deliberately. Requiring a supplied settlement_quantity to equal listing_price rules out depositing more than the sale costs, which the chain accepts and leaves as balance. Requiring one at all on the oracle branch rules out depositing nothing and letting a standing balance pay, which the chain also accepts. Both are legitimate for a caller who means them and indistinguishable from a wrong amount for one who does not, and the helper cannot see a balance to tell them apart. If you want either, assemble the transaction from assertsale, your own transfer, and purchasesale on the builder, which assert nothing.
Nothing here reads chain state. Whether a symbol is supported, and whether a pairing of two is registered, is chain state, which is why announceSaleActions checks nothing at all and why the settlement amount an oracle-settled sale deposits goes unchecked here, the helper never being handed the pair it derives from. Bound anything else you read from a response before you trust it.
Refuses a path id that would send the request somewhere else.
- An empty id,
., or..passed to an Explorer method that puts it in a path segment now throws before the request.encodeURIComponentleaves a dot alone, so the URL parser insidefetchused to resolve the segment away and read a neighbouring route on the same origin:getRoyaltyAccount('..')requested/v1/royalties/, and an empty id turned a single-row route into its list. The thirteen readers that take an id or a name in the path, over sales, auctions, buy-offers, marketplaces, royalties, assets, and offers, all carry the check. (#25)
Reads the settled royalty ledger, so a consumer no longer has to page the payout logs itself.
IRoyaltyConfig,IRoyaltyTemplateRule, andIRoyaltyAttributeRulegain requiredmarket_contract,collection_name, and timestamp fields, and the attribute rule gainslookup_hash. Reading a response is unaffected. Code that builds one of these rows by hand, such as a test mock, must supply the added fields. (#23)
getRoyaltyPayouts,countRoyaltyPayouts, andgetRoyaltyAccountcover the AtomicMarket v2 payout ledger: every settled royalty, the count behind it, and one account's totals per token symbol. Payout filters travel asRoyaltyPayoutApiParams, whose primary boundary ranges overlog_global_sequence. The account totals take the date window alone, because that route groups the boundary column away. (#23)IRoyaltyPayoutandIRoyaltyAccountTotaltype the two new row shapes, andRoyaltyListingType,RoyaltyPayoutCategory, andRoyaltyPayoutSortpin the strings the indexer serves and filters on. (#23)IRoyaltyConfig,IRoyaltyTemplateRule, andIRoyaltyAttributeRulecarry themarket_contract,collection_name, and four timestamps of their rows, and the attribute rule also carries itslookup_hash. (#23)
Adds the auction, buy-offer and template-buy-offer builders and aligns the purchase path with the v2 contract.
purchaseSaleActionsthrows whenasset_idshas more than one entry. On AtomicMarket v2 a bundle purchase commits with the buyer charged and nothing delivered. Setallow_v1_bundle_sale: trueon chains still running v1, where bundles are ordinary listings. (#19)deriveSettlementAmountmatches the contract's own conversion instead of the exact quotient: where the contract's double arithmetic lands one raw unit above the exact floor, so does the SDK, and that unit is the difference between a deposit that funds the purchase and one that leaves it short. It throws on a pair with a negative exponent and on a result at or past 2^64. (#19)purchaseSaleActionsrequiressettlement_quantityinsettlement_symbolon the cross-symbol branch, and on a same-symbol sale it must equallisting_pricewithintended_delphi_medianset to'0'. (#19)purchaseSaleActionspicks its branch from the contract's settlement discriminator, full symbol equality betweenlisting_priceandsettlement_symbol. A settlement symbol differing from the price in precision alone therefore settles through the oracle rather than reading as a mismatch. (#19)
- Adds auction, buy-offer and template-buy-offer actions to
MarketActionBuilderandMarketActionGenerator:announceauct,cancelauct,auctionbid,auctclaimbuy,auctclaimsel,assertauct,createbuyo,cancelbuyo,declinebuyo,createtbuyo,canceltbuyo. (#20) announceAuctionActions,acceptBuyofferActionsandfulfillTemplateBuyofferActionscompose the multi-action flows.acceptbuyoandfulfilltbuyoread the last created row of the AtomicAssets offers table, so neither is safe to build alone and both are reachable only through these helpers. (#20)AnnounceAuctionInput,AcceptBuyofferInputandFulfillTemplateBuyofferInputare exported besidePurchaseSaleInputandAnnounceSaleInput. (#20)
sideEffects: falseinpackage.json, matching the sibling@atomichub/atomicassetspackage, so bundlers may drop the package from builds that import nothing from it. (#19)
- The Explorer client percent-encodes caller-supplied path ids and custom data-filter keys, across the twelve path-building methods and the query-string builder. A sale id, marketplace or collection name, or
DataOptionskey carrying/,?,#,&, or=used to escape its own segment and reshape the request path or query. Hostile input now stays a value. (#17)
- Typed data filters travel as
data%3Anumber.fieldrather thandata:number.field. Standards query parsers percent-decode keys before matching, so servers see the same key. This is confirmed against the live API, where the encoded and raw forms return identical filtered rows. The plaindata.fieldform is unchanged. (#17)
Composes the purchase and listing flows and adds the delphi settlement helpers.
purchaseSaleActionscomposesassertsale, the deposit transfer, andpurchasesale.announceSaleActionscomposesannouncesaleand the atomicassetscreateoffercarrying the sale memo. Both emit authorization-free action data in the order the market contract expects. (#16)purchaseSaleActionsthrows when a delphi-priced sale arrives without asettlement_quantity, because that omission pays the wrong amount rather than failing the transaction. (#16)deriveSettlementAmountreproduces the market contract's settlement math withBigInt, the float formula losing precision at on-chain magnitudes.formatQuantityrenders raw integer amounts as chain quantities. (#16)DelphiPairSpecprojects a supported pair fromgetConfigand rejects a non-positive median or a precision outside the chain's 0 to 18, naming the field in the error. (#16)IMarketPair.data.quote_precisionis typednumberrather than the literal2, so a pair read from the API feeds aDelphiPairSpecdirectly. (#16)regmarketandwithdrawaction builders cover marketplace registration and market-balance withdrawal. (#15)
Adds the sale lifecycle and RAM payment actions, plus the /v2/sales routes.
- Adds the sale lifecycle actions to
MarketActionBuilderandMarketActionGenerator:announcesale,cancelsale,assertsale,purchasesale. The uint64 fields, the listing ids andintended_delphi_median, are strings, so 64-bit values pass through without precision loss. (#14) - RAM payment actions
paysaleram,payauctramandpaybuyorammove the RAM cost of a listing's table row onto the payer. Any account may pay, signing as the payer. (#14) - Adds
getSalesV2andcountSalesV2for the/v2/salesroute, a newer materialized index with the same row shape as/v1/sales./v2/salesomits Waiting sales, so a state filter that includes Waiting belongs on the v1 route. (#14) countOffersjoinsgetOffers, both taking the newMarketOfferApiParams, which widens the state filter to comma-joined multi-state queries like the sale and auction params already allow. (#14)
Corrects the buy-offer state enums and the ESM import graph, and depends on the published @atomichub/atomicassets.
BuyofferStatenames the lifecycle the indexer reports (Pending,Declined,Canceled,Accepted,Invalid) instead of the sale lifecycle. The numeric values are unchanged, so filters built from raw numbers behave the same. Code using the removedBuyofferState.ListedandBuyofferState.Soldno longer compiles and moves to the corrected names. (#13)
TemplateBuyofferStateis new. The template buyoffer routes number their states fromListed, one below the other listing types. (#13)
- A bare
import ... from '@atomichub/atomicmarket'under Node ESM no longer fails with "does not provide an export named 'AtomicHubNetwork'".Networks.tsnow splits the value importNETWORK_ENDPOINTSfrom the type-onlyAtomicHubNetwork, so esbuild drops the type from the emitted graph. The CJS build was never affected. (#7)
- The
@atomichub/atomicassetsdependency is a registry range,^2.0.0, rather than a git ref, so the lockfile carries a tarball with an integrity hash. (#10) - npm publishing runs through the trusted publisher rather than a stored token, so releases carry provenance with no long-lived publish secret. (#8)
Publishes the AtomicHub fork as @atomichub/atomicmarket.
- The package name is
@atomichub/atomicmarket. Install it under that name and change imports from'atomicmarket'. (#1) - Deep imports such as
atomicmarket/build/API/Explorer/Paramsare replaced by root exports, for exampleimport { SaleApiParams } from '@atomichub/atomicmarket'. (#1) - Numeric ABI fields (
template_id, weights, splits) are numbers. The 64-bit id fields, sale ids and rule ids, remain strings. (#1) - Node.js 20 or newer is required. (#1)
- The package has no third-party runtime dependencies. The built-in
fetchreplaces node-fetch, and a customfetchcan still be injected. The only dependency is the sibling@atomichub/atomicassets. (#1) - Ships dual CJS and ESM output with bundled type declarations, plus a browser IIFE build. (#1)
- Adds the v2 royalty read endpoints
getRoyaltyConfig,getRoyaltyTemplateRulesandgetRoyaltyAttributeRules. (#1) - Adds
MarketActionBuilder, a synchronous builder for royalty-config actions, alongsideMarketActionGenerator, which covers the six royalty-configuration actions and their deletes. (#1) - Exports typed on-chain table rows and the
AtomicMarketActionsaction-name constants. (#1) - Exports the market API response-object, query-parameter and enum types from the package root, so no deep
build/imports are needed. (#1) - Adds
current_collection_feeto sales, auctions and buyoffers. (#1)
IBuyoffermatches what the publishedatomicmarket1.1.6 returns. It carriesbuyoffer_id,memoanddecline_memo, where it used to carryauction_idand neither memo field. (#1)
- Package name:
npm install @atomichub/atomicmarketand change imports from'atomicmarket'to'@atomichub/atomicmarket'. - Deep imports such as
atomicmarket/build/API/Explorer/Paramsare replaced by root exports:import { SaleApiParams } from '@atomichub/atomicmarket'. - Numeric ABI fields (
template_id, weights, splits) are numbers; 64-bit id fields (sale ids, rule ids) remain strings. - Node.js 20 or newer is required.
Fork of atomicmarket-js by pink.network, updated for the v2 AtomicMarket contract. Maintained by AtomicHub.
MIT licensed; see LICENSE for the full text including the original pink.network copyright.