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 side | the offering itself: an issuer, or a third-party supplier | another user, through their standing order or targeted offer |
| Where the units come from | minted or allocated at purchase time | escrowed by the maker before the trade |
| What the buyer names | an offering and an amount of settlement currency | an order id or an offer id |
| Price discovery | none: the offering has a price | the maker sets a limit price |
| Book on chain | none | order book plus targeted-offer book |
| Fee | taken from the buyer, in the settlement currency | maker and taker fees, in the pair's settlement currency |
| Entry point | settlePrimary | placeOrder, 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 offering | The venue is | Who holds the units before the sale |
|---|---|---|
| A third-party asset | that supplier's own settlement contract | the supplier |
| Assetera's own issuance | an AsseteraIssuanceVenue, deployed one per offering | nobody: 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 currency | How the buyer authorises | Transactions |
|---|---|---|
Supports ERC-2612 permit | a signature, carried into the settlement by permitAndCall | one |
No permit support | an approve for venueQuoteIn + buyerFee, sent first | two |
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 build | Why you do not |
|---|---|
| A compliance check before the buy | The 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 arithmetic | The 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 protection | The buyer signs a delivery floor. Fewer units than that floor reverts the whole transaction rather than filling badly. |
| Refunding an over-quote | If the venue takes less than it was approved, the difference goes back to the buyer in the same transaction. |
| A per-supplier log decoder | One event, PrimarySettled, with the same fields whoever the venue was. |
| Gas for the buyer | The call is relayed (ERC-2771). The buyer holds no native currency. |
| A "did it really deliver" check | The 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:
| Leg | Amount | Destination |
|---|---|---|
| Debited from the buyer | at most venueQuoteIn + buyerFee, never more than the signed maxSettlementIn | the router, for the length of one transaction |
| Approved to the venue | exactly venueQuoteIn. The fee is never inside the approval | the venue, which may consume less |
| Refunded | venueQuoteIn minus what the venue actually consumed | back to the buyer, same transaction |
| Fee | buyerFee, in the settlement currency, charged on top of the quote | an allowlisted fee collector |
| The asset | at least the signed minAssetOut, measured | the buyer |
| Left in the router afterwards | zero, 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 consumergetPrimarySalesAddress 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
Settlement router
AsseteraPrimarySales in detail: what is signed and by whom, the nonce namespaces, the constrained executor, and the per-currency cap.
Issuance venue
AsseteraIssuanceVenue: one contract per offering, the price semantics, and the decimals trap.
AsseteraECS
The secondary market: the order book and the targeted-offer book.
TypeScript SDK
ABIs, per-chain addresses and typed accessors, generated from the Solidity.
Offer lifecycle
The targeted offer and counter-offer negotiation flow in AsseteraECS: who may respond, what each transition escrows, and the states an offer can end in.
Settlement router
AsseteraPrimarySales in detail: the settlement intent, four signatures over three nonce namespaces, and a constrained executor judged on measured balance deltas.