{% note alert %}
This smart contract is presented here as an example, do not use it for production without prior verification.
{% endnote %}
If you don't have a local GlobalForce node installed, use the deployment tools at docs.globalforce.io
Download msg.cpp
Download msg.abi
messenger is a simple, fully on-chain private messaging smart contract for GlobalForce blockchains.
It allows any user to send, receive, and erase messages using only GlobalForce actions.
All messages and notifications are stored on-chain, enabling DApp or wallet integrations that provide private inbox and outbox functionality.
Send a message from one account to another.
sendmsg(account_name from, account_name to, std::string msg)
from: Sender (must sign the transaction)
to: Recipient
msg: The message text (must not be empty)
Receive and delete a message from your inbox.
receivemsg(account_name to, uint64_t id)
to: The recipient (must sign)
id: The unique message/notification ID
Delete a message you previously sent (can be used before it’s read).
erasemsg(account_name from, uint64_t id)
from: The sender (must sign)
id: The unique message/notification ID
When a message is sent, a notification is stored in a global table (so the recipient can quickly find new messages), and the message itself is stored in the sender’s scoped message table.
Each message and notification shares a unique ID.
To receive a message, the recipient calls receivemsg, which deletes both the notification and the message.
The sender can also delete a message (before it is read) by calling erasemsg.
message (scoped by sender):
id: unique message id
to: recipient
text: message body
send_at: timestamp
type: reserved for future use
notification (global scope):
id: unique notification/message id
from: sender
to: recipient
Secondary index by to enables efficient inbox lookups.
Send a message:
cleos push action messengeracc sendmsg '["alice", "bob", "Hello, Bob!"]' -p alice
View incoming notifications (inbox) for Bob:
cleos get table messengeracc messengeracc notification --index 2 --key-type name --lower bob --upper bob
Bob receives (deletes) a message:
cleos push action messengeracc receivemsg '["bob", 7]' -p bob
Alice deletes a sent message (before it is read):
cleos push action messengeracc erasemsg '["alice", 7]' -p alice
Authorization: Only the sender can erase their sent messages. Only the recipient can receive messages addressed to them.
Message Privacy: Message content is stored on-chain and can be read by anyone with access to blockchain state. (For privacy, encrypt message text before sending.)
Notification Table: Enables DApps to build inbox/outbox user interfaces efficiently.
Resource Usage: Each message/notification consumes contract RAM (paid by sender).
Not truly private: On-chain data is public. For confidential messaging, users should encrypt message text. No attachments or advanced metadata (but can be added via the type field or message format). No group chat (one-to-one messages only). No message pagination (but easy to implement via message ids).
Group messages Message encryption utilities Message expiry and archiving DApp wallet notification integration
MIT or similar permissive license.
/**
* @file
* EOSIO Messenger Contract
* Enables sending, receiving, and deleting private messages on chain.
*/
#include <utility>
#include <vector>
#include <string>
#include <eosiolib/eosio.hpp> // EOSIO contract base class and macros
#include <eosiolib/asset.hpp> // For asset types (not used in this contract)
#include <eosiolib/contract.hpp> // EOSIO contract base
#include <eosiolib/time.hpp> // EOSIO time and time_point_sec
#include <eosiolib/print.hpp> // EOSIO print for debugging (not used in production)
#include <eosiolib/transaction.hpp> // For inline/deferred transactions (not used here)
using namespace eosio;
/**
* @class messenger
* Implements a simple on-chain messenger with message sending, receiving, and deletion.
*/
class messenger : public eosio::contract
{
public:
using contract::contract;
// Contract constructor
messenger(account_name self) : contract(self) {}
/**
* @brief Send a message from one account to another.
* - Stores a notification and a message record on chain.
* - The notification enables the recipient to find new incoming messages.
* @param from Sender account (must authorize)
* @param to Recipient account
* @param msg Message text (must not be empty)
* @abi action
*/
void sendmsg(const account_name from,
const account_name to,
const std::string msg)
{
require_auth(from); // Ensure sender authorized
eosio_assert(msg.size() > 0, "Empty message");
// (Optional) Check that recipient account "to" exists
notification_table notifications(_self, _self); // Notifications table (global scope)
message_table messages(_self, from); // Messages table (scoped to sender)
uint64_t newid = notifications.available_primary_key(); // Unique message/notification id
// Add a notification for the recipient (so they can find new messages)
notifications.emplace(from, [&](auto &n) {
n.id = newid;
n.from = from;
n.to = to;
});
// Store the actual message (including text and timestamp)
messages.emplace(from, [&](auto &m) {
m.id = newid;
m.to = to;
m.text = msg;
m.send_at = eosio::time_point_sec(now());
m.type = 0; // Reserved for future message type expansion
});
}
/**
* @brief Receive (and delete) a message that was sent to the recipient.
* - The notification and message are deleted.
* - Only the recipient can call this action.
* @param to The recipient account (must authorize)
* @param id The message/notification id
* @abi action
*/
void receivemsg(const account_name to, uint64_t id)
{
require_auth(to);
notification_table notifications(_self, _self);
auto itr_notif = notifications.find(id);
eosio_assert(itr_notif != notifications.end(), "Notification not found");
const auto ¬if = *itr_notif;
eosio_assert(notif.to == to, "Message not addressed to your account");
message_table messages(_self, notif.from); // Message stored in sender's scope
auto itr_msg = messages.find(id);
eosio_assert(itr_msg != messages.end(), "Message not found");
// Remove notification and message
notifications.erase(itr_notif);
messages.erase(itr_msg);
}
/**
* @brief Delete a message sent by the sender (without recipient reading it).
* - Only the sender can call this action.
* - The notification and the message are deleted.
* @param from Sender account (must authorize)
* @param id The message/notification id
* @abi action
*/
void erasemsg(const account_name from, uint64_t id)
{
require_auth(from);
notification_table notifications(_self, _self);
auto itr_notif = notifications.find(id);
eosio_assert(itr_notif != notifications.end(), "Notification not found");
const auto ¬if = *itr_notif;
eosio_assert(notif.from == from, "Message was not sent from your account");
message_table messages(_self, from); // Message stored in sender's scope
auto itr_msg = messages.find(id);
eosio_assert(itr_msg != messages.end(), "Message not found");
// Remove notification and message
notifications.erase(itr_notif);
messages.erase(itr_msg);
}
private:
/**
* @struct message
* @brief Table structure for messages sent from this user.
* - Scoped by sender's account.
* - Stores recipient, message text, send time, and type.
* @abi table message i64
*/
struct message
{
uint64_t id; // Unique message id
account_name to; // Recipient account
std::string text; // Message body
eosio::time_point_sec send_at; // Timestamp of when sent
uint8_t type; // Reserved for future message types
uint64_t primary_key() const { return id; }
EOSLIB_SERIALIZE(message, (id)(to)(text)(send_at)(type))
};
typedef eosio::multi_index<N(message), message> message_table;
/**
* @struct notification
* @brief Table structure for notifications of new messages.
* - Stored globally (scope: contract).
* - Each notification has sender and recipient accounts.
* @abi table notification i64
*/
struct notification
{
uint64_t id; // Unique notification id (matches message id)
account_name from; // Sender account
account_name to; // Recipient account
uint64_t primary_key() const { return id; }
account_name get_to_key() const { return to; }
EOSLIB_SERIALIZE(notification, (id)(from)(to))
};
typedef eosio::multi_index<N(notification), notification,
eosio::indexed_by<N(to),
eosio::const_mem_fun<
notification,
account_name,
¬ification::get_to_key>>>
notification_table;
};
EOSIO_ABI(messenger, (sendmsg)(receivemsg)(erasemsg))
{
"____comment": "This file was generated by eosio-abigen. DO NOT EDIT - 2018-08-26T20:16:23",
"version": "eosio::abi/1.0",
"types": [],
"structs": [{
"name": "message",
"base": "",
"fields": [{
"name": "id",
"type": "uint64"
},{
"name": "to",
"type": "name"
},{
"name": "text",
"type": "string"
},{
"name": "send_at",
"type": "time_point_sec"
},{
"name": "type",
"type": "uint8"
}
]
},{
"name": "notification",
"base": "",
"fields": [{
"name": "id",
"type": "uint64"
},{
"name": "from",
"type": "name"
},{
"name": "to",
"type": "name"
}
]
},{
"name": "sendmsg",
"base": "",
"fields": [{
"name": "from",
"type": "name"
},{
"name": "to",
"type": "name"
},{
"name": "msg",
"type": "string"
}
]
},{
"name": "receivemsg",
"base": "",
"fields": [{
"name": "to",
"type": "name"
},{
"name": "id",
"type": "uint64"
}
]
},{
"name": "erasemsg",
"base": "",
"fields": [{
"name": "from",
"type": "name"
},{
"name": "id",
"type": "uint64"
}
]
}
],
"actions": [{
"name": "sendmsg",
"type": "sendmsg",
"ricardian_contract": ""
},{
"name": "receivemsg",
"type": "receivemsg",
"ricardian_contract": ""
},{
"name": "erasemsg",
"type": "erasemsg",
"ricardian_contract": ""
}
],
"tables": [{
"name": "message",
"index_type": "i64",
"key_names": [
"id"
],
"key_types": [
"uint64"
],
"type": "message"
},{
"name": "notification",
"index_type": "i64",
"key_names": [
"id"
],
"key_types": [
"uint64"
],
"type": "notification"
}
],
"ricardian_clauses": [],
"error_messages": [],
"abi_extensions": []
}
{% note alert %} This smart contract is presented here as an example, do not use it for production without prior verification. {% endnote %} If you don't have a