ANS delivers Apple Push Notifications for the ADAMANT iOS app without ANS or Apple ever learning who is messaging whom. It was the first of the ADAMANT Services.
Status: this service is legacy and is planned to be succeeded by adamant-ns. Until that migration is complete, ANS keeps running in production, and this repository receives targeted maintenance rather than new features.
ADAMANT Blockchain and Messenger apps are fully functional without ANS and other Services — the goal of ADAMANT Services is to provide features that cannot be implemented on the blockchain itself. More on adamant.im.
Delivering a notification privately and securely involves 4 parties:
- The user's device (e.g. an iPhone)
- ADAMANT's blockchain
- Apple Push Notification Service (APNs)
- This application, ADAMANT Notification Service (ANS)
The workflow:
- The user's device sends an encrypted signal message with a unique device token to an ADAMANT blockchain node. The recipient is ANS's own ADM address. See AIP-6: Signal Messages.
- ANS polls the blockchain and decrypts the device token.
- ANS polls the blockchain and filters transactions where a registered device's address is the recipient. For each match, ANS asks APNs to deliver a notification to that device's token — the notification carries only a transaction id, never message contents.
- APNs notifies the user's device.
- The device holds the secret key needed to fetch and decrypt the actual message, entirely locally.
The user's device never communicates with ANS directly, and ANS never learns the device's IP or other network identity — all communication happens through blockchain nodes.
This repository builds two independent console workers plus a set of shared libraries:
-
ANSSignalsRegistration — polls ADAMANT blockchain nodes for new service signal transactions and registers/unregisters device tokens. Signal payloads are JSON, encrypted the same way as any other chat transaction:
{ "token": "DeviceToken", "provider": "apns", "action": "add" }token: the device's push tokenprovider: push service provider —apnsfor release builds,apns-sandboxfor debug builds (not yet supported)action(optional):add(default) registers a device,removeunregisters it
-
ANSPollingWorker — polls ADAMANT nodes for new transactions, checks whether the recipient has a registered device, and sends a push notification through APNs when it does.
- .NET 8 SDK (LTS) to build and run
- Ubuntu 22.04 LTS or newer (any platform .NET 8 supports works; Ubuntu 22.04+ is what production targets)
- MySQL or SQLite for the device-token database
git clone https://github.com/Adamant-im/adamant-notificationService.git
cd adamant-notificationService
dotnet restore-
Copy the sample
config.jsonfrom the repository root, edit connection strings, nodes, delays, and certificate paths, and save it to{UserHomeDirectory}/.ans/config.json. See Configuration. -
On first launch, each worker automatically applies pending database migrations.
-
To run ANSPollingWorker, you need an Apple Push Authentication Key from your Apple Developer account (see Configuration for
ApplePusher:Keys):cd ANSPollingWorker dotnet run -
To run ANSSignalsRegistration, set your ANS account's address and private key in the config, then:
cd ANSSignalsRegistration dotnet run -
For a deployable build:
dotnet publish -c Release -r linux-x64 --self-contained -o {output path}See Microsoft's docs on runtime identifiers and
dotnet publishfor other targets.
To point your own iOS ADAMANT build at your own ANS server:
-
Register a regular ADAMANT (
U) account for ANS. -
In the iOS app's source, set your ANS account's address and public key in the
AdamantResourcesstruct. -
In the ANS config, set your ANS account's address and private key. See Configuration.
-
Create a
.pfxcertificate with an ECDSA private key from your Apple Developer Auth Key:openssl req -new -x509 -key key.p8 -out selfsigned.cer openssl pkcs12 -export -in selfsigned.cer -inkey key.p8 -out cert.pfx
Put the resulting
.pfxin~/.ansand reference it from the config. -
Done — the iOS app sends device tokens to your ANS account, ANSSignalsRegistration polls signal transactions and registers them, and ANSPollingWorker polls new transactions and notifies registered devices.
The sample configuration file lives at the repository root. Both workers load their config from ~/.ans/config.json, so a single file covers both.
-
Database(optional): database configuration.ConnectionString(optional, default:devices): the name of the connection string to use, defined inConnectionStringsbelowProvider(optional, default:mysql):sqliteormysql
-
ConnectionStrings: standard .NET connection string section. The active entry is named byDatabase:ConnectionString(defaultdevices). -
Api: ADAMANT node settings.Server[]: node addressesip(string): node hostname or IPprotocol(string, optional, default:https)port(int, optional)
-
PollingWorker: polling settings for ANSPollingWorker.Delay(int, milliseconds, optional, default:2000): interval between polling requestsNlogConfig(string, optional, default:nlog.config): path to the NLog configuration fileStartup(enum, optional, default:database): startup modedatabase: resume from the last height stored in the database; falls back tonetworkmode if none is storednetwork: fetch the blockchain's current height and start from thereinitial: start from height 0
-
SignalsRegistration: settings for ANSSignalsRegistration.Delay(int, milliseconds, optional, default:2000): interval between polling requestsNlogConfig(string, optional, default:nlog.config): path to the NLog configuration fileAddress(string, required): ANS's own ADM account address, used to poll signal transactionsPrivateKey(string, required): ANS's own ADM account private key, used to decrypt signal transactionsStartup(enum, optional, default:database): same options asPollingWorker:Startup
-
ApplePusher: APNs settings.Keys— the token-based auth path actually used at runtime:keyId(string): your developer key id, from the Auth Keys pageteamId(string): your Apple team id, from Membership DetailsbundleAppId(string): your application's bundle idpfxPath(string): path to a self-signed.pfxcertificate containing an ECDSA private keypfxPassword(string): the certificate's password
Certificate— legacy.p12-based path; present for historical reasons only, not read by the current build:path(string): path to an APNs.p12certificatepass(string): the certificate's password
Payload[]: push notification content, keyed by transaction typetransactionType:0for an ADM token transfer,8for chat transactions and coin transferstitle,body,sound
- ANS does not use third-party services to deliver notifications; tokens and addresses never leave ADAMANT infrastructure and Apple's own servers.
- ANS cannot read a transaction's message contents and cannot include decrypted content in a push payload — decryption requires a secret key that only the user's device holds.
- Since version 0.4, pushes carry only a
txn-id. The client app fetches the transaction from a blockchain node and decrypts it on-device, using a passphrase stored in the Keychain and handled by aNotificationServiceExtension. - Device tokens are unique per app install; they cannot be used to identify a user elsewhere. A new token is generated on every reinstall or notification re-enable, and a stale token in ANS's database simply stops working the next time APNs rejects it.
- The push badge is always
1when there's any unread message, never an exact unread count — iOS badges are set by the server, and ANS deliberately doesn't track how many messages a device hasn't read.
For background on APNs itself, see Apple's docs.
dotnet restore
dotnet build
dotnet test