Compliance Server
The task of an anchor is handling regulatory compliance, like Anti-Money Laundering (AML). To accomplish that, you should use the Stellar compliance protocol, a standard way to exchange compliance information and pre-approve a transaction with another financial institution.
You can write your own server that matches the compliance protocol, but Stellar also provides a compliance server that takes care of most of the work for you.
Your bridge server contacts your compliance server in order to authorize a transaction before sending it. Your compliance server uses the compliance protocol to clear the transaction with the recipient’s compliance server, then lets the bridge server know the transaction is ok to send.
When another compliance server contacts yours to clear a transaction, a series of callbacks are used to check the information with you. Later, when your bridge server receives a transaction, it contacts your compliance server to verify that it was cleared.
Create a Database
The compliance server requires a MySQL or PostgreSQL database in order to save transaction and compliance information. Create a new database named stellar_compliance
and a user to manage it. You don’t need to add any tables; the server includes a command to configure and update your database.
Download and Configure Compliance Server
Start by downloading the latest compliance server for your platform and install the executable anywhere you like. In the same directory, create a file named config_compliance.toml
. This will store the configuration for the compliance server. It should look something like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
external_port = 8003
internal_port = 8004
# Set this to `true` if you need to check the information of a person receiving
# a payment you are sending (if false, only the sender will be checked). For
# more information, see the callbacks section below.
needs_auth = false
network_passphrase = "Futurenet XDBChain Network ; October 2023"
[database]
type = "mysql" # Or "postgres" if you created a PostgreSQL database
url = "dbuser:dbpassword@/stellar_compliance"
[keys]
# This should be the secret seed for your base account (or another account that
# can authorize transactions from your base account).
signing_seed = "SAV75E2NK7Q5JZZLBBBNUPCIAKABN64HNHMDLD62SZWM6EBJ4R7CUNTZ"
encryption_key = "SAV75E2NK7Q5JZZLBBBNUPCIAKABN64HNHMDLD62SZWM6EBJ4R7CUNTZ"
[callbacks]
sanctions = "http://localhost:8005/compliance/sanctions"
ask_user = "http://localhost:8005/compliance/ask_user"
fetch_info = "http://localhost:8005/compliance/fetch_info"
# The compliance server must be available via HTTPS. Specify your SSL
# certificate and key here. If the server is behind a proxy or load balancer
# that implements HTTPS, you can omit this section.
[tls]
certificate_file = "server.crt"
private_key_file = "server.key"
The configuration file lists both an external_port
and an internal_port
. The external port must be publicly accessible. This is the port that other organizations will contact in order to determine whether you will accept a payment.
The internal port should not be publicly accessible. It is the port through which you initiate compliance operations and transmit private information. It’s up to you to keep this port secure through a firewall, a proxy, or some other means.
You’ll also need to tell your bridge server that you now have a compliance server it can use. Update config_bridge.toml
with the address of your compliance server’s internal port:
1
2
3
4
5
6
port = 8001
horizon = "https://horizon.futurenet.xdbchain.com"
network_passphrase = "Futurenet XDBChain Network ; October 2023"
compliance = "https://your_org.com:8004"
# ...the rest of your configuration...
Implement Compliance Callbacks
In the server configuration file, there are three callback URLs, much like those for the bridge server. They are HTTP POST URLs that will be sent form-encoded data:
fetch_info
is sent a federation address (liketunde_adebayo*your_org.com
) and should return all the information necessary for another organization to perform compliance checks. It can be any data you deem reasonable and must be formatted as JSON.When you are sending a payment, it will be called to get information on the customer who is sending the payment in order to send it to the receiving organization. When receiving a payment, it will be called if the sending organization has requested information on the receiver to do its own compliance checks (based on the
needs_auth
configuration).1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
app.post('/compliance/fetch_info', function (request, response) { var addressParts = response.body.address.split('*'); var friendlyId = addressParts[0]; // You need to create `accountDatabase.findByFriendlyId()`. It should look // up a customer by their XDBchain account and return account information. accountDatabase.findByFriendlyId(friendlyId) .then(function(account) { // This can be any data you determine is useful and is not limited to // these three fields. response.json({ name: account.fullName, address: account.address, date_of_birth: account.dateOfBirth }); response.end(); }) .catch(function(error) { console.error('Fetch Info Error:', error); response.status(500).end(error.message); }); });
sanctions
is given information about the person who is sending a payment to you or one of your customers. This is the same data the sending server would have received from its ownfetch_info
callback. The HTTP response code it produces indicates whether the payment will be accepted (status200
), denied (status403
), or if you need additional time for processing (status202
).1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
app.post('/compliance/sanctions', function (request, response) { var sender = JSON.parse(request.body.sender); // You need to create a function to check whether there are any sanctions // against someone. sanctionsDatabase.isAllowed(sender) .then(function() { response.status(200).end(); }) .catch(function(error) { // In this example, we're assuming `isAllowed` returns an error with a // `type` property that indicates the kind of error. Your systems may // work differently; just return the same HTTP status codes. if (error.type === 'DENIED') { response.status(403).end(); } else if (error.type === 'UNKNOWN') { // If you need to wait and perform manual checks, you'll have to // create a way to do that as well notifyHumanForManualSanctionsCheck(sender); // The value for `pending` is a time to check back again in seconds response.status(202).json({pending: 3600}).end(); } else { response.status(500).end(error.message); } }); });
ask_user
is called when receiving a payment if the sender has requested information about the receiver. Its return code indicates whether you will send that information (fetch_info
is then called to actually get the info). It is sent information on both the payment and the sender.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
app.post('/compliance/ask_user', function (request, response) { var sender = JSON.parse(request.body.sender); // You can do any checks that make sense here. For example, you may not // want to share information with someone who has sanctions as above: sanctionsDatabase.isAllowed(sender) .then(function() { response.status(200).end(); }) .catch(function(error) { if (error.type === 'UNKNOWN') { // If you need to wait and perform manual checks, you'll have to // create a way to do that as well. notifyHumanForManualInformationSharing(sender); // The value for `pending` is a time to check back again in seconds response.status(202).json({pending: 3600}).end(); } else { response.status(403).end(); } }); });
To keep things simple, we’ll add all three callbacks to the same server we are using for the bridge server callbacks. However, you can implement them on any service that makes sense in your infrastructure. Just make sure they’re reachable at the URLs in your config file.
Update Stellar.toml
When other organizations need to contact your compliance server to authorize a payment to one of your customers, they consult your domain’s stellar.toml
file for the address, just as when finding your federation server.
For compliance operations, you’ll need to list two new properties in your stellar.toml
:
1
2
3
FEDERATION_SERVER = "https://www.your_org.com:8002/federation"
AUTH_SERVER = "https://www.your_org.com:8003"
SIGNING_KEY = "GAIGZHHWK3REZQPLQX5DNUN4A32CSEONTU6CMDBO7GDWLPSXZDSYA4BU"
AUTH_SERVER
is the address for the external port of your compliance server. Like your federation server, this can be any URL you like, but it must support HTTPS and use a valid SSL certificate.1
SIGNING_KEY
is the public key that matches the secret seed specified for signing_seed
in your compliance server’s configuration. Other organizations will use it to verify that messages were actually sent by you.
Start the Server
Before starting the server the first time, the tables in your database need to be created. Running compliance server with the --migrate-db
argument will make sure everything is set to go:
1
./compliance --migrate-db
Each time you update the compliance server to a new version, you should run this command again. It will upgrade your database in case anything needs to be changed.
Now that your database is fully set up, you can start the compliance server by running:
1
./compliance
Try It Out
Now that you’ve got your compliance server set up and ready to verify transactions, you’ll want to test it by sending a payment to someone who is running their own compliance and federation servers.
The easiest way to do this is to simply test a payment from one of your own customers to another. Your compliance, federation, and bridge servers will perform both the sending and receiving sides of the transaction.
Send a payment through your bridge server, but this time, use federated addresses for the sender and receiver and an extra_memo
2 to trigger compliance checks:
1
2
3
4
5
6
7
8
9
10
# NOTE: `extra_memo` is required for compliance (use it instead of `memo`)
curl -X POST -d \
"amount=1&\
asset_code=USD&\
asset_issuer=GAIUIQNMSXTTR4TGZETSQCGBTIF32G2L5P4AML4LFTMTHKM44UHIN6XQ&\
destination=amy*your_org.com&\
source=SAV75E2NK7Q5JZZLBBBNUPCIAKABN64HNHMDLD62SZWM6EBJ4R7CUNTZ&\
sender=tunde_adebayo*your_org.com&\
extra_memo=Test%20transaction" \
http://localhost:8001/payment
For a more realistic test, set up a duplicate copy of your bridge, federation, and compliance servers at a different domain and send a payment to them!
Requiring that public services are available via SSL helps keep things secure. While testing, you can get free certificates from http://letsencrypt.org. You can also generate your own self-signed certificates, but you must add them to all the computers in your tests. ↩
Compliance transactions with the bridge server don’t support the
memo
field. The actual transaction’smemo
will store a hash used to verify that the transaction submitted to the XDBchain network matches the one agreed upon during initial compliance checks. Yourextra_memo
data will be transmitted instead during the compliance checks. For details, see the compliance protocol. ↩