Only this pageAll pages
Powered by GitBook
1 of 24

Docs

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Tutorials

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Related pages

Atsign Docs

Welcome to the Atsign Docs

Learn

Some great places to start learning more about the technology

Tutorials

Alternatively, learn how to work directly with our tech through some of our tutorials

Core Technology

The atSDK

Getting started with atSDK with Dart

atRecord

The format atServers use to store and share data.

At Atsign, whenever we mention the word "key" we are normally talking about a cryptographic key.

The main exception being the atKey, this is the "key" of a "key value pair" that makes up every atRecord.

It's unfortunate that the word "key" is polymorphic in computer science, and we have tried in the past to move to other words, but we have decided to stick with keys and the developer will have to understand the context of cryptographic key or key value pair (sorry!)

atRecords are the data records that are stored by the atServers. We use the common key-value pair format.

atKey

An atKey is the identifier half of the "key-value" pair. Similar to the primary key of a tabular database, the atKey must be a unique string which represents the data.

There are 5 different types of an atKey.

Type
Purpose

Structure of an atKey

Cached

A marker for whether it is a cached key or not.

Visibility Scope

A marker for who has access to the key.

Record ID

A unique string used to represent the atRecord.

Owner's atSign

The owner (i.e. creator's) atSign for that particular atRecord. The shared by atSign of an atRecord is synonymous to the owner atSign of an atRecord.

Rules for atKeys
  1. Length of an atKey should not be more than 240 characters (a limitation of the current implementation of the atServer, not a protocol limitation)

  2. A maximum of 55 7-bit characters for the atSign (unicode is translated to UTF-7)

Reserved atKeys

The following is a list of reserved atIKeys which the atServer requires to function.

Don't try to delete or overwrite these keys, the atServer cannot function without them.

  • privatekey:at_pkam_privatekey

Example atKeys

Public atKey

  1. A public atKey with a record id of location shared by @alice. This atKey typically holds public data that any atSign can access.

public:location@alice

atMetadata

Metadata of the atRecord is also stored and describes the following properties of the atValue.

atValue

Text or binary values can be saved in an atServer.

Small objects are fine to use the atServer but large objects should be used by reference.

For example, derive a new encryption key, encypt a file, upload that file to location, then notify other atSigns of the location and the encyption key. This is how works.

The size of the value saved in an atServer is bound by the atProtocol's config parameter "maxBufferSize".

atPlatform

An overview of Atsign's core pillars of technology

TL;DR

The atPlatform allows people, entities and things to communicate privately and securely without having to know about the intricacies of the underlying IP network. The atProtocol is the application protocol used to communicate and atSigns are the addresses on the protocol. All cryptographic keys are cut at the edge by the atSign owner, meaning only the receiving and sending atSigns see data in the clear.

The atPlatform can be used to send data synchronously or asynchronously and can be used as a data plane or a control plane or both simultaneously at Internet scale.

Cutting your atSigns keys

It takes two to tango!

To run through the tutorial you will need at least two atSigns, so you can send and receive. You can get your atSigns for free at or if you like purchase some that are more personal to you.

Once you have you atsigns you are ready to activate them, which means spinning up your atServer per atSign and cutting your cryptographic keys for each atSign. Sounds complicated but it is easy ! In fact just a single command. In the terminal window type:

You will get an error message telling you to add the -a flag and your atsign, so if you atSign was @crunchfrog you would type:

*Note on windows the atSign needs to be in double quotes so the shell does not get confused with the @ symbol. Below you can see the activation process for two atSigns @energetic22 and @7capricon. Once the command is run you will get and email with the one time password, enter that and the program will create an atKeys file for that atSign. Any atKeys files you create will be located in your home directory then .atsign/keys.

These keys are important to be kept safe, as they are the only keys to your atSign and your data. Talking of data lets send some between the two atSigns next.

Allowed characters in an entity are: [\w._,-"']

  • Namespace is mandatory in the current implementation of the protocol i.e entity must follow the notation: <identifier>.<namespace>

  • Cached atKeys should have a different owner than the current atSign

  • Visibility scope and owner cannot be the same for a shared atKey

  • Reserved atKeys cannot be modified or notified

  • For newly created atKeys, the owner must match the current atSign

  • privatekey:at_pkam_publickey

  • public:publickey

  • privatekey:privatekey

  • shared_key

  • privatekey:self_encryption_key

  • signing_privatekey

  • public:signing_publickey

  • privatekey:at_secret

  • privatekey:at_secret_deleted

    1. A public atKey with a record id of publickey shared by @bob. Note that this is a reserved atKey.

    public:publickey@bob

    Private atKey

    1. A private atKey with a record id of pk1 shared by @alice.

    privatekey:pk1@alice

    Shared atKey

    1. A shared atKey with a record id of phone shared with @bob, shared by @alice.

    @bob:phone@alice

    1. A shared atKey with the record id of name, a namespace of wavi, shared with @alice, and shared by @bob.

    @alice:name.wavi@bob

    Internal atKey

    1. An internal atKey with a record id of _latestnotificationid, namespace of at_skeleton_app and is shared by @alice.

    _latestnotificationid.at_skeleton_app@alice

    Cached atKey

    1. A cached atKey with a record id of phone, shared with @bob, and is shared with @alice.

    cached:@bob:phone@alice

    Date and time when the key has been created.

    expiresOn

    Yes

    A Date and Time derived from the ttl (now + ttl). A Key should be auto deleted once it expires.

    isBinary

    No

    True if the value is a binary value.

    isCached

    No

    True if the key is cached.

    isEncrypted

    No

    True if the value is encrypted.

    refreshAt

    No

    A Date and Time derived from the ttr. The time at which the key gets refreshed.

    sharedWith

    No

    atSign of the user with whom the key has been shared. Can be null if not shared with anyone.

    updatedOn

    Yes

    Date and time when the key has been last updated.

    ttb

    No

    Time to birth in milliseconds.

    ttl

    No

    Time to live in milliseconds.

    ttr

    No

    Time in milliseconds after which the cached key needs to be refreshed. A ttr of -1 indicates that the key can be cached forever. ttr of 0 indicates do not refresh. ttr of > 0 will refresh the key. ttr of null indicates the key is impossible to cache, hence, refreshing does not make sense (which has the same effect as a ttr of 0).

    Public

    Store and share public data which can be seen by anyone.

    Self

    Store data which can only be seen by the owner.

    Shared

    Store and share private data which can only be seen by the owner and intended recipient.

    Private

    Store data which can only be seen by the owner, hidden by default.

    Cached

    Cache shared data from other atSigns for performance and offline mode.

    Meta Attribute

    Auto create?

    Description

    availableFrom

    Yes

    A Date and Time derived from the ttb (now + ttb). A Key should be only available after availableFrom.

    ccd

    No

    Indicates if a cached key needs to be deleted when the atSign user who has originally shared it deletes it.

    createdBy

    Yes

    atSign that has created the key

    createdOn

    atmospherePro

    Yes

    dart run .\bin\at_activate.dart 
    dart run .\bin\at_activate.dart -a "@crunchyfrog"
    atsign.com
    [cached:]<visibility scope>:<record ID><owner’s atSign>
    Relationships

    Every atServer is associated with one atSign, and each atServer stores many atRecords.

    When provided an atSign, the atDirectory returns a DNS address and port number for its atServer.

    The atProtocol is the application layer protocol used to communicate with an atServer.

    atServer

    An atServer is both a personal data service for storing encrypted data owned by an atSign, and a rendezvous point for information exchange. An atServer is responsible for the delivery of encrypted information to other atServers, from which the owners of those atSigns can then retrieve the data.

    Unless explicitly made public, atServers only store encrypted data and do not have access to the cryptographic keys, nor the ability to decrypt the stored information.

    atServer Functionality
    • Cryptographic authentication of client devices.

    • Cryptographic authentication of other atServers.

    • Persistence of encrypted data on behalf of the controlling atSign.

    • Caching of data shared by others with the controlling atSign.

    • Notification of data change events to clients (edge devices) and other atServers to facilitate delivery of information shared with them.

    • Synchronization of data with multiple clients (edge devices).

    • TLS wire encryption from clients to atServers using SSL certificates.

    • Mutually authenticated TLS 1.2/1.3 wire encryption between atServers using SSL certificates.

    atDirectory

    In order for an atSign to communicate with another one on the internet, we need to locate the atServer that can send and receive information securely on its behalf.

    The location of an atServer is found using the atDirectory service (root.atsign.org:64). This directory returns the DNS address and port number of the atServer for any atSign that it has a record for. The atDirectory service contains no information about the owner of the atSign.

    atProtocol

    The atProtocol communicates via layer 7, the application layer of the OSI model, over TCP/IP.

    The atProtocol is an application protocol that enables data sharing between atSigns. You can learn more about the atProtocol by reading the . The atProtocol uses TCP/IP and TLS but does not specify how data itself is encrypted, that is the job of the atSDK and atClient libraries.

    atSDK

    atSDKs provide developers with atPlatform specific building tools in a number of languages and for a number of operating systems and hardware. The atSDK allows developers to rapidly develop applications that use the atPlatform.

    Diagram of Atsign's Core TechnologyDiagram of Atsign's Core Technology

    atSign

    A unique identifier which serves as the address of the atServer

    What is an atSign?

    An atSign (e.g. @alice) is simply a resolvable address for an atServer. Anything can have an atSign, a person, entity, or thing (IoT), even individual songs or videos could have atSign addresses.

    Synchronization

    Flutter / Dart

    SyncService

    SyncService syncs the client app and remote secondary server’s changes:

    atSDK

    Get to know Atsign's SDK, the atSDK

    Overview

    The atSDK is the best way to embed the atProtocol into new or existing software. This can be anything from a graphical desktop application to firmware flashed on a microcontroller.

    Sections

    Dart atSDK Walkthrough

    Get hands-on with the atSDK. Simple examples to kickstart your development.

    This guide is primarily targeted towards individuals who want to learn how to build backend / cli based Dart applications with the atSDK. Some of this content will also be applicable to Flutter developers (but not all).

    Table of Contents

    What's it used for?

    An atSign is used to protect and securely exchange information with other atSigns without any chance of surveillance, impersonation, or theft of the information by anyone.

    What characters can be used in an atSign?

    An atSign supports any combination of Unicode UTF-8 characters that are translated to UTF-7 and must have less than characters 55 characters in length. This provides an enormous name space of 10^224 atSigns.

    How do I get an atSign?

    Head to the registrar site. It is recommended that you login with your email. One email can hold up to 10 free atSigns and unlimited paid atSigns.

    How do I generate my associated cryptographic keys?

    There are multiple ways to generate the associated .atKeys file for an atSign. Within applications the .atKeys are stored in encrypted keychains that the OS provides. Using the command line .atKeys files are produced and put in the directory ~/.atsign/keys.

    Most applications have a way to export the .atkeys to a file if you need to keep them safe or use them on another device.

    • Using atmospherePro (3 minute video)

    • Dart at_onboarding_cli/at_activate to activate an owned atSign or at_onboarding_cli/at_register to generate a new free atSign.

    • Java Registration CLI

    Paid atSigns

    You can purchase custom atSigns from the registrar site.

    Using the atSDK with Dart
    Get sample code
    Cutting your atSigns keys
    Put and Get data asynchronously
    Send and Receive data synchronously.
    Remote Procedure Calls (RPC)
    atTalk - Encrypted chat client
  • If the client app’s changes are ahead, it pushes the changes to the remote secondary.

  • If the remote secondary is ahead, it pulls the changes to the client app.

  • Usage

    To trigger a sync, call the .sync function:

    AtClientManager atClientManager = AtClientManager.getInstance();
    SyncService syncService = atClientManager.syncService;
    syncService.sync();

    Send and Receive data synchronously.

    Send me a txt!

    Sending and receiving data in near real-time

    Sending data as we saw is pretty easy but the receiver would have to continually poll unless we had a mechanism to notify the receiving atSign that they had data to deal with. Fortunately we have just that with atNotifications.

    A notification can be sent to an atSign and if that atSign is listening for it, it will create an event that can be handled as soon as the notification is received.

    To receive we can use code like this:

      atClient.notificationService
          .subscribe(regex: 'message.$nameSpace@', shouldDecrypt: true)
          .listen(((notification)

    And to send code like this:

      await atClient.notificationService.notify(
          NotificationParams.forUpdate(sharedRecordID, value: message));

    There are two examples in the repo at_notify_receive.dart and at_notify_send.dart both contain the basics to send an receive synchronously.

    To test for yourself you will need two terminal windows to do that you can press the split button on the VS Code IDE.

    You can then run the following commands agian note to replace the atSigns with your own.

    In the right window run this first to receive:

    In the left window you can now send a message using:

    The net result should look like this, and your message should appear in the right-hand side.

    You can try sending different messages and make sure they arrive, you can also put Dart on another machine and talk from machine to machine without setting up any other infrastructure.

    Neither the sender nor the receiver need any TCP ports open so this can be used to send end to end encrypted messages in near real-time without VPNs/Firewalls being required, just access to the Internet.

    If you are thinking this is the basis of a simple end-to-end encrypted chat application, that's coming soon with at_talk.

    Additional Features

    Implementation specific features to know about

    Some atSDKs have additional features, usually due to some technical reason. You can find a list of those features and which platforms/implementations support them here.

    Feature
    What it does
    Platform(s) Supported

    Offline support for apps

    Flutter / Dart

    Control the connection lifecycle

    C

    Infrastructure

    How we scale and provide resilience.

    The atPlatform is designed to be distributed and allows people to run their own infrastructure for atDirectory and atServer services on their own networks. Here, we show how at a high level how Atsign runs the Internet atPlatform Infrastructure.

    Atsign services are monitored by an independent third party for uptime and can be seen here:

    atDirectory

    Atsign runs the Internet atDirectory, which has to be resilient and dependable. To provide that level of service, we use Google's Cloud Platform, Kubernetes, containers, and a distributed in-memory database.

    The atDirectory runs in a GCP Virtual Private Cloud. This VPC also houses an auto-scaling Kubernetes cluster which is spread across multiple datacenters and availability zones.

    Remote Procedure Calls (RPC)

    spooky actions at a distance

    Do something remotely and send back the answer.

    RPC has been a design pattern for decades and no surprise we have RPC in the atSDK also. This demo code is in another directory named at_rpc_demo. You can get to it by using VS Code and open folder and select that folder. Next once again open two windowpanes.

    In the right hand pane enter (again using your atSigns!)

    This pulls in needed libraries, and then this starts the RPC listener.

    Get sample code

    Open Source on GitHub

    The atPlaform and the atSDKs are all open souce and the code can be found on GitHub and downloaded from . But we will start with some easy sample code to show how data can be shared and notifications can be sent between atSigns. These are the basic building blocks for powerful applications like and graphical applications like , both of which are also opensource so you can see the code in action.

    Using the atSDK with Dart

    Some very simple example of using the atSDK to get a feel for what it offers and create building blocks for your larger projects.

    What and why Dart ?

    Dart is an opensource project from Google offering a fast and multi-platform programming language. The atPlatform team choose Dart as a high level language to build proof of concept code but Dart proved to be a fast and reliable language to build on and as we needed features like being able to compile to executables, the Dart team delivered.

    At this point we have ported the atSDK to other languages, like Java and Python the Dart atSDK is a great place to start.

    Synchronization
    Connection Hooks

    Setup the atSDK for your preferred language

    How to authenticate to an atServer

    Learn how to create atKeys for your chosen platform

    How to do basic CRUD operations on an atServer

    How to send and receive real-time messages

    Implementation specific features to know about

    Get Started
    Authentication
    atKey Reference
    CRUD Operations
    Notifications
    Additional Features
    Message received.
    dart run .\bin\at_notify_receive.dart -a "@energetic22" -n "atsign"
    dart run .\bin\at_notify_send.dart -a "@7capricorn" -n "atsign" -o "@energetic22" -m "hello world"

    The atDirectory service is found on the well-known DNS address root.atsign.org on port 64. This is load balanced across the atDirectory containers. These containers, through an internal load balancer, to read-only in-memory databases containing the atSign to Fully Qualified Domain Name (FQDN) and port number mappings for all atSigns.

    The read-only databases are kept up to date with a single read-write database. This database is updated by the registrar website, which is run in another Kubernetes cluster.

    This design has proved to be reliable and allows upgrades in place without downtime. It automatically scales as load increases by spinning up more containers, and, if required, by adding new machines to the cluster itself. GCP's platform and Kubernetes have demonstrated resilience during data center or hardware issues, and have self-healed the infrastructure.

    atServers

    Each atSign has its own dedicated personal data store, called an "atServer," running as a Docker container within a Docker Swarm. Atsign runs a number of Docker Swarms and can move atServers from one swarm to another. However, for high availability, Atsign relies on the Docker Swarm's manager nodes to orchestrate and ensure each atServer is up and running even if hardware fails within a swarm.

    Why Docker Swarm and not Kubernetes? Kubernetes is an excellent choice for groups of containers that provide a service like the atDirectory or websites. But, Kubernetes does not scale down well for thousands or millions of tiny independent containers like atServers. Docker Swarm also provides highly resilient networking and is very lightweight.

    resilient atServer Cluster

    The FQDN and port number for a given atSign from the atDirectory is connected to the Docker Swarm. Each Docker Swarm node will route the port number to the right container on the swarm via its internal VXLAN. The Manager Nodes are responsible for ensuring each container is running and available across the whole swarm.

    For data requiring persistent storage beyond the Docker Swarm, encrypted atServer data gets transferred to a highly resilient NetApp Cloud Volume managed by GCP. This cloud volume functions as a network file system accessible to the atServers.

    All infrastructure components are distributed across multiple data centers and availability zones, and have proven to be highly reliable with very little downtime of individual atServers during failures or upgrades.

    Architecture Diagram of the atDirectory infrastructureArchitecture Diagram of the atDirectory infrastructure
    Highly available design
    Then in the left window you can run up the client

    You will get a prompt after a second or two and you can put in a math expression and hit enter. The expression will be send to the other atSign the answer calculated and then returned.

    See our session in action:

    RPC

    Couple of things to notice here are that the RPC server will only respond to RPC's from the designated atSign with the --allow-list argument.

    The RPC can of course do anything you want it to and the atSigns can be running anywhere.

    The code

    An AtRpc object is set up pretty simply with:

    The Callbacks are sent to the DemoRpcServerclass and handled appropriately.

    On the sending side the RPC client is initiated:

    Then the RPC simple sent and awaiting a reply

    dart pub get
    dart pub get
    dart run .\bin\arithmetic_server.dart -a "@energetic22" -n "atsign" --allow-list "@7capricorn"
    Clone the demo repo

    In VS Code you can click "Clone Git Repository" and enter the URL

    This will copy down all the sample code from GitHub to the directory you select. VS Code will ask you if you want to open the directory you just downloaded and say no. We will pick the one directory we need in the next step.

    Clone code from GitHub

    Open at_getting_started

    Click the Open Folder selection and go to the directory you selected and then at_demos then finally select the at_getting_started directory, then click "select folder". You might get a pop up asking if you trust the authors and if you trust us say yes!

    At this point you will have the demo code in the IDE and probably see a bunch of red files, meaning something is wrong with them.

    dart pub get

    VS Code if you have the Dart plugin installed will ask if you want to run pub get you can safely say yes.

    Next you can open a terminal session from the VS Code menu, and you will get a window with a prompt. At that prompt you can type the following two commands to pull down the required libraries.

    Once you have run those commands in the terminal window all the red text will have gone and we are ready to run some code.

    Ready to run.
    Atsign Foundation
    SSH No Ports
    atmospherePro
    Get Dart

    Dart is available at dart.dev for Windows, Linux, macOS, download and follow the instructions to install Dart. You can program in the IDE of your choice, but we can recommend VS Code as it has excellent support for Dart, download install VS Code then install the Dart extension.

    to get to the Extensions screen press "Control Shift X" then add Dart

    Put and Get data asynchronously

    You have mail!

    Sending and receiving data

    The next step is to send and receive for this to work we need to get the atKeys file containing the cryptographic keys and login or onboard to the atServer. From there we can put data and get data as if a database was local to us but in fact data is remote on the data owners atServer.

    This is achieved by every data element having an owner (the sending atSign) and a receiving atSign, behind the scenes the atSDK encrypts the data and sends it on to the atServer for delivery. All we need to do is a simple:

    await atClient.put(sharedRecordID, message, putRequestOptions: pro);

    of course we also have to setup the atClient object and onboard but that is all in the at_key_put.dart file for you. so let's send some data.

    Running the program without arguments brings up this help, most is self explanatory.

    *Note here we are using the atSigns activated in the previous section just substitute your own atSigns.

    The -nnamespace is lefthand side of the atRecord so in this case the full atRecord would be @7capricorn:message.atsign@energetic22 and you see this in the results too. Experiment yourself!

    You may also notice yourself that the first time you fail to get the message. This is because we specified in the metadata that the TTL is 60000 millisecond or 1 minute. Try again as you see in the above image and you should get your message too.

    The code is worth taking a look at the code as it less than 70 lines but allows data to transferred from a device anywhere to a device anywhere, and the data is fully encrypted.

    The data in this example is "store and forward", which is ideal for many use cases but sometimes we need near real time data and that's what we cover next.

    atTalk - Encrypted chat client

    Let's chat!

    For the final example we have at_talk, which is a fully end-to-end encrypted chat application in less than 300 lines of code. There is nothing pretty, its just a command line app, that takes the Unix/Linux command talk to a global level.

    The command talk allows people on a Unix/Linux machine to IM each other, at_talk allows anyone with an atSign to talk with each other.

    This code is in a separate repo so once again in VS Code click the "Clone Git Repository" button. The enter:

    Like before, you will get asked if you want to run pub get and say yes or if you prefer you can open a terminal window and type:

    This as we know pulls in the needed libraries and the code will loose all the red underlines as those libraries are loaded.

    At this point we can open two terminal panels as we have done before and run the at_talk code. You can run at_talk without any arguments and it will return some help:

    dart run .\bin\arithmetic_client.dart -a "@7capricorn" -n "atsign" --server-atsign "@energetic22"
        var rpc = AtRpc(
          atClient: atClient,
          baseNameSpace: atClient.getPreferences()!.namespace!,
          domainNameSpace: 'at_rpc_arithmetic_demo',
          callbacks: DemoRpcServer(),
          allowList: allowList,
        );
    
        rpc.start();
      } catch (e) {
        print(e);
        print(CLIBase.argsParser.usage);
      }
        var rpc = AtRpcClient(
            atClient: atClient,
            baseNameSpace: atClient.getPreferences()!.namespace!,
            domainNameSpace: 'at_rpc_arithmetic_demo',
            serverAtsign: serverAtsign);
    var response = await rpc.call({'expr': expr}).timeout(Duration(seconds: 5));
    https://github.com/atsign-foundation/at_demos.git
    dart pub get
    dart pub update
    specification
     dart run .\bin\at_key_put.dart -a "@energetic22" -n "atsign" -o "@7capricorn" -m "hello world" 
     
     dart run .\bin\at_key_get.dart -a "@7capricorn" -n "atsign" -o "@energetic22"
    dart run .\bin\at_key_put.dart                                                                
        --help                        Usage instructions
    -a, --atsign (mandatory)          This client's atSign
    -n, --namespace (mandatory)       Namespace
    -k, --key-file                    Your atSign's atKeys file if not in ~/.atsign/keys/
    -c, --cram-secret                 atSign's cram secret
    -h, --home-dir                    home directory
    -s, --storage-dir                 directory for this client's local storage files
    -d, --root-domain                 Root Domain
                                      (defaults to "root.atsign.org")
    -v, --verbose                     More logging
        --never-sync                  Do not run sync
    -o, --other-atsign (mandatory)    The atSign we want to communicate with
    -m, --message (mandatory)         The message we want to send
    Invalid argument(s): Option other-atsign is mandatory.
        ..metadata = (Metadata()
          ..ttl = 60000 // expire after 60 seconds
          ..ttr = -1); // allow recipient to keep a cached copy

    From here you can split windows like before and run two at_talk applications and send messages to each other.

    In the left window for example (subsitute your own atSigns!

    In the right window, the same but in reverse.

    In this session you can see the typed messages in white and th received messages in green with the prompts in red.

    talking atSigns

    Unlike Linux talk, however these two atSigns can be anywhere on the Internet and communicating with Privacy, get a friend to run through the demo and use at_talk !

    There are a couple of features, that are worth mentioning you can use the / and then an atSign to change who you are sending messages to and yes you can message to yourself.

    The other feature that has proved very useful is being able to "pipe" commands to at_talk. To do this you can compile the code to a binary using:

    Now you can "pipe" the output of a command into the chat with the other atSign.

    This prints the code of at_talk.dart into the receiving atsigns chat window. To our knowledge the is the only chat application that you can pipe things too!

    If you go this far first thank you and second, please enjoy your continuing journey and raise issues and PR's to any of the repos!

    https://github.com/atsign-foundation/at_talk.git

    Connection Hooks

    C

    The full example code can be found on our GitHub.

    Connection Hooks

    Connections hooks are useful for when you want to add additional control to the connection lifecycle in the atSDK. This means you as the developer can implement things like automatically reconnecting when connections drop (example below).

    Usage

    1. Enable hooks in your context's atserver_connection

    Since not every connection uses hooks, you must explicitly enable it.

    2. Write a void* function

    Write a function that we would like our atclient connection to run at certain times during the program.

    3. Set the hook

    For example, we can set the ATCLIENT_CONNECTION_HOOK_TYPE_PRE_WRITE hook to run our function pre_write_hook before we write any data to our atServer connection.

    Examples

    Print something before we write to the atServer

    The full example code can be found on our .

    Automatically reconnect when the connection drops

    Get Started

    Setup the atSDK for your preferred language

    Starter Dart App

    Guide coming soon!

    For now, please checkout our

     dart pub get
    dart run .\bin\at_talk.dart
    -k, --key-file                Your atSign's atKeys file if not in ~/.atsign/keys/
    -a, --atsign (mandatory)      Your atSign
    -t, --toatsign (mandatory)    Talk to this atSign
    -d, --root-domain             Root Domain (defaults to root.atsign.org)
    -n, --namespace               Namespace (defaults to ai6bh)
    -v, --verbose                 More logging
        --never-sync              Completely disable sync
    Invalid argument(s): Option atsign is mandatory.
     dart run .\bin\at_talk.dart -a "@7capricorn" -t "@energetic22" -n "atsign"
     dart run .\bin\at_talk.dart -a "@energetic22" -t "@7capricorn" -n "atsign"
    dart compile exe -o attalk bin/at_talk.dart
    cat .\bin\at_talk.dart| attalk -a "@energetic22" -t "@7capricorn" -n "atsign"
    GitHub
    atclient_connection_hooks_enable(&(atclient->atserver_connection));
    void *pre_write_hook(atclient_connection_hook_params *params)
    {
        atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "pre_write_hook called\n");
        return NULL;
    }
    atclient_connection_hooks_set(&(atclient->atserver_connection), ATCLIENT_CONNECTION_HOOK_TYPE_PRE_WRITE, pre_write_hook))
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/connection_hooks.h>
    #include <atclient/constants.h>
    #include <atclient/notify.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define TAG "5a-hooks"
    
    #define ATSIGN "@soccer0"
    
    void *pre_write_hook(atclient_connection_hook_params *params)
    {
        atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "pre_write_hook called\n");
        return NULL;
    }
    
    int setup_hooks(atclient *atclient)
    {
        int exit_code;
    
        if ((exit_code = atclient_connection_hooks_enable(&(atclient->atserver_connection))) != 0)
        {
            return exit_code;
        }
    
        if ((exit_code = atclient_connection_hooks_set(&(atclient->atserver_connection), ATCLIENT_CONNECTION_HOOK_TYPE_PRE_WRITE, pre_write_hook)) != 0)
        {
            return exit_code;
        }
    
        exit_code = 0;
        return exit_code;
    }
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient1;
        atclient_init(&atclient1);
    
        atclient_atkey atkey;
        atclient_atkey_init(&atkey);
    
        atclient_notify_params notify_params;
        atclient_notify_params_init(&notify_params);
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_pkam_authenticate(&atclient1, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        /*
         * Set up hooks so that we can run code before our connection writes to the atServer.
         */
        if ((exit_code = setup_hooks(&atclient1)) != 0)
        {
            goto exit;
        }
        atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "Hooks set up successfully.\n");
    
        /*
         * Now at this point, it's business as normal.
         * For the sake of this example, we're going to send a notification.
         * Since we set up a pre write hook, it will log a message before we send any data to the atServer.
         */
        if ((exit_code = atclient_atkey_create_shared_key(&atkey, "phone", ATSIGN, "@soccer99", "c_demos")) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_notify_params_set_atkey(&notify_params, &atkey)) != 0)
        {
            goto exit;
        }
    
        const char *value = "Hello! This is a notification!";
        if ((exit_code = atclient_notify_params_set_value(&notify_params, value)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_notify(&atclient1, &notify_params, NULL)) != 0)
        {
            goto exit;
        }
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient1);
        atclient_atkey_free(&atkey);
        atclient_notify_params_free(&notify_params);
        return exit_code;
    }
    }
    // TODO
    Starter Flutter App

    Create a Flutter application initialized for use with the atSDK

    If you don't have a Flutter setup for development yet, please see the Flutter docs.

    1. Create a new Flutter app

    First, generate a new Flutter app (replace at_app with the name of your application).

    2. Add dependencies

    Then add the following dependencies to your project:

    3. Replace main.dart

    Now copy the contents of main.dart from below into lib/main.dart into your project.

    main.dart
    import 'dart:async';
    
    import 'package:flutter/material.dart';
    import 'package:at_onboarding_flutter/at_onboarding_flutter.dart';
    import 'package:flutter_dotenv/flutter_dotenv.dart';
    import 'package:path_provider/path_provider.dart'
        show getApplicationSupportDirectory;
    
    import
    

    4. Add home_screen.dart

    Create a second file in lib/ called home_screen.dart. Copy the contents of home_screen.dart from below into your project.

    home_screen.dart

    5a. gitignore the .env file

    Add the following line to the top of .gitignore:

    This will prevent accidentally uploading the secret values stored in .env to any git repositories associated with your app.

    5b. Add and populate the .env file

    Create a new file in the root of the project (next to pubspec.yaml) called .env. Add the following lines to it:

    These values should be populated with your unique app namespace, and registrar API key.

    5.c. Register the .env file in pubspec.yaml

    In pubspec.yaml there should be a flutter: key, under this key, we want to create and add .env the assets: list:

    6. Start developing!

    Start your application with the following command:

    Now you are ready to begin developing!

    Get Started C Application

    Setup a CMake project which includes the atSDK package suite.

    The full example can be found here.

    1. Ensure you have a C Compiler, CMake, and a build automation tool

    The method at which you decide to use to install these prerequisites are up to you. These three tools can be installed using either apt or pip.

    Firstly, ensure you have a C compiler such as or . We recommend because that is currently what Atsign is focused on supporting.

    Secondly, ensure you have installed using cmake --version. Ensure that your CMake version is >= 3.24. If you have issues obtaining a newer version of CMake, we have found lots of success using pip when installing CMake via pip install [email protected]

    Lastly, ensure you have a automation tool installed, such as . We recommend because that is currently what Atsign is focused on supporting. These examples are using GNU Make version 4.3, but any version should work.

    2. Create an empty directory

    This command will create a directory and then go into it.

    3. Create CMakeLists.txt

    Inside your project folder, create a new file called CMakeLists.txt.

    Copy and paste the CMake code into the file. Feel free to change the project name by changing this line:project(my_first_c_app) .

    The above CMakeLists.txt will use FetchContent to download the C atSDK for you. Then, we will create a target named main for you and link our atclient library statically.

    4. Create main.c

    Inside your project folder, create a new file called main.c

    Change the line #define ATSIGN "@jeremy_0" to the atSign that you have keys to. For example, if you own an atSign @alice and have its keys in the correct directory ~/.atsign/keys/@alice_key.atKeys, then I would change this line in my code to #define ATSIGN "@alice"

    5. CMake Configure

    In the terminal, run the following command.

    This is known as the "configure" step where CMake will build instructions on how to build the app specific to your machine.

    Output should be similar to the following:

    6. Build

    Use CMake to build your configure and build your programs:

    This command is the same as running cd build && make all (if you are using Makefiles).

    Your output will look similar to:

    7. Run the binary

    The previous command, if ran successfully, would have created a binary executable for you: ./build/main.

    Simply run the program:

    Your output will look similar to:

    Congratulations! You have successfully ran a barebones Atsign C application.

    Dart atSDK Walkthrough
    flutter create at_app
    cd at_app
    flutter pub add at_client_mobile at_onboarding_flutter flutter_dotenv path_provider
    import 'package:at_client_mobile/at_client_mobile.dart';
    import 'package:flutter/material.dart';
    
    // * Once the onboarding process is completed you will be taken to this screen
    class HomeScreen extends StatelessWidget {
      const HomeScreen({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        // * Getting the AtClientManager instance to use below
        AtClientManager atClientManager = AtClientManager.getInstance();
        // * Getting the AtClient instance to use below
        AtClient atClient = atClientManager.atClient;
    
        // * From this widget onwards, you are fully onboarded!
        // * You can use the AtClient instance to perform operations as the onboared atSign
        return Scaffold(
          appBar: AppBar(
            title: const Text('What\'s my current @sign?'),
          ),
          body: Center(
            child: Column(children: [
              const Text('Successfully onboarded and navigated to FirstAppScreen'),
    
              // * Use the AtClient to get the current @sign
              Text('Current @sign: ${atClient.getCurrentAtSign()}')
            ]),
          ),
        );
      }
    }
    
    .env
    NAMESPACE=
    API_KEY=
    ...
    flutter:
      ...
      assets:
        - .env
      ...
    ...
    flutter run
    https://status.atsign.com/status.atsign.com
    UpTimeRobot Reports
    'home_screen.dart'
    ;
    Future<void> main() async {
    WidgetsFlutterBinding.ensureInitialized();
    await dotenv.load();
    runApp(const MyApp());
    }
    Future<AtClientPreference> loadAtClientPreference() async {
    var dir = await getApplicationSupportDirectory();
    return AtClientPreference()
    ..rootDomain = 'root.atsign.org'
    ..namespace = dotenv.env['NAMESPACE']
    ..hiveStoragePath = dir.path
    ..commitLogPath = dir.path
    ..isLocalStoreRequired = true;
    // * By default, this configuration is suitable for most applications
    // * In advanced cases you may need to modify [AtClientPreference]
    // * Read more here: https://pub.dev/documentation/at_client/latest/at_client/AtClientPreference-class.html
    }
    class MyApp extends StatefulWidget {
    const MyApp({Key? key}) : super(key: key);
    @override
    MyAppState createState() => MyAppState();
    }
    class MyAppState extends State<MyApp> {
    // * load the AtClientPreference in the background
    Future<AtClientPreference> futurePreference = loadAtClientPreference();
    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    // * The onboarding screen (first screen)
    home: Scaffold(
    appBar: AppBar(
    title: const Text('MyApp'),
    ),
    body: Builder(
    builder: (context) => Center(
    child: ElevatedButton(
    onPressed: () async {
    AtOnboardingResult onboardingResult =
    await AtOnboarding.onboard(
    context: context,
    config: AtOnboardingConfig(
    atClientPreference: await futurePreference,
    rootEnvironment: RootEnvironment.Production,
    ),
    );
    if (mounted) {
    switch (onboardingResult.status) {
    case AtOnboardingResultStatus.success:
    Navigator.push(
    context,
    MaterialPageRoute(builder: (_) => const HomeScreen()),
    );
    break;
    case AtOnboardingResultStatus.error:
    ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
    backgroundColor: Colors.red,
    content: Text('An error has occurred'),
    ),
    );
    break;
    case AtOnboardingResultStatus.cancel:
    break;
    }
    }
    },
    child: const Text('Onboard an @sign'),
    ),
    ),
    ),
    ),
    );
    }
    }
    clang
    gcc
    gcc
    CMake
    GNU Make
    GNU Make
    gcc --version
    cmake --version
    make --version
    mkdir my_first_c_app && cd my_first_c_app
    # Minimum cmake version required by atSDK is 3.24
    cmake_minimum_required(VERSION 3.24)
    
    project(my_first_c_app)
    
    include(FetchContent)
    
    FetchContent_Declare(
      atsdk
      URL https://github.com/atsign-foundation/at_c/archive/refs/tags/v0.1.0.tar.gz
      URL_HASH SHA256=7ca4215a473037ca07bef362b852291b0a1cf4e975d24d373d58ae9c1df832bc
    )
    
    FetchContent_MakeAvailable(atsdk)
    
    add_executable(main ${CMAKE_CURRENT_LIST_DIR}/main.c)
    
    target_link_libraries(main PRIVATE atclient)
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@jeremy_0"
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient;
        atclient_init(&atclient);
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
        
        if ((exit_code = atclient_pkam_authenticate(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        atlogger_log("my_first_c_app", ATLOGGER_LOGGING_LEVEL_INFO, "Authenticated to atServer successfully!\n");
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient);
        return exit_code;
    }
    }
    cmake -S . -B build
    $ cmake -S . -B build
    -- [ATSDK] ATSDK_AS_SUBPROJECT: ON
    -- Building atlogger
    -- [ATLOGGER] ATLOGGER_AS_SUBPROJECT: ON
    -- Building atchops
    -- [ATCHOPS] ATCHOPS_AS_SUBPROJECT: ON
    -- [MbedTLS] fetching package...
    -- [uuid4] fetching package...
    -- Building atclient
    -- [ATCLIENT] ATCLIENT_AS_SUBPROJECT: ON
    -- [cjson] fetching package...
    -- Configuring done
    -- Generating done
    -- Build files have been written to: /home/jeremy/GitHub/at_demos/demos/get_started_c/1-authentication/build
    cmake --build build
    cmake --build build
    [ 50%] Building C object CMakeFiles/main.dir/main.c.o
    [100%] Linking C executable main
    [100%] Built target main
    ./build/main
    [DEBG] 2024-08-15 00:37:16.517243 | connection |        SENT: "jeremy_0"
    [DEBG] 2024-08-15 00:37:16.550027 | connection |        RECV: "3b419d7a-2fee-5080-9289-f0e1853abb47.swarm0002.atsign.zone:5770"
    [DEBG] 2024-08-15 00:37:16.892252 | connection |        SENT: "from:jeremy_0"
    [DEBG] 2024-08-15 00:37:16.929632 | connection |        RECV: "data:_d6d1a569-b944-4fd1-8c1e-7fc26e508452@jeremy_0:75be9182-bfdc-437f-96bc-c45eb5a1c0ff"
    [DEBG] 2024-08-15 00:37:16.968986 | connection |        SENT: "pkam:QLuCl8JITCbOF6J8A6WlqBX3FUKoRR8DHh0FMnbVhHYRiuXZdBHJks0aQtq2cUj47KdhJCvN3Uk728UJHzwLgIF00wE3893xUi+9luoD7OFdV4Pbtmrxvj4K/s81A4Z9XBEJwtKerWStUavX5hR39By6Y6NJ2HeCuqVvBw2WQ/gKM15cAndpXrYzEVNJk9eCCN4+VXWxJOm6FhoY41Qxn/QSYqnRqp6PfHiY1rFXjsikJX6ip3LrCFTabJAS78BHx4BmmUzGKVPn0dFtYwvEKBgumViQhKHwS1ae1xBkLTZCARdIUhdhudoyba/ogwIEMjTWanLzAbqtNRa6qPTDrw=="
    [DEBG] 2024-08-15 00:37:17.018977 | connection |        RECV: "data:success"
    [INFO] 2024-08-15 00:37:17.019891 | my_first_c_app | Authenticated to atServer successfully!

    Authentication

    How to authenticate to an atServer

    Dart

    Package Installation

    In Dart we provide the package which handles onboarding to the atServer via files stored in the ~/.atsign/keys directory

    Add the package to your project automatically using pub:

    Usage

    Set up the to onboard to the atServer.

    Next get the onboardingService

    Finally wait to be onboarded, this returns true once complete.

    This can be wrapped to check that the onboard was successful with the code snippet below.

    Flutter

    If you followed the guide for Flutter, then you should already have onboarding setup in your app. Feel free to skip this section and move on.

    C

    Find the full example on our .

    1. Fetch your atServer's address from the production atDirectory

    First, include atclient_utils.h

    Package Installation

    In Flutter, we provide the at_onboarding_flutter package which handles secure management of these secret keys.

    Add the package to your project automatically using pub:

    Usage

    Simply call the onboard method whenever you want your app to open the onboarding widget.

    API Key setup

    To get an API key for your app, first head to the registrar website, then click manage under one of your atSigns:

    Then open the Advanced settings drop down and click Generate New API Key:

    and
    #include <atclient/constants.h>
    at the top of you program.

    Next, initialize variables that will hold the host and port of our atServer. We will pass pointers to these variables to the function that will populate these values for us.

    Lastly, use the atclient_utils_find_atserver_address function to populate our atserver_host and atserver_port variables. This function returns an int for error handling. A non-zero exit code indicates an error.

    Don't forget to free the atserver_host variable! It is mentioned in the documentation of the atclient_utils_find_atserver_address function that it is the responsibility of the caller to free this variable.

    2. Load your atSign's atKeys

    First, include atkeys.h.

    Next, allocate memory for the atclient_atkeys struct. In this scenario, we are statically allocating. We need to call the atclient_atkeys_init function and pass a pointer to our atclient_atkeys struct. You will find this to be a common pattern when working with C structs. This is our method of mimicking object-oriented methodology in our C SDK.

    Finally, call the atclient_utils_populate_atkeys_from_homedir function which will look for your ATSIGN's keys in your $HOME/.atsign/keys/ directory.

    For example, if my atSign was @alice, I would have my @alice keys set up such that this file exists: $HOME/.atsign/keys/@alice_key.atKeys.

    This function returns an int for error handling purposes. A non-zero exit code indicates that an error has occurred.

    Don't forget to run atclient_atkeys_freeat the end of your application. You will see it as a common pattern that for every *_init function that we call, we must have a corresponding *_free function.

    3. PKAM Authenticate

    If your atSign's keys exist and your atSign's atServer is already activated, then this is known as pkam authentication.

    First include atclient.h at the top of your application.

    Secondly, we will create our atclient object. This variable holds data necessary for doing various operations on our atServer later on (such as CRUD or Events).

    Thirdly, call the atclient_pkam_authenticate function.

    This function connects to the atServer (if necessary) and uses your atSign's atKeys file to generate a special pkam command by signing a challenge sent from the atServer. Once pkam authentication is successful, your connection will be authenticated and your client will be free to make various operations on the atServer.

    This function will return an int for error handling, in which a non-zero exit code indicates an error.

    Lastly, do not forget to call atclient_free at the end of your application.

    Example Application

    Here is an example application that authenticates my atSign @jeremy_0.

    This code is also available on our GitHub.

    at_onboarding_cli
    dart pub add at_onboarding_cli
    preferences
    Get Started
    GitHub
     AtOnboardingPreference atOnboardingConfig = AtOnboardingPreference()
        ..hiveStoragePath = '$homeDirectory/.$nameSpace/$fromAtsign/storage'
        ..namespace = nameSpace
        ..downloadPath = '$homeDirectory/.$nameSpace/files'
        ..isLocalStoreRequired = true
        ..commitLogPath = '$homeDirectory/.$nameSpace/$fromAtsign/storage/commitLog'
        ..rootDomain = rootDomain
        ..fetchOfflineNotifications = true
        ..atKeysFilePath = atsignFile
        ..atProtocolEmitted = Version(2, 0, 0);

    atKey Reference

    Learn how to create atKeys for your chosen platform

    AtKey

    Please note that any reference to the word "AtKey" in this document is not associated with cryptographic keys. The atKey is the "key" of the key-value pair that makes up an atRecord.

      AtOnboardingService onboardingService = AtOnboardingServiceImpl(
          fromAtsign, atOnboardingConfig,
          atServiceFactory: atServiceFactory);
    await onboardingService.authenticate();
    
    bool onboarded = false;
      Duration retryDuration = Duration(seconds: 3);
      while (!onboarded) {
        try {
          stdout.write('\r\x1b[KConnecting ... ');
          await Future.delayed(Duration(
              milliseconds:
              1000)); // Pause just long enough for the retry to be visible
          onboarded = await onboardingService.authenticate();
        } catch (exception) {
          stdout.write(
              '$exception. Will retry in ${retryDuration.inSeconds} seconds');
        }
        if (!onboarded) {
          await Future.delayed(retryDuration);
        }
      }
      stdout.writeln('Connected');
    flutter pub add at_onboarding_flutter
    AtOnboardingResult onboardingResult = await AtOnboarding.onboard(
      context: context,
      config: AtOnboardingConfig(
        atClientPreference: futurePreference,
        rootEnvironment: RootEnvironment.Production,
        appAPIKey: dotenv.env['API_KEY'],
        domain: 'root.atsign.org',
      ),
    );
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    char *atserver_host = NULL;
    int atserver_port = 0;
    if(atclient_utils_find_atserver_address(
      ATCLIENT_ATDIRECTORY_PRODUCTION_HOST,
      ATCLIENT_ATDIRECTORY_PRODUCTION_PORT,
      ATSIGN,
      &atserver_host,
      &atserver_port) != 0) {
      // an error occurred
    }
    free(atserver_host);
    #include <atclient/atkeys.h>
    atclient_atkeys atkeys;
    atclient_atkeys_init(&atkeys);
    if (atclient_utils_populate_atkeys_from_homedir(
      &atkeys,
      ATSIGN) != 0)
    {
        // an error occurred
    }
    atclient_atkeys_free(&atkeys);
    #include <atclient/atclient.h>
    atclient atclient;
    atclient_init(&atclient);
    ```
    if (atclient_pkam_authenticate(
      &atclient,
      atserver_host,
      atserver_port,
      &atkeys,
      ATSIGN) != 0)
    {
      // error occurred
    }
    ```
    atclient_free(&atclient);
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@jeremy_0"
    
    int main()
    {
        int exit_code = -1;
    
        /*
         * this function will print DEBUG logs and below
         */
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        /*
         * these variables will hold the output of the atclient_utils_find_atserver_address function
         */
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        /*
         * the `atkeys` variable will hold the encryption keys that the atClient will use to do various things like authentication and end-to-end encryption.
         * these keys are typically located in `~/.atsign/keys/` and would have been generated by you for each atSign using at_activate.
         * It is important to call the `_init` function before using the struct.
         */
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        /*
         * the `atclient` variable will hold the state of the atClient and is used to interact with the atServer.
         * you will need to pass this context to most atclient functions
         * It is important to call the `_init` function before using the struct.
         */
        atclient atclient;
        atclient_init(&atclient);
    
        /*
         * this function will find the atServer's address from the atDirectory
         * and populate the `atserver_host` and `atserver_port` variables
         * with the atServer's address and port.
         * Don't forget to free the `atserver_host` variable after use, when using this function.
         */
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        /*
         * my keys are assumed to be set up in ~/.atsign/keys/@soccer99_key.atKeys
         * this function will read the keys from the file and populate the `atkeys` variable
         */
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        /*
         * this function will connect to the atServer, if it is not already connected,
         * then authenticate to the atServer and establish an authenticated connection
         * using the populated `atkeys` variable.
         */
        if ((exit_code = atclient_pkam_authenticate(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        atlogger_log("my_first_c_app", ATLOGGER_LOGGING_LEVEL_INFO, "Authenticated to atServer successfully!\n");
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient);
        return exit_code;
    }
    }
    This article explains how to create an atKey in the atSDK.

    If you are unfamiliar with atKeys please read this first.

    Flutter / Dart

    Package Installation

    The at_commons package contains common elements used in a number of Atsign's Flutter and Dart packages. This package needs to be included in the application in order to create atKeys.

    First add the package to your project:

    Usage

    See below for how to create the various types of atKeys.

    Public atKey

    To create a public atKey, first use the AtKey.public builder to configure it, then call .build to create it.

    The build method on PublicKeyBuilder takes no parameters.

    Example

    public:phone.wavi@alice

    Self atKey

    To create a self atkeyD, first use the AtKey.self builder to configure it, then call .build to create it.

    The build method on SelfKeyBuilder takes no parameters.

    Example

    phone.wavi@alice

    Shared atKey

    To create a shared atKey, first use the AtKey.shared builder to configure it, then call .build to create it.

    The build method on SharedKeyBuilder takes no parameters.

    Example

    @bob:phone.wavi@alice

    Caching shared atKeys

    To cache a shared atKey, you can do a call on SharedKeyBuilder.cache.

    API Docs

    You can find the API reference for the entire package available on .

    The AtKey class API reference is available .

    C

    You can find all these examples also on our .

    Introduction

    There are three kinds of atKeys:

    Public atKey

  • Self atKey

  • Shared atKey

  • Public atKey holds public (and non-encrypted) data, available for any atSign to get from you.

    Self atKey holds self encrypted data, only available for your own atSign to get.

    Shared atKey holds encrypted data that is only decipherable by you and the intended recipient.

    Every atKey has metadata, which is free for you to also control (to an extent). Not all metadata should be handled by the developer. Some metadata is managed by the SDK itself. Check out our documentation on metadata to find out which metadata is worth your time handling.

    Before running any of the examples, be sure to include atkey.h

    Public atKey

    This is how you create a Public atKey.

    First, create the atkey struct.

    Next, call the atclient_atkey_create_public_key function.

    Don't forget to call atclient_atkey_free at the end of the intended life-time of your atkey struct.

    Example Application

    Self atKey

    This is how you create a Self atKey.

    First, create the atclient_atkey struct

    Next, call the atclient_atkey_create_self_key function.

    Don't forget to call atclient_atkey_free at the end of the intended life-time of your atkey struct.

    Example Application

    Shared atKey

    This is how you create a Shared atKey.

    First, create the atclient_atkey struct.

    Next, call the atclient_atkey_create_shared_key function.

    Don't forget to call atclient_atkey_free at the end of the intended life-time of your atkey struct.

    Example Application

    Metadata

    This is how you modify the metadata of any atKey. For the sake of this example, we will modify the metadata of a Shared atKey.

    First, create your atKey as usual.

    Next, let's create a pointer to the atKey's metadata. We can feel safe reading the atclient_atkey's inner metadata field because we have previously called atclient_atkey_init.

    Call a atclient_atkey_metadata_set_* function. For the sake of this example, we will call atclient_atkey_metadata_set_ttl which sets the atKey's lifespan, specified in milliseconds.

    We can check if that was set and is safe to read by using the atclient_atkey_metadata_is_ttl_initialized(metadata) function. It is important to check that ttl was initialized. If this function returns true, we can feel safe knowing that the the internal initialized bit was set to true previously and that we are not reading garbage data.

    Example Application

    x

    flutter pub add at_commons
    cascade
    pub
    here
    GitHub
    AtKey.public signature
    static PublicKeyBuilder public(String key,
        {String? namespace, String sharedBy = ''})
    AtKey myPublicID = AtKey.public('phone', namespace: 'wavi', sharedBy: '@alice').build();
    AtKey.self signature
    static SelfKeyBuilder self(String key,
        {String? namespace, String sharedBy = ''})
    AtKey mySelfID = AtKey.self('phone', namespace: 'wavi', sharedBy: '@alice').build();
    AtKey.shared signature
    static SharedKeyBuilder shared(String key,
        {String? namespace, String sharedBy = ''})
    AtKey sharedKey = (AtKey.shared('phone', 'wavi')
        ..sharedWith('@bob')).build();
    Caching example
    AtKey atKey = (AtKey.shared('phone', namespace: 'wavi', sharedBy: '@alice')
     ..sharedWith('@bob')
     ..cache(1000, true))
     .build();
    #include <atclient/atkey.h>
    atclient_atkey my_public_atkey;
    atclient_atkey_init(&my_public_atkey);
    const char *atkey_key = "phone";
    const char *atkey_shared_by = "@jeremy_0";
    const char *atkey_namespace = "wavi"; 
    
    if(atclient_atkey_create_public_key(
      &my_public_atkey,
      atkey_key,
      atkey_shared_by,
      atkey_namespace) != 0) {
      // an error occurred
    }
    atclient_atkey_free(&my_public_atkey);
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer99"
    
    int main()
    {
        int exit_code = -1;
    
        /*
         * Create an atkey struct
         */
        atclient_atkey my_public_atkey;
        atclient_atkey_init(&my_public_atkey);
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_namespace = "wavi"; 
    
        /*
         * Use the dandy atkey_create_public_key function to populate the struct for you with your desired values.
         */
        if((exit_code = atclient_atkey_create_public_key(&my_public_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0) {
            goto exit;
        }
    
        exit_code = 0;
    exit:
    {
        atclient_atkey_free(&my_public_atkey);
        return exit_code;
    }
    }
    atclient_atkey my_self_atkey;
    atclient_atkey_init(&my_self_atkey);
    const char *atkey_key = "phone";
    const char *atkey_shared_by = "@jeremy_0";
    const char *atkey_namespace = "wavi";
    
    if(atclient_atkey_create_self_key(
      &my_self_atkey,
      atkey_key,
      atkey_shared_by,
      atkey_namespace) != 0) {
      // an error occurred
    }
    atclient_atkey_free(&my_public_atkey);
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer99"
    
    int main()
    {
        int exit_code = -1;
    
        /*
         * Create an atkey struct
         */
        atclient_atkey my_self_atkey;
        atclient_atkey_init(&my_self_atkey);
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_namespace = "wavi";
    
        /*
         * Use the dandy atkey_create_self_key function to populate the struct for you with your desired values.
         */
        if((exit_code = atclient_atkey_create_self_key(&my_self_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0) {
            goto exit;
        }
    
        exit_code = 0;
    exit:
    {
        atclient_atkey_free(&my_self_atkey);
        return exit_code;
    }
    }
    atclient_atkey my_shared_atkey;
    atclient_atkey_init(&my_shared_atkey);
    const char *atkey_key = "phone";
    const char *atkey_shared_by = ATSIGN;
    const char *atkey_shared_with = "@soccer99";
    const char *atkey_namespace = "wavi";
    
    if(atclient_atkey_create_shared_key(&my_shared_atkey,
      atkey_key,
      atkey_shared_by,
      atkey_shared_with, 
      atkey_namespace) != 0) {
      // an error occurred
    }
    atclient_atkey_free(&my_public_atkey);
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer99"
    
    int main()
    {
        int exit_code = -1;
    
        /*
         * Create an atkey struct
         */
        atclient_atkey my_shared_atkey;
        atclient_atkey_init(&my_shared_atkey);
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_shared_with = "@soccer99";
        const char *atkey_namespace = "wavi";
    
        /*
         * Use the dandy atkey_create_shared_key function to populate the struct for you with your desired values.
         */
        if((exit_code = atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace)) != 0) {
            goto exit;
        }
    
    
        exit_code = 0;
    exit:
    {
        atclient_atkey_free(&my_shared_atkey);
        return exit_code;
    }
    }
    atclient_atkey my_shared_atkey;
    atclient_atkey_init(&my_shared_atkey);
    
    const char *atkey_key = "phone";
    const char *atkey_shared_by = "@jeremy_0";
    const char *atkey_shared_with = "@soccer99";
    const char *atkey_namespace = "wavi";
    
    if (atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace) != 0)
    {
      // an error occurred
    }
    atclient_atkey_metadata *metadata = &(my_shared_atkey.metadata);
    if (atclient_atkey_metadata_set_ttl(metadata, 1 * 1000) != 0)
    {
        // an error occurred
    }
    if(atclient_atkey_metadata_is_ttl_initialized(metadata)) {
        // metadata->ttl is safe to read and we know it is populated.
        printf("metadata->ttl: %d\n", metadata->ttl);
    }
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer99"
    
    #define ATSERVER_HOST "root.atsign.org"
    #define ATSERVER_PORT 64
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        atclient_atkey my_shared_atkey;
        atclient_atkey_init(&my_shared_atkey);
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_shared_with = "@soccer99";
        const char *atkey_namespace = "wavi";
    
        if ((exit_code = atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace)) != 0)
        {
            goto exit;
        }
    
        /*
         * Now that the AtKey is properly set up, we can set the metadata of that AtKey like this.
         * Since we called the `atclient_atkey_init` function earlier, we can feel safe knowing that the metadata is also already initialized.
         */
        atclient_atkey_metadata *metadata = &(my_shared_atkey.metadata);
    
        /*
         * Set the ttl (time to live) of the AtKey. Once this AtKey is put into the atServer, it will only live for 1000 milliseconds.
         */
        if ((exit_code = atclient_atkey_metadata_set_ttl(metadata, 1 * 1000)) != 0)
        {
            goto exit;
        }
    
        /*
         * Set the ttr (time to refresh) of the AtKey. Once this AtKey is put into the atServer, the recipient of the AtKey will have it refreshed every 1000
         * milliseconds, in case there are any value changes.
         */
        if ((exit_code = atclient_atkey_metadata_set_ttr(metadata, 1 * 1000)) != 0)
        {
            goto exit;
        }
    
        if(atclient_atkey_metadata_is_ttl_initialized(metadata)) {
            // metadata->ttl is safe to read and we know it is populated.
            atlogger_log("main", ATLOGGER_LOGGING_LEVEL_DEBUG, "metadata->ttl: %d\n", metadata->ttl); // [DEBG] 2024-08-15 01:52:09.596055 | main | metadata->ttl: 1000
        }
    
        exit_code = 0;
    exit:
    {
        atclient_atkey_free(&my_shared_atkey); // this _free command will automatically free the metadata as well
        return exit_code;
    }
    }

    Notifications

    How to send and receive real-time messages

    In Dart, the AtClient is stored within the AtClientManager. Once an atSign has been onboarded, you will be able to access the AtClientManager for its associated atSign.

    AtClientManager

    AtClientManager is a singleton model. When AtClientManager.getInstance() is called, it will get the AtClientManager instance for the last onboarded atSign.

    AtClientManager atClientManager = AtClientManager.getInstance();

    If you need simultaneous access to multiple atClients, you need to create a new for each additional atClient, and onboard its atSign within the isolate.

    An example of this pattern can be found in .

    AtClient

    As previously mentioned, the AtClientManager stores the actual AtClient itself. You can retrieve the AtClient by calling atClientManager.atClient.

    Monitor

    You can subscribe to a stream of notifications like this:

    Notify

    You can send (a.k.a. publish) a notification like this:

    C

    You can find the full code of this example on our .

    Table of Contents

    Introduction

  • Monitor

    • 1. Include `monitor.h`

    • 2. Create a Monitor Context

    • 3. Call `atclient_monitor_pkam_authenticate`

  • Notify

    • 1. Include `notify.h`

    • 2. Create a Shared atKey

    • 3. Set up Notify Params

  • Introduction

    Events is how we send and receive real-time messages in the atProtocol. The client SDK assists in using the atProtocol simply and handles all the complex encryption stuff for you.

    • notify is how we send a real-time message to an atSign

    • monitor is how we listen for real-time messages

    Monitor

    1. Include `monitor.h`

    2. Create a Monitor Context

    First, we need to create a separate monitor connection to our atServer. This is similar to creating a atclient context for conducting CRUD operations, but it is important to make a completely separate one so that connection can be clear of any noise and used purely for monitoring for notifications.

    3. Call `atclient_monitor_pkam_authenticate`

    This is similar to the atclient_pkam_authenticate that you know and love. It should use the same atserver_host, atserver_port, and atclient_atkeys in your original atclient context because we are connecting to the same atServer that can be authenticated using the same set of cryptographic atKeys.

    This function will return an int for error handling in which a non-zero exit code indicates an error.

    4. Call `atclient_monitor_start`

    This will begin the monitoring process of the atclient context that you pass. Since we made a completely different connection, this context will be purely for listening for any notifications.

    In this example, we pass .* for the regex to receive all incoming notifications.

    This function will return an int for error handling in which a non-zero exit code indicates an error.

    5. Call `atclient_monitor_read`

    Calling atclient_monitor_read will attempt to read the network buffer for any notifications.

    It may be useful to put this in a loop to constantly be checking for any notifications that may have arrived to your atServer. You will commonly get error code -26624 because most times, there will be nothing to read!

    See the Example Application on how to read the different kinds of values from the atclient_monitor_response struct.

    6. Free everything

    Don't forget to free everything at the end of your application's life time

    Also free the atclient_monitor_response struct if you haven't already.

    Example Application

    You can find the full code of this example on our GitHub.

    Notify

    1. Include `notify.h`

    2. Create a Shared atKey

    Since we are notifying another atSign, we have to set up a Shared atKey.

    This process should be familiar to you if you have already gone through CRUD Operations.

    3. Set up Notify Params

    Next, we will set up the parameters before sending the notification. This will essentially hold the instructions on how to send the notification.

    The atKey and value are the two fields that are mandatory in order to send a notification. If you forget to set these two fields, then you will not be allowed to send a notification because the instructions of sending a notification are incomplete.

    4. Call `atclient_notify`

    Simply call atclient_notify .This will send a notification to the shared_with atSign that you specified when creating the Shared atKey which was done in this step.

    We will pass NULL into the commit_id parameter because we don't reallty care about the commt id for now. You can receive it by passing a int * if you would like.

    5. Free everything

    Don't forget to free everything

    Example Application

    You can find the full code of this example on our GitHub.

    isolate
    at_daemon_server
    GitHub
    AtClient atClient = atClientManager.atClient;
    AtClient atClient = AtClientManager.getInstance().atClient;
    atClient.notificationService
      .subscribe(regex: 'message.$nameSpace@', shouldDecrypt: true)
      .listen((notification) {
          print("Got a message: ${notification.value}");
      });
    AtKey messageKey = (AtKey.shared('message', nameSpace)
        ..sharedWith('@bob')).build();
    String message = "Hi bob, how are you?";
    await atClient.notificationService.notify(
      NotificationParams.forUpdate(messageKey, value: message),
    );
    #include <atclient/monitor.h>
    atclient monitor_client;
    atclient_init(&monitor_client);
    if (atclient_monitor_pkam_authenticate(
      &monitor_client, 
      atserver_host,
      atserver_port,
      &atkeys,
      "@jeremy_0") != 0) {
        // an error occurred
    }
    if(atclient_monitor_start(&monitor_client, ".*") != 0) {
        // an error occurred
    }
    atclient_monitor_response response;
    atclient_monitor_response_init(&response);
    
    int exit_code = atclient_monitor_read(&monitor_client, &atclient1, &response, NULL);
    
    // do things with the `response` variable
    if (response.type == ATCLIENT_MONITOR_MESSAGE_TYPE_NOTIFICATION) {
      // we should check if this value is populated first, so that we know it is safe for reading.
      if (atclient_atnotification_is_decrypted_value_initialized(&response.notification)) {
        atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "Received notification: %s\n", response.notification.decrypted_value);
      }
    }
    
    atclient_monitor_response_free(&response);
    atclient_free(&monitor_client);
    atclient_monitor_response_free(&response);
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atclient/monitor.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define TAG "4a-monitor"
    
    #define ATSIGN "@soccer99"
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient1;
        atclient_init(&atclient1);
    
        atclient monitor_client;
        atclient_init(&monitor_client);
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_pkam_authenticate(&atclient1, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        /*
         * Our monitor connection is just an ordinary atclient context, but we will use a completely separate atclient context to manage it.
         * And instead of calling the atclient functions on our atclient monitor context, we will use monitor functions on it instead.
         * In this case, we are using `atclient_monitor_pkam_authenticate` instead of `atclient_pkam_authenticate` even though it's essentially the same function
         * signature (aside from the function name)
         */
        if ((exit_code = atclient_monitor_pkam_authenticate(&monitor_client, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if((exit_code = atclient_monitor_start(&monitor_client, ".*")) != 0) {
            goto exit;
        }
    
        while (true)
        {
            atclient_monitor_response response;
            atclient_monitor_response_init(&response);
    
            exit_code = atclient_monitor_read(&monitor_client, &atclient1, &response, NULL);
    
            if (exit_code != 0)
            {
                if (response.type == ATCLIENT_MONITOR_ERROR_READ)
                {
                    atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Error reading from monitor: %d\n", response.error_read.error_code);
                }
                else if (response.type == ATCLIENT_MONITOR_ERROR_PARSE_NOTIFICATION)
                {
                    atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Parse notification from monitor: %d\n", exit_code);
                }
                else if (response.type == ATCLIENT_MONITOR_ERROR_DECRYPT_NOTIFICATION)
                {
                    atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Error response from monitor: %d\n", exit_code);
                } else {
                    atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Unknown error: %d\n", exit_code);
                }
            }
            else
            {
    
                if (response.type == ATCLIENT_MONITOR_MESSAGE_TYPE_NOTIFICATION)
                {
                    // we should check if this value is populated first, so that we know it is safe for reading.
                    if (atclient_atnotification_is_decrypted_value_initialized(&response.notification))
                    {
                        atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "Received notification: %s\n", response.notification.decrypted_value);
                    }
                }
                else if (response.type == ATCLIENT_MONITOR_MESSAGE_TYPE_DATA_RESPONSE)
                {
                    atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "Received data: %s\n", response.data_response); // most times, this is a `data:ok` from a heart beat
                }
                else if (response.type == ATCLIENT_MONITOR_MESSAGE_TYPE_ERROR_RESPONSE)
                {
                    atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Received error: %s\n", response.error_response);
                }
                else if (response.type == ATCLIENT_MONITOR_MESSAGE_TYPE_NONE)
                {
                    atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_INFO, "No message received\n");
                } else {
                    atlogger_log(TAG, ATLOGGER_LOGGING_LEVEL_ERROR, "Unknown message type: %d\n", response.type);
                }
            }
    
            atclient_monitor_response_free(&response);
        }
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient1);
        atclient_free(&monitor_client);
        return exit_code;
    }
    }
    
    #include <atclient/notify.h>
    atclient_atkey atkey;
    atclient_atkey_init(&atkey);
    
    if(atclient_atkey_create_shared_key(&atkey, "phone", "@jeremy_0", "@soccer99", "c_demos") != 0) {
        // an error occurred
    }
    atclient_notify_params notify_params;
    atclient_notify_params_init(&notify_params);
    
    if(atclient_notify_params_set_atkey(&notify_params, &atkey) != 0) {
      // an error occurred
    }
    
    const char *value = "Hello! This is a notification!";
    if(atclient_notify_params_set_value(&notify_params, value) != 0) {
      // an error occurred
    }
    if(atclient_notify(&atclient1, &notify_params, NULL) != 0) {
      // an error occurred
    }
    atclient_atkey_free(&atkey);
    atclient_notify_params_free(&notify_params);
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atclient/notify.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer0"
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient1;
        atclient_init(&atclient1);
    
        atclient_atkey atkey;
        atclient_atkey_init(&atkey);
    
        atclient_notify_params notify_params;
        atclient_notify_params_init(&notify_params);
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_pkam_authenticate(&atclient1, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        /*
         * 1a. Set up atkey in our notify_params struct
         */
        if((exit_code = atclient_atkey_create_shared_key(&atkey, "phone", ATSIGN, "@soccer99", "c_demos")) != 0) {
            goto exit;
        }
    
        if((exit_code = atclient_notify_params_set_atkey(&notify_params, &atkey)) != 0) {
            goto exit;
        }
    
        /*
         * 1b. Set up value in our notify_params struct
         */
        const char *value = "Hello! This is a notification!";
        if((exit_code = atclient_notify_params_set_value(&notify_params, value)) != 0) {
            goto exit;
        }
    
        /*
         * 2. Send the notification
         * We will pass `NULL` for the commit id.
         */
        if((exit_code = atclient_notify(&atclient1, &notify_params, NULL)) != 0) {
            goto exit;
        }
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient1);
        atclient_atkey_free(&atkey);
        atclient_notify_params_free(&notify_params);
        return exit_code;
    }
    }
    4. Call `atclient_monitor_start`
    5. Call `atclient_monitor_read`
    6. Free everything
    Example Application
    4. Call `atclient_notify`
    5. Free everything
    Example Application

    CRUD Operations

    How to do basic CRUD operations on an atServer

    In Dart, the AtClient is stored within the AtClientManager. Once an atSign has been onboarded, you will be able to access the AtClientManager for its associated atSign.

    AtClientManager

    AtClientManager is a singleton model. When AtClientManager.getInstance() is called, it will get the AtClientManager instance for the last onboarded atSign.

    AtClientManager atClientManager = AtClientManager.getInstance();

    If you need simultaneous access to multiple atClients, you need to create a new for each additional atClient, and onboard its atSign within the isolate.

    An example of this pattern can be found in .

    AtClient

    As previously mentioned, the AtClientManager stores the actual AtClient itself. You can retrieve the AtClient by calling atClientManager.atClient.

    atKey

    Before you can do anything with an atRecord, you need an to represent it.

    If you don't know how to create an atKey, please see the first.

    The following examples use the self atKey phone.wavi@<current atSign>

    It is up to the developer to modify the atKey according to their use case.

    Creating / Updating Data

    To create data the put method is used, this method accepts text (String) or binary (List<int>).

    Strongly Typed Methods

    Since put accepts both String or List<int> as a value, the typing is dynamic. atClient also contains the strongly typed putText which only accepts String for the value, or putBinary which only accepts List<int> for the value.

    To update existing data

    Updating existing data is done by doing a put to the same atKey, this will overwrite any existing data stored in the atRecord.

    The bytes [1, 2, 3, 4] have now replaced the string "123-456-7890".

    Reading Data

    There are two parts to reading data using the atClient SDK:

    1. Scanning for and listing out the atKeys for atRecords that can be retrieved

    2. Retrieving the atRecord for a given atKey

    1. Scanning and listing atKeys

    There are two methods available for scanning and listing atKeys:

    1. getAtKeys which provides the list in Class format (i.e. List<AtKey>)

    2. getKeys which provides the list in String format (i.e. List<String>)

    Both methods accept the same parameters, only the return type is different. So we will show the more commonly used getAtKeys signature:

    regex

    A regular expression used to filter the list of atKeys.

    sharedBy

    Filter the list of atKeys to only include ones shared by a particular atSign.

    sharedWith

    Filter the list of atKeys to only include ones shared with a particular atSign.

    showHiddenKeys

    A boolean flag to enable the inclusion of hidden atKeys (default = false)

    Usage

    Get all available (non-hidden) atKeys:

    All atKeys which end with ".wavi" in the record identifier part:

    2. Retrieving atRecords by atKey

    To retrieve an atRecord, you must know the atKey and pass it to the get method.

    Calling this function will return an AtValue, and update the atKey passed to it with any changes to the metadata.

    Usage

    Deleting Data

    To delete data, simply call the delete method with the atKey for the atRecord to delete.

    Usage

    Additional Features

    See to learn about synchronization, which supports syncing of a local atServer, allowing CRUD operations to work even if the application has no internet access.

    API Docs

    You can find the API reference for the entire package available on .

    The AtClient class API reference is available .

    C

    You can find all of these examples on our .

    Table of Contents

    Introduction

  • Put Public atKey

    • 1. Create Public AtKey

    • 2. Call `atclient_put_public_key`

    • Example Application

  • Put Self atKey

    • 1. Create a Self atKey

    • 2. Call `atclient_put_self_key`

    • Example Application

  • Put Shared atKey

    • 1. Create a Shared atKey

    • 2. Call `atclient_put_shared_key`

    • Example Application

  • Get Public atKey

    • 1. Create Public AtKey

    • 2. Call `atclient_get_public_key`

    • 3. Free `value`

  • Get Self atKey

    • 1. Create a Self atKey

    • 2. Call `atclient_get_self_key`

    • 3. Free `value`

  • Get Shared atKey

    • 1. Create a Shared atKey

    • 2. Call `atclient_get_shared_key`

    • 3. Free `value`

  • Delete an atKey

    • 1. Create an AtKey

    • 2. Call `atclient_delete`

    • Example Application

  • Request Options

  • Introduction

    In this section, we will learn how to put, get, and delete an atKey.

    If you are unfamiliar with the different atKey types, check out our documentation on atRecords.

    Put Public atKey

    1. Create Public AtKey

    First, create a public atKey. It is important to note that the shared_by atSign should be the same as the authenticated atSign in the application.

    2. Call `atclient_put_public_key`

    Next, simply *put* the value into your atServer. Since we are putting a public value into our atServer, no data will be encrypted and this data will be available for any atSign to get.

    We will pass NULL into the request_options and commit_id parameters because we want to use the default options for now and we don't particularly care about the commit_id that it returns, but you could receive it if you would like.

    This function returns an int for error handling, in which a non-zero exit code indicates an error.

    Example Application

    Put Self atKey

    1. Create a Self atKey

    2. Call `atclient_put_self_key`

    This will put a value specially encrypted for your atServer that only the atSign's atKeys can decrypt.

    We will pass NULL into the request_options and commit_id parameters because we want to use the default options for now and we don't particularly care about the commit_id that it returns, but you could receive it if you would like.

    This function will return an int for error handling, in which a non-zero exit code indicates an error.

    Example Application

    Put Shared atKey

    1. Create a Shared atKey

    2. Call `atclient_put_shared_key`

    This function will put our string value into the atServer. Since we are using a Shared atKey, that means only the shared_by and shared_with atSign will be able to decrypt this value.

    We will pass NULL into the request_options and commit_id parameters because we want to use the default options for now and we don't particularly care about the commit_id that it returns, but you could receive it if you would like.

    This function returns an int for error handling, in which a non-zero exit code indicates an error.

    Example Application

    Get Public atKey

    1. Create Public AtKey

    First, create a public atKey. It is important to note that the shared_by atSign should be the same as the authenticated atSign in the application.

    2. Call `atclient_get_public_key`

    We will create a variable named value and pass the address to it in our atclient_get_public_key function call. The function will allocate memory for us and populate that variable for us.

    We will pass NULL into the request_options parameter because we want to use the default options for now.

    This function returns an int for error handling, in which a non-zero exit code indicates an error.

    3. Free `value`

    Once we are done with the value itself, we should free it to avoid any memory leaks.

    Example Application

    Get Self atKey

    1. Create a Self atKey

    2. Call `atclient_get_self_key`

    We will create a variable named value and pass the address to it in our atclient_get_self_key function call. The function will allocate memory for us and populate that variable for us.

    We will pass NULL into the request_options parameter because we want to use the default options for now.

    This function returns an int for error handling, in which a non-zero exit code indicates an error.

    3. Free `value`

    Once we are done with the value itself, we should free it to avoid any memory leaks.

    Example Application

    Get Shared atKey

    1. Create a Shared atKey

    2. Call `atclient_get_shared_key`

    We will create a variable named value and pass the address to it in our atclient_get_shared_key function call. The function will allocate memory for us and populate that variable for us.

    We will pass NULL into the request_options parameter because we want to use the default options for now.

    This function returns an int for error handling, in which a non-zero exit code indicates an error.

    3. Free `value`

    Once we are done with the value itself, we should free it to avoid any memory leaks.

    Example Application

    Delete an atKey

    1. Create an AtKey

    First step is to create the atKey that you wish to delete. This can be of any atKey type (public, self, or shared). What is important to note is that you can only delete an atKey that you own (which means that the authenticated atSign is the same as the shared_by atSign). This should be obvious because you can only delete atKeys that have once been created by you. Only the rightful owners of the atKey that was created can delete it.

    For the sake of this demo, we will create a Shared atKey.

    2. Call `atclient_delete`

    atclient_delete will delete the atKey from your atServer.

    We will pass NULL to the request_options and commit_id parameter because we want to use the default options for now and we do not care about the commit_id. You can receive the commit_id if you would like by passing an int pointer.

    It is pointless to set any metadata to the atKey when deleting. Metadata is only useful when getting (you can read any metadata that the key possesses) and putting (you can modify the atKey's behavior). When you are deleting, no metadata is used.

    This functions returns an int for error handling. A non-zero exit code indicates an error.

    Example Application

    Request Options

    Under Construction

    isolate
    at_daemon_server
    atKey
    reference
    pub
    here
    GitHub
    Additional Features
    AtClient atClient = atClientManager.atClient;
    String currentAtSign = atClient.getCurrentAtSign()!;
    AtKey myID = AtKey.self('phone', namespace: 'wavi',
                            sharedBy: currentAtSign).build();
    String dataToStore = "123-456-7890";
    bool res = await atClient.put(myID, dataToStore);
    put signature
    Future<bool> put(
        AtKey key,
        dynamic value,
        {bool isDedicated = false,
        PutRequestOptions? putRequestOptions});
    List<int> binaryData = [1, 2, 3, 4];
    bool res = await atClient.put(myID, binaryData);
    getAtKeys signature
    Future<List<AtKey>> getAtKeys(
        {String? regex,
        String? sharedBy,
        String? sharedWith,
        bool showHiddenKeys = false});
    List<AtKey> allIDs = await atClient.getAtKeys();
    List<AtKey> waviIDs = await atClient.getAtKeys(regex: '^.*\.wavi@.+$');
    get signature
    Future<AtValue> get(
        AtKey key,
        {bool isDedicated = false,
        GetRequestOptions? getRequestOptions});
    AtValue atValue = await atClient.get(myID);
    String? text = atValue.value;
    delete signature
    Future<bool> delete(
        AtKey key,
        {bool isDedicated = false});
    bool res = await atClient.delete(myID);
    atclient_atkey my_public_atkey;
    atclient_atkey_init(&my_public_atkey);
    
    const char *atkey_key = "phone";
    const char *atkey_shared_by = "@jeremy_0";
    const char *atkey_namespace = "c_demos";
    
    if (atclient_atkey_create_public_key(&my_public_atkey, atkey_key, atkey_shared_by, atkey_namespace) != 0)
    {
      // an error occurred
    }
    const char *atkey_value = "123-456-7890";
    
    if (atclient_put_public_key(&atclient, &my_public_atkey, atkey_value, NULL, NULL) != 0)
    {
        // an error occurred
    }
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer99"
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient;
        atclient_init(&atclient);
    
        atclient_atkey my_public_atkey;
        atclient_atkey_init(&my_public_atkey);
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_pkam_authenticate(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_namespace = "c_demos";
    
        if ((exit_code = atclient_atkey_create_public_key(&my_public_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0)
        {
            goto exit;
        }
    
        const char *atkey_value = "123-456-7890";
    
        /*
         * atclient_put_self_key lets you put a key-value pair in your atSign's atServer.
         * For our purposes, we will pass `NULL` for the request options and the commit id. 
         * We want to use the default options and we don't want to receive and
         * store the commit id.
         */
        if ((exit_code = atclient_put_public_key(&atclient, &my_public_atkey, atkey_value, NULL, NULL)) != 0)
        {
            goto exit;
        }
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient);
        atclient_atkey_free(&my_public_atkey);
        return exit_code;
    }
    }
    atclient_atkey my_self_atkey;
    atclient_atkey_init(&my_self_atkey);
    
    const char *atkey_key = "phone";
    const char *atkey_shared_by = "@jeremy_0";
    const char *atkey_namespace = "c_demos";
    
    if (atclient_atkey_create_self_key(&my_self_atkey, atkey_key, atkey_shared_by, atkey_namespace) != 0)
    {
        // an error occurred
    }
    const char *atkey_value = "123-456-7890";
    
    if (atclient_put_self_key(&atclient, &my_self_atkey, atkey_value, NULL, NULL) != 0)
    {
      // an error occurred
    }
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer99"
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient;
        atclient_init(&atclient);
    
        atclient_atkey my_self_atkey;
        atclient_atkey_init(&my_self_atkey);
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_pkam_authenticate(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_namespace = "c_demos";
    
        if ((exit_code = atclient_atkey_create_self_key(&my_self_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0)
        {
            goto exit;
        }
    
        const char *atkey_value = "123-456-7890";
    
        /*
         * atclient_put_self_key lets you put a key-value pair in your atSign's atServer.
         * For our purposes, we will pass `NULL` for the request options and the commit id. 
         * We want to use the default options and we don't want to receive and
         * store the commit id.
         */
        if ((exit_code = atclient_put_self_key(&atclient, &my_self_atkey, atkey_value, NULL, NULL)) != 0)
        {
            goto exit;
        }
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient);
        atclient_atkey_free(&my_self_atkey);
        return exit_code;
    }
    }
    
    atclient_atkey my_shared_atkey;
    atclient_atkey_init(&my_shared_atkey);
    
    const char *atkey_key = "phone";
    const char *atkey_shared_by = "@jeremy_0";
    const char *atkey_shared_with = "@soccer0";
    const char *atkey_namespace = "c_demos";
    if (atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace) != 0)
    {
        // an error occurred
    }
    const char *atkey_value = "123-456-7890";
    if (atclient_put_shared_key(&atclient, &my_shared_atkey, atkey_value, NULL, NULL) != 0)
    {
        // an error occurred
    }
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer99"
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient;
        atclient_init(&atclient);
    
        atclient_atkey my_shared_atkey;
        atclient_atkey_init(&my_shared_atkey);
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_pkam_authenticate(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_shared_with = "@soccer0";
        const char *atkey_namespace = "c_demos";
    
        if ((exit_code = atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace)) != 0)
        {
            goto exit;
        }
    
        const char *atkey_value = "123-456-7890";
    
        /*
         * atclient_put_self_key lets you put a key-value pair in your atSign's atServer.
         * For our purposes, we will pass `NULL` for the request options and the commit id. 
         * We want to use the default options and we don't want to receive and
         * store the commit id.
         */
        if ((exit_code = atclient_put_shared_key(&atclient, &my_shared_atkey, atkey_value, NULL, NULL)) != 0)
        {
            goto exit;
        }
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient);
        atclient_atkey_free(&my_shared_atkey);
        return exit_code;
    }
    }
    atclient_atkey my_public_atkey;
    atclient_atkey_init(&my_public_atkey);
    
    const char *atkey_key = "phone";
    const char *atkey_shared_by = "@jeremy_0";
    const char *atkey_namespace = "c_demos";
    
    if (atclient_atkey_create_public_key(&my_public_atkey, atkey_key, atkey_shared_by, atkey_namespace) != 0)
    {
      // an error occurred
    }
    char *value = NULL;
    if (atclient_get_public_key(&atclient, &my_public_atkey, &value, NULL) != 0)
    {
        // an error occurred
    }
    free(value);
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer99"
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient;
        atclient_init(&atclient);
    
        atclient_atkey my_public_atkey;
        atclient_atkey_init(&my_public_atkey);
    
        /*
         * Let's declare a null pointer which will later be allocated by our `atclient_get_public_key` function. It is our responsibility to free it once we are
         * done with it.
         */
        char *value = NULL;
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_pkam_authenticate(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_namespace = "c_demos";
    
        if ((exit_code = atclient_atkey_create_public_key(&my_public_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0)
        {
            goto exit;
        }
    
        /*
         * `atclient_get_public_key` will allocate memory for the `value` pointer. It is our responsibility to free it once we are done with it.
         * We will pass `NULL` into the request_options argument for now and just use the default request options.
         */
        if ((exit_code = atclient_get_public_key(&atclient, &my_public_atkey, &value, NULL)) != 0)
        {
            goto exit;
        }
    
        atlogger_log("3d-get-public-atkey", ATLOGGER_LOGGING_LEVEL_INFO, "value: \"%s\"\n", value);
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient);
        atclient_atkey_free(&my_public_atkey);
        free(value);
        return exit_code;
    }
    }
    atclient_atkey my_self_atkey;
    atclient_atkey_init(&my_self_atkey);
    
    const char *atkey_key = "phone";
    const char *atkey_shared_by = "@jeremy_0";
    const char *atkey_namespace = "c_demos";
    
    if (atclient_atkey_create_self_key(&my_self_atkey, atkey_key, atkey_shared_by, atkey_namespace) != 0)
    {
        // an error occurred
    }
    char *value = NULL;
    if (atclient_get_self_key(&atclient, &my_self_atkey, &value, NULL) != 0)
    {
        // an error occurred
    }
    free(value);
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer99"
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient;
        atclient_init(&atclient);
    
        atclient_atkey my_self_atkey;
        atclient_atkey_init(&my_self_atkey);
    
        /*
         * Let's declare a null pointer which will later be allocated by our `atclient_get_public_key` function. It is our responsibility to free it once we are
         * done with it.
         */
        char *value = NULL;
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_pkam_authenticate(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_namespace = "c_demos";
    
        if ((exit_code = atclient_atkey_create_self_key(&my_self_atkey, atkey_key, atkey_shared_by, atkey_namespace)) != 0)
        {
            goto exit;
        }
    
        /*
         * `atclient_get_self_key` will allocate memory for the `value` pointer. It is our responsibility to free it once we are done with it.
         * We will pass `NULL` into the request_options argument for now and just use the default request options.
         */
        if ((exit_code = atclient_get_self_key(&atclient, &my_self_atkey, &value, NULL)) != 0)
        {
            goto exit;
        }
    
        atlogger_log("3E-get-self-atkey", ATLOGGER_LOGGING_LEVEL_INFO, "value: \"%s\"\n", value);
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient);
        atclient_atkey_free(&my_self_atkey);
        free(value);
        return exit_code;
    }
    }
    atclient_atkey my_shared_atkey;
    atclient_atkey_init(&my_shared_atkey);
    
    const char *atkey_key = "phone";
    const char *atkey_shared_by = "@jeremy_0";
    const char *atkey_shared_with = "@soccer0";
    const char *atkey_namespace = "c_demos";
    if (atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace) != 0)
    {
        // an error occurred
    }
    char *value = NULL;
    if (atclient_get_shared_key(&atclient, &my_self_atkey, &value, NULL) != 0)
    {
        // an error occurred
    }
    free(value)
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@soccer99"
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient;
        atclient_init(&atclient);
    
        atclient_atkey my_shared_atkey;
        atclient_atkey_init(&my_shared_atkey);
    
        /*
         * Let's declare a null pointer which will later be allocated by our `atclient_get_public_key` function. It is our responsibility to free it once we are
         * done with it.
         */
        char *value = NULL;
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_pkam_authenticate(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_shared_with = "@soccer0";
        const char *atkey_namespace = "c_demos";
    
        if ((exit_code = atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace)) != 0)
        {
            goto exit;
        }
    
        /*
         * `atclient_get_self_key` will allocate memory for the `value` pointer. It is our responsibility to free it once we are done with it.
         * We will pass `NULL` into the request_options argument for now and just use the default request options.
         */
        if ((exit_code = atclient_get_shared_key(&atclient, &my_shared_atkey, &value, NULL)) != 0)
        {
            goto exit;
        }
    
        atlogger_log("3E-get-self-atkey", ATLOGGER_LOGGING_LEVEL_INFO, "value: \"%s\"\n", value);
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient);
        atclient_atkey_free(&my_shared_atkey);
        free(value);
        return exit_code;
    }
    }
    atclient_atkey my_shared_atkey;
    atclient_atkey_init(&my_shared_atkey);
    
    const char *atkey_key = "phone";
    const char *atkey_shared_by = "@jeremy_0";
    const char *atkey_shared_with = "@soccer0";
    const char *atkey_namespace = "c_demos";
    if (atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace) != 0)
    {
        // an error occurred
    }
    if (atclient_delete(&atclient, &my_shared_atkey, NULL, NULL) != 0) {
        // an error occurred
    }
    #include <atclient/atclient.h>
    #include <atclient/atclient_utils.h>
    #include <atclient/constants.h>
    #include <atlogger/atlogger.h>
    #include <stdlib.h>
    
    #define ATSIGN "@jeremy_0"
    
    int main()
    {
        int exit_code = -1;
    
        atlogger_set_logging_level(ATLOGGER_LOGGING_LEVEL_DEBUG);
    
        char *atserver_host = NULL;
        int atserver_port = 0;
    
        atclient_atkeys atkeys;
        atclient_atkeys_init(&atkeys);
    
        atclient atclient;
        atclient_init(&atclient);
    
        atclient_atkey my_shared_atkey;
        atclient_atkey_init(&my_shared_atkey);
    
        if ((exit_code = atclient_utils_find_atserver_address(ATCLIENT_ATDIRECTORY_PRODUCTION_HOST, ATCLIENT_ATDIRECTORY_PRODUCTION_PORT, ATSIGN, &atserver_host, &atserver_port)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_utils_populate_atkeys_from_homedir(&atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        if ((exit_code = atclient_pkam_authenticate(&atclient, atserver_host, atserver_port, &atkeys, ATSIGN)) != 0)
        {
            goto exit;
        }
    
        const char *atkey_key = "phone";
        const char *atkey_shared_by = ATSIGN;
        const char *atkey_shared_with = "@soccer0";
        const char *atkey_namespace = "c_demos";
    
        if ((exit_code = atclient_atkey_create_shared_key(&my_shared_atkey, atkey_key, atkey_shared_by, atkey_shared_with, atkey_namespace)) != 0)
        {
            goto exit;
        }
    
        /*
         * `atclient_delete` will delete this shared atkey from our atServer. It is important to note that only the `shared_by` atSign can delete the shared atKey
         * When deleting, the `shared_by` atSign should always be the authenticated atSign in the `atclient` object.
         * We will pass `NULL` to the request_options and commit_id parameters because we want to use the default request_options and we don't care about the
         * commit_id we get back.
         */
        if ((exit_code = atclient_delete(&atclient, &my_shared_atkey, NULL, NULL) != 0)) {
            goto exit;
        }
    
        exit_code = 0;
    exit:
    {
        free(atserver_host);
        atclient_atkeys_free(&atkeys);
        atclient_free(&atclient);
        atclient_atkey_free(&my_shared_atkey);
        return exit_code;
    }
    }
    Example Application
    Example Application
    Example Application