Send an issuer its shareholder list after an auction or at month end
Produce one security's shareholder list at a record block, check that it adds up to total supply, and send the CSV, XLSX or PDF file to the issuer.
Overview
After each auction cycle settles, and on the last trading day of each month for securities that trade continuously, the settlement team sends each issuer the list of its shareholders and their settled holdings. DALP produces that list for one security at one record block. Every holder's balance in the list is the on-chain balance after that block.
The settlement team runs the export below and sends the file. For a security that trades by auction, the platform also produces the list itself and stores it for the issuing organization. The delivery section of this guide covers that path.
Before you start
- The security must have the historical-balances feature, attached when the security was created. DALP reads holders from the balance history that feature records. A security without it, a record block before the feature was attached, or a security that already had supply when the feature was attached is rejected, because its balance history is incomplete.
- You need a reporting role on the organization's system: admin, auditor, compliance manager or system manager. Scheduling a delivery needs more: you must be an owner of the issuing organization, or a platform administrator acting in it. Listing and downloading deliveries needs only the reporting role.
- For the Platform API, use an API key from the organization that holds the security. Send it as
X-Api-Key.
Choose when to run it
| Trading model | When to run | Record block |
|---|---|---|
| Auction | After every trade of the auction cycle is final. | The block that holds the cycle's last settlement, or any later block before the next trade of that security. |
| Continuous trading | On the last trading day of the month in the venue calendar, after the close, once the day's trades are final. | The last block of that trading day. DALP does not compute the last trading day; take it from the venue calendar. |
The record block must be final. DALP returns an error for a block later than the newest final block, so the list cannot change after you send it. Leave the record block out to use the newest final block. The answer names the block it used.
Export from the Console
- Open the security and go to Historical balances.
- Enter the record block in As-of block, or leave it empty for the newest final block.
- In Issuer holder list, read the record block and its time, the number of holders, the units held and the total supply. The section uses the class-neutral words holder and units because it also lists stablecoins and deposit tokens.
- Check that Adds up to total supply shows Yes.
- Select Download PDF, Download CSV or Download XLSX. The file is read at the record block shown above the buttons.
Export with the Platform API
Read the totals first:
curl "https://your-instance.example.com/api/v2/reporting/shareholder-lists/<tokenAddress>?atBlock=<recordBlock>" \
-H "X-Api-Key: <your api key>"data.reconciled is true when the holders' balances add up to data.totalSupply. data.recordBlock is the block the list is read at.
Then download the file at that same block. format is csv, xlsx or pdf:
curl "https://your-instance.example.com/api/v2/reporting/shareholder-lists/<tokenAddress>/download?atBlock=<recordBlock>&format=csv" \
-H "X-Api-Key: <your api key>" \
-o shareholder-list.csvPass atBlock on the download even when you left it out of the first request. The newest final block moves as the chain grows, and a fixed block keeps the file equal to the totals you checked.
Have the platform deliver the list to the issuer
For a security that trades by auction, skip the export and the handoff. The platform produces the list itself, stores the CSV, XLSX and PDF editions, and mails the issuing organization that the list arrived. Each delivery is bound to one record block.
What starts a delivery
Two triggers create a delivery. The platform opens one with trigger auction-settled once every auction trade of the security has settled and no fill is still waiting to settle, at the highest settled fill block. This covers auction-only markets with no holding period. You open one with trigger scheduled when you post a record block through the Console or the Platform API.
A delivery stays pending until its record block is final. A scheduled pass produces each delivery whose block is final. One record block holds one delivery: posting a block that already has one returns that delivery, whichever trigger created it. A delivery whose holders' balances do not add up to total supply also stays pending: the pass records an attempt and a last error, writes no files, sends no mail, and writes no outcome audit until the projection reconciles.
Schedule from the Console
- Open the security and go to Historical balances.
- Enter the record block in As-of block.
- In Issuer deliveries, select Deliver to issuer.
The table shows each delivery with its record block, trigger, status, delivered time, holder count and downloads. A pending row reads Waiting and refreshes while the pass produces it. A delivered row offers one download per format. A refused row names the reason.
Schedule with the Platform API
Post the record block as a string:
curl -X POST "https://your-instance.example.com/api/v2/reporting/shareholder-lists/<tokenAddress>/deliveries" \
-H "X-Api-Key: <your api key>" \
-H "Content-Type: application/json" \
-d '{"recordBlock": "1200"}'The platform answers with the delivery. A block past the newest final block stays pending until the block is final.
A security your organization does not hold answers 404 with code reporting.register.SHAREHOLDER_LIST_NOT_FOUND. That answer covers a missing security, a security of another organization, and a security with no resolvable issuer. Your organization learns nothing about other organizations from it.
List deliveries
curl "https://your-instance.example.com/api/v2/reporting/shareholder-lists/<tokenAddress>/deliveries?offset=0&limit=50" \
-H "X-Api-Key: <your api key>"The newest record block comes first. limit caps at 200. meta.total counts every delivery of the security in your organization:
{
"data": [
{
"id": "0199b2d1-8a19-7a08-8d21-4d33e3c1a111",
"chainId": 1,
"tokenAddress": "0x0000000000000000000000000000000000000001",
"recordBlock": "900",
"trigger": "auction-settled",
"status": "delivered",
"requestedBy": null,
"attempts": 1,
"lastError": null,
"createdAt": "2026-10-10T08:05:00.000Z",
"settledAt": "2026-10-10T08:10:00.000Z",
"holderCount": 12,
"refusal": null,
"notice": "sent",
"noticeAttempts": 1,
"parts": {
"csv": {
"objectKey": "shareholder-lists/org_a/0199b2d1-8a19-7a08-8d21-4d33e3c1a111.csv",
"digest": "9f2c4a7e1b5d83f06a2c9e4b7d105f3a8c6e2d4b9f105a3c7d2e6b8a1f4c5d6",
"contentLength": 1840
},
"xlsx": {
"objectKey": "shareholder-lists/org_a/0199b2d1-8a19-7a08-8d21-4d33e3c1a111.xlsx",
"digest": "41bd9c0e77f2a5d18c3e6b0a4f9d2c7e5b8a1f306d4c5e9b8a7f1c2d3e4a5b6c",
"contentLength": 6231
},
"pdf": {
"objectKey": "shareholder-lists/org_a/0199b2d1-8a19-7a08-8d21-4d33e3c1a111.pdf",
"digest": "c7e30bd4a19f6c2d5e8b40a173f5c9d6e2b80417f6a3c5e9d0b2f486a1c7d3e5",
"contentLength": 12904
}
}
}
],
"meta": { "total": 1 }
}Download a stored edition
curl "https://your-instance.example.com/api/v2/reporting/shareholder-lists/<tokenAddress>/deliveries/<deliveryId>/download?format=csv" \
-H "X-Api-Key: <your api key>" \
-o shareholder-list-900.csvformat is required: csv, xlsx or pdf. The platform returns the stored bytes. A download of a pending delivery answers 503, as does a file whose bytes no longer match the recorded digest or length. A refused delivery never becomes available: a security-not-found or not-issuer refusal answers 404, and a feature or history refusal answers 422 with code reporting.register.SHAREHOLDER_LIST_REFUSED. A delivery of another organization answers 404.
How the issuer learns that the list arrived
The platform emails every owner and admin of the issuing organization when a delivery is delivered or refused. The mail names the security, the record block and the trigger, states the reason when refused, and links to the security page in the Console. It carries no holder data.
A notice that fails to send retries on later passes after a backoff, up to three attempts, then stays failed. The wait is 10 minutes after the first failure and 20 minutes after the second. A retry only emails the owners and admins who have not yet received that notice; recipients who already received it are not emailed again. When mail is not configured, the notice stays not-configured. If the platform crashes after the mail transport accepts a notice but before that acceptance is recorded, the next pass may email that recipient again: delivery is at-least-once per recipient, not exactly-once.
Audit and retry
Each delivered delivery, refused delivery, sent notice and failed notice writes an operations audit row for the issuing organization. You read those rows through the reporting audit trail. The schedule call and each download are audited as API calls.
A short-lived failure leaves the delivery pending with an attempt count and a last error, and a later pass retries it after a backoff. The wait starts at 5 minutes and doubles with each attempt, up to 24 hours. An unreconciled list waits the same way: it stays pending with an attempt and a last error, and a later pass delivers it once the holders add up to total supply. A refused delivery is terminal and never retries.
A stored record that does not match its expected shape is skipped and counted while the pass continues with healthy records. A rejected delivery row is not generated or emailed, and a rejected audit event is not acknowledged; valid events from the same row can still be recorded. A user read that needs a rejected row answers temporarily unavailable instead of serving partial data. An operator can repair the stored record so a later pass can process it.
When a delivery is refused
refusal | What it means | What to do |
|---|---|---|
feature-not-attached | The security had no historical-balances feature at the record block. | See the matching row under the on-demand refusals. |
feature-detached | The historical-balances feature was detached at or before the record block. | Choose a record block before the detachment block. |
enablement-after-record-block | The feature was attached after the record block. | Choose a record block at or after the attachment block. |
history-incomplete | The security had supply before the feature was attached. | No record block can be delivered. |
security-not-found | The security was missing at the record block. | Check the address and the block. |
not-issuer | The organization no longer issues the security. | No block can be delivered to it. |
A block past the newest final block never refuses a delivery. The delivery waits as pending. An unreconciled list never refuses either; it waits as pending until the projection reconciles.
Systems that organizations share
The platform names the issuing organization from the system the security lives in. When one organization holds the system, that organization is the issuer. When organizations share the system, the recorded issuer is the issuer only while it still holds the system. A missing recorded issuer, or one that no longer holds the system, names no issuer: the security gets no auction delivery, and scheduling answers not found.
What the file contains
The CSV and XLSX files have one row per wallet that holds the security after the record block, largest holding first. A wallet with a zero balance is left out. The PDF shows the same holders with the network and its chain ID, the record block and its time, and the reconciliation result. Its holders table ends with a totals row: the number of holders, the units they hold, and the share of supply those units are.
The columns follow the shareholder response of Commission Implementing Regulation (EU) 2018/1212, Annex Table 2, and the register of members of the ADGM Companies Regulations 2020, section 118. They hold the fields the chain records.
| Column | Content | Table 2 field | Section 118 entry |
|---|---|---|---|
security | The security's contract address, in place of an ISIN. | A.4 | Shares held, by class |
symbol | The security's symbol. | ||
recordDate | The date of the record block, as YYYYMMDD in UTC. | A.5 | |
recordBlock | The record block. | ||
account | The holder's wallet, the account the shares are held in. | B.7 | |
identity | The holder's registered OnchainID identity. Empty when the wallet has no registered identity. | C.1 | Member |
country | The holder's registered country as an ISO 3166-1 alpha-2 code. | C.6 | |
shares | The holder's balance in the security's own units. | C.11 | Shares held |
percentOfSupply | The holder's share of total supply, cut to four decimal places and not rounded, so the column never adds up to more than 100. |
In the XLSX file, recordDate and percentOfSupply are numbers. recordBlock is a number when it fits spreadsheet precision exactly, and text otherwise, so the block identifier cannot change. shares is a number only when every holder's balance and their sum fit a spreadsheet number exactly, which is 15 significant digits. Otherwise the whole column is text, so that no amount is rounded. A spreadsheet total of a text column is 0, so check the column type before you add it up. The CSV file holds text in every column.
The file does not hold names or postal addresses. The issuer matches each identity to the member's name and address in its own records.
Check the list before you send it
- Adds up to total supply must be Yes, or
reconciledmust betrue. A No means a holder is missing from the list. Do not send it; contact support with the security and the record block. - The number of holders should match the investors you expect after the auction or month.
Send the list
Send the CSV or XLSX file, and the PDF as the record of what was sent, through the channel agreed with the issuer. Keep the security address and the record block with the sent file, so that the same list can be produced again.
When DALP cannot produce the list
A refused list returns DALP-0753 with a reason in data.reason:
data.reason | What it means | What to do |
|---|---|---|
feature-not-attached | The security had no historical-balances feature at the record block, so there is no balance history to list. | For a security created without the feature, none: DALP attaches the feature only when it creates a security, so issue new securities with it. For a security whose feature was detached, choose a record block from before the detachment. |
feature-detached | The feature was detached at data.boundaryBlock; the record block is at or after it. | Choose a record block before data.boundaryBlock. |
enablement-after-record-block | The feature was attached after the record block. data.boundaryBlock is the block it was attached at. | Choose a record block at or after data.boundaryBlock. |
history-incomplete | The security had supply before the feature was attached, so the balance history is incomplete. data.boundaryBlock is the block the feature was attached at. | No record block can be listed. |
record-block-not-final | The record block is later than the newest final block, data.boundaryBlock. | Wait until the block is final, or leave the record block out. |