Assetera Docs
Smart contracts

Primary issuance

How a buyer's first acquisition of an asset settles on Assetera: offerings, the AsseteraPrimarySales router, and what an integrator actually has to build.

Primary issuance is a buyer's first acquisition of an asset: the units come into existence (or come out of an issuer's allocation) at the moment of purchase. It settles through AsseteraPrimarySales, a contract that is deliberately separate from the trading venue. Once a buyer holds units, every later trade is a secondary trade on AsseteraECS, which is a different contract with its own address, its own roles and its own events.

Primary versus secondary, in one table

Primary issuance (AsseteraPrimarySales)Secondary trading (AsseteraECS)
Who is on the other sidethe offering itself: an issuer, or a third-party supplieranother user, through their standing order or targeted offer
Where the units come fromminted or allocated at purchase timeescrowed by the maker before the trade
What the buyer namesan offering and an amount of settlement currencyan order id or an offer id
Price discoverynone: the offering has a pricethe maker sets a limit price
Book on chainnoneorder book plus targeted-offer book
Feetaken from the buyer, in the settlement currencymaker and taker fees, in the pair's settlement currency
Entry pointsettlePrimaryplaceOrder, fillOrder, makeOffer, acceptOffer

The two contracts share a shape (upgradeable proxy, gasless relaying, EIP-712 attestations) and share nothing else. They have separate addresses, separate pause levers, separate fee-collector allowlists and separate EIP-712 domains, so an attestation minted for one is rejected by the other.

What an offering is

An offering is one instrument being sold at a price, on one chain, in one settlement currency. On chain it is an address: the venue the router settles against.

There are two kinds, and the router cannot tell them apart because there is nothing to tell apart. To AsseteraPrimarySales a venue is an address in a signed instruction, whichever kind it is.

Kind of offeringThe venue isWho holds the units before the sale
A third-party assetthat supplier's own settlement contractthe supplier
Assetera's own issuancean AsseteraIssuanceVenue, deployed one per offeringnobody: the units are minted into existence at purchase

Assetera's own issuance is documented on its own page, because the price lives there and so does the one arithmetic trap worth reading before you integrate: see Issuance venue.

The flow, end to end

What you call, and in what order

Ask the platform for a settlement. You supply the offering and the amount. You get back a SettlementIntent (fourteen named fields: the buyer, the asset, the delivery floor, the currency, the quote, the fee, the buyer's spend cap, the venue, the calldata binding, a nonce and a deadline), the opaque venue calldata, and three signatures the platform produced: the settlement operator's, the compliance attestation and the fee attestation.

The concrete endpoints that produce this bundle are on Primary sale API. Everything below the API boundary is on this page and on Settlement router.

Let the buyer authorise the spend, for exactly this settlement. The amount is venueQuoteIn + buyerFee and nothing more. Assetera never asks a buyer for an unlimited allowance on this path, because the sum of live allowances is the real ceiling on what a settlement key could ever move.

Prefer a permit, and treat a separate approve as the fallback:

Settlement currencyHow the buyer authorisesTransactions
Supports ERC-2612 permita signature, carried into the settlement by permitAndCallone
No permit supportan approve for venueQuoteIn + buyerFee, sent firsttwo

Why the one-transaction path is the default, and not just a gas saving

An approve takes 15 to 30 seconds to mine. A firm quote is only good for a short execution buffer, so a two-transaction purchase can lose the race against its own quote and fail at the delivery floor. A permit is a signature and costs no wall-clock time at all.

permitAndCall adds no new selector and no second code path: it is generic over the call it wraps and re-enters settlePrimary itself, so the same four signatures, the same nonce namespaces and the same guards apply either way. Take its exact argument list from the ABI in the TypeScript SDK rather than from this page, and read the mechanism in contracts/src/core/PermitRelay.sol.

Have the buyer sign the intent. It is ordinary EIP-712 typed data with named fields, so a wallet renders it as a readable payload rather than as opaque bytes. Both EOA wallets and contract wallets (Safe and embedded smart accounts, via ERC-1271) are accepted on the same terms.

Submit settlePrimary. Signature:

function settlePrimary(
  bytes calldata venueCalldata,
  SettlementIntent calldata intent,
  bytes calldata intentSignature,
  bytes calldata buyerSignature,
  KycAttestation calldata kyc,
  FeeAttestation calldata fee
) external;

The call is relayed through the same trusted forwarder AsseteraECS uses, so the buyer pays no gas and needs no native currency. The buyer is resolved from the meta-transaction, and intent.buyer must equal that resolved actor: nobody settles on somebody else's behalf.

Read the result from the event, not from a return value. settlePrimary returns nothing. The outcome is the PrimarySettled log, and every amount on it is a balance delta the router measured after the venue ran, not a number the venue reported.

What you do not have to build

You might expect to buildWhy you do not
A compliance check before the buyThe router refuses to settle without a valid compliance attestation, and its gate is closed by default for every action ordinal, including ones that do not exist yet.
Fee arithmeticThe fee is attested in basis points by one signer and as an absolute amount by another, and the contract refuses the settlement unless the two agree.
Slippage protectionThe buyer signs a delivery floor. Fewer units than that floor reverts the whole transaction rather than filling badly.
Refunding an over-quoteIf the venue takes less than it was approved, the difference goes back to the buyer in the same transaction.
A per-supplier log decoderOne event, PrimarySettled, with the same fields whoever the venue was.
Gas for the buyerThe call is relayed (ERC-2771). The buyer holds no native currency.
A "did it really deliver" checkThe router asserts the buyer's measured asset balance grew by at least the signed floor, and asserts its own balances returned to where they started, on both tokens.

Where the money and the units go

For one settlement, with venueQuoteIn the venue's firm quote and buyerFee Assetera's fee:

LegAmountDestination
Debited from the buyerat most venueQuoteIn + buyerFee, never more than the signed maxSettlementInthe router, for the length of one transaction
Approved to the venueexactly venueQuoteIn. The fee is never inside the approvalthe venue, which may consume less
RefundedvenueQuoteIn minus what the venue actually consumedback to the buyer, same transaction
FeebuyerFee, in the settlement currency, charged on top of the quotean allowlisted fee collector
The assetat least the signed minAssetOut, measuredthe buyer
Left in the router afterwardszero, on both tokens, asserted(nothing)

The buyer pays the fee, in the currency, on top

The fee is a buyer-side fee only. An issuer-side fee cannot be charged here at all: the router never controls the proceeds side of a settlement, so attesting one reverts rather than silently doing nothing. For Assetera's own issuance the proceeds sit in the offering's venue and the issuer withdraws them from there.

Addresses are per chain, and you should never type one

Both contracts are deployed per chain and their addresses have already moved once. Resolve them from the published @asseteragmbh/evm-contracts package, which ships a deployment record per chain alongside the ABIs:

import {
  getEcsAddress,
  getPrimarySalesAddress,
  getDeployment,
} from '@asseteragmbh/evm-contracts';

const ecs = getEcsAddress(chainId);            // secondary trading
const router = getPrimarySalesAddress(chainId); // primary issuance
const record = getDeployment(chainId);          // the whole record, for a non-TS consumer

getPrimarySalesAddress returns undefined on a chain whose deployment record predates the router. Treat that as "primary issuance is not available on this chain", not as an error to retry. A venue address is not in the deployment record at all: an offering is catalog data, deployed one per offering, so it reaches you through the catalog and through the signed intent.

Next

On this page