The task of an Institutional Host is handling regulatory regulation, like Anti-Money Laundering (AML). To accomplish that, you should use the HC Net regulation protocol, a standard way to exchange regulation information and pre-approve a transaction with another financial institution.
You can write your own server that matches the regulation protocol, but HCNet.org also provides a regulation server that takes care of most of the work for you.
Your communication server contacts your regulation server in order to authorize a transaction before sending it. Your regulation server uses the regulation protocol to clear the transaction with the recipient’s regulation server, then lets the communication server know the transaction is ok to send.
When another regulation server contacts yours to clear a transaction, a series of callbacks are used to check the information with you. Later, when your communication server receives a transaction, it contacts your regulation server to verify that it was cleared.
The regulation server requires a MySQL or PostgreSQL database in order to save transaction and regulation information. Create a new database named HCNet_regulation 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.
Start by downloading the latest regulation server for your platform and install the executable anywhere you like. In the same directory, create a file named config_regulation.toml. This will store the configuration for the regulation server. It should look something like:
Config_regulation.toml TOML
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 communication server that you now have a regulation server it can use. Update config_communication.toml with the address of your regulation server’s internal port:
Config_communication.toml TOML
In the server configuration file, there are three callback URLs, much like those for the communication server. They are HTTP POST URLs that will be sent form-encoded data:
Implementing the fetch_info callback
app.post('/regulation/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 Hashcash 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);
});
});
@POST
@Path("regulation/fetch_info")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response fetchInfo(
@FormParam("address") String address) {
String friendlyId = address.split("\\*", 2)[0];
// You need to create `accountDatabase.findByFriendlyId()`. It should
// find customers by their HC Net account and return account information.
try {
Account account = accountDatabase.findByFriendlyId(friendlyId);
return Response.ok(
// This can be any data you determine is useful and is not limited to
// these three fields.
Json.createObjectBuilder()
HttpClient httpClient = HttpClients.createDefault();
.add("name", account.fullName)
.add("address", account.address)
.add("date_of_birth", account.dateOfBirth)
.build())
.build();
)
} catch (Exception error) {
System.out.println(
String.format("Could not find account: %s", address));
return
Response.status(500).entity(error.getMessage()).build();
}
}
Implementing the sanctions callback
app.post('/regulation/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);
}
});
});
import java.io.*;
import javax.json.Json;
import javax.json.JsonObject;
import javax.json.JsonReader;
@POST
@Path("regulation/sanctions")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response sanctions(@FormParam("sender") String sender) {
JsonReader jsonReader = Json.createReader(new StringReader(sender));
JsonObject senderData = jsonReader.readObject();
jsonReader.close();(
// You need to create a function to check whether there are any sanctions
// against someone.
Permission permission = sanctionsDatabase.isAllowed(
senderData.getString("name"),
senderData.getString("address"),
senderData.getString("date_of_birth"));
// In this example, we're assuming `isAllowed` returns a Permissions enum
// that indicates whether someone is Allowed, Denied, or Unknown. Your
// systems may work differently; just return the same HTTP status codes.
if (permission.equals(Permission.Allowed)) {
return Response.ok().build();
}
else if (permission.equals(Permission.Denied)) {
return Response.status(403).build();
}
else {
// If you need to wait and perform manual checks, you'll have to implement
// a way to do that as well.
notifyHumanForManualSanctionsCheck(senderData);
// The value for `pending` is a time to check back again in seconds.
return Response.accepted(
Json.createObjectBuilder()
.add("pending", 3600)
.build())
.build();
}
}
Implementing the ask_user callback
app.post('/reulation/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 {
// create a way to do that as well
response.status(403).end();
}
});
});
@POST
@Path("regulation/ask_user")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_JSON)
public Response askUser(@FormParam("sender") String sender)
{
JsonReader jsonReader = Json.createReader(new StringReader(sender));
JsonObject senderData = jsonReader.readObject();
jsonReader.close();
// 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:
Permission permission = sanctionsDatabase.isAllowed(
senderData.getString("name"),
senderData.getString("address"),
senderData.getString("date_of_birth"));
if (permission.equals(Permission.Allowed)) {
return Response.ok().build();
}
else if (permission.equals(Permission.Denied)) {
return Response.status(403).build();
}
else {
// If you need to wait and perform manual checks, you'll have to create
// a way to do that as well.
notifyHumanForManualInformationSharing(senderData);
// The value for `pending` is a time to check back again in seconds.
return Response.accepted(
Json.createObjectBuilder()
.add("pending", 3600)
.build())
.build();
}
}
To keep things simple, we’ll add all three callbacks to the same server we are using for the communication 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.
When other organizations need to contact your regulation server to authorize a payment to one of your customers, they consult your domain’s HCNet.toml file for the address, just as when finding your mapping server.
For regulation operations, you’ll need to list two new properties in your
HCNet.toml:
HCNet.toml TOML
AUTH_SERVER is the address for the external port of your regulation server. Like your mapping 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.
Before starting the server the first time, the tables in your database need to be created. Running regulation server with the --migrate-db argument will make sure everything is set to go:
./regulation --migrate-db
Each time you update the regulation 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 regulation server by running:
./regulation
Now that you’ve got your regulation 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 regulation and mapping servers.
The easiest way to do this is to simply test a payment from one of your own customers to another. Your regulation,mapping, and communication servers will perform both the sending and receiving sides of the transaction.
Send a payment through your communication server, but this time, use mapped addresses for the sender and receiver and an extra_memo[2] to trigger compliance checks:
Send a Payment
# NOTE: `extra_memo` is required for regulation (use it instead of `memo`)
curl -X POST -d \
"id=unique_payment_id&\
amount=1&\
asset_code=USD&\
asset_issuer=GAPJN32J7O6EXXWNZ2P5VIMT7KFETQMZN27IILUSW7KZDZKCNRLQFON&\
destination=amy*your_org.com&\
source=S6EXXWNZ7J7EXXWNZ2P5VIMT7KAPJNMZN27MTLUSW7KZW7KCN2P5VIM&\
sender=tunde_adebayo*your_org.com&\
extra_memo=Test%20transaction" \
http://localhost:8001/payment
var request = require('request');
request.post({
url: 'http://localhost:8001/payment',
form: {
id: 'unique_payment_id',
amount: '1',
asset_code: 'USD',
asset_issuer: 'GAPJN32J7O6EXXWNZ2P5VIMT7KFETQMZN27IILUSW7KZDZKCNRLQFON',
destination: 'amy*your_org.com',
source: 'S6EXXWNZ7J7EXXWNZ2P5VIMT7KAPJNMZN27MTLUSW7KZW7KCN2P5VIM',
sender: 'tunde_adebayo*your_org.com',
// `extra_memo` is required for regulation (use it instead of `memo`)
extra_memo: 'Test transaction',
}
}, function(error, response, body) {
if (error || response.statusCode !== 200) {
console.error('ERROR!', error || body);
}
else {
console.log('SUCCESS!', body);
}
});
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.util.ArrayList;
import java.util.List;
public class PaymentRequest() {
public static void main(String [] args) {
HttpPost paymentRequest = new HttpPost("http://localhost:8001/payment");
List <NameValuePair> params = new ArrayList <NameValuePair>();
params.add(new BasicNameValuePair("id", "unique_payment_id"));
params.add(new BasicNameValuePair("amount", "1"));
params.add(new BasicNameValuePair("asset_code", "USD"));
params.add(new BasicNameValuePair("asset_issuer", "GAPJN32J7O6EXXWNZ2P5VIMT7KFETQMZN27IILUSW7KZDZKCNRLQFON"));
params.add(new BasicNameValuePair("destination", "amy*your_org.com"));
params.add(new BasicNameValuePair("source", "S6EXXWNZ7J7EXXWNZ2P5VIMT7KAPJNMZN27MTLUSW7KZW7KCN2P5VIM"));
params.add(new BasicNameValuePair("sender", "tunde_adebayo*your_org.com"));
// `extra_memo` is required for regulation (use it instead of `memo`)
params.add(new BasicNameValuePair("extra_memo", "Test transaction"));
HttpResponse response = httpClient.execute(paymentRequest);
HttpEntity entity = response.getEntity();
if (entity != null) {
String body = EntityUtils.toString(entity);
System.out.println(body);
}
}
}
For a more realistic test, set up a duplicate copy of your communication, mapping, and regulation servers at a different domain and send a payment to them!
Complying with Anti-Money Laundering (AML) laws requires financial institutions (FIs) to know not only who their customers are sending money to but who their customers are receiving money from. In some jurisdictions banks are able to trust the AML procedures of other licensed banks. In other jurisdictions each bank must do its own sanction checking of both the sender and the receiver. The Regulation Protocol handles all these scenarios.
The customer information that is exchanged between FIs is flexible but the typical fields are:
The Regulation Protocol is an additional step after mapping. In this step the sending FI contacts the receiving FI to get permission to send the transaction. To do this the receiving FI creates an AUTH_SERVER and adds its location to the HCNet.toml of the FI.
You can create your own endpoint that implements the regulation protocol or we have also created this simple regulation service that you can use.
The AUTH_SERVER provides one endpoint that is called by a sending FI to get approval to send a payment to one of the receiving FI’s customers. The AUTH_SERVER url should be placed in organization’s HCNet.toml file.
To send transaction data to the receiving organization, send HTTP POST to AUTH_SERVER with Content-Type equal
application/x-www-form-urlencoded and the following body:
data=<data value>&sig= <sig value> </sig>
Name | Data Type | Description |
---|---|---|
sender | string | The payment address of the customer that is initiating the send. Ex. bob*bank.com |
need_info | boolean | If the caller needs the recipient’s AML info in order to send the payment. |
tx | string: base64 encoded xdr.Transaction | The transaction that the sender would like to send in XDR format. This transaction is unsigned and it’s sequence number should be equal 0. |
attachment | string | The full text of the attachment. The hash of this attachment is included as a memo in the transaction. The attachment field follows the HC Net Attachment Convention and should contain at least enough information of the sender to allow the receiving FI to do their sanction check. |
sig is the signature of the data block made by the sending FI. The receiving institution should check that this signature is valid against the public signature key that is posted in the sending FI’s HCNet.toml (SIGNING_KEY field).
Example request body (please note it contains both parameters data and sig):
After decoding the data parameter it has a following form:
Please note that the memo value of tx is a sha256 hash of the attachment.
Response
Name | Data Type | Description |
---|---|---|
info_status | ok, denied, pending | If this FI is willing to share AML information or not. |
tx_status | ok, denied, pending | If this FI is willing to accept this transaction. |
dest_info | string | (only present if info_status is ok) Marshalled JSON of the recipient’s AML information. |
pending | integer | (only present if info_status or tx_status is pending) Estimated number of seconds till the sender can check back for a change in status. The sender should just resubmit this request after the given number of seconds./td> |
Response Example
In this example, Aldi aldi*bankA.com wants to send to Bogart bogart*bankB.com:
This is done by looking up BankB’s HCNet.toml file.
BankA -> fetches https://bankB.com/.well-known/HCNet.toml
from this .toml file it pulls out the following info for BankB:
This is done by asking BankB’s mapping server to resolve
bogart*bankB.com.
BankA -> MAPPING_SERVER?type=name&q=bogart*bankB.com
See Mapping for a complete description. The returned fields of interest here are:
Example mapping response:
This request will ask BankB for Bogart’s AML info and for permission to send to Bogart.
BankA -> AUTH_SERVER
Example request body (please note it contains both parameters data and sig):
After decoding data parameter it has a following form:
Please note that memo value of tx is the sha256 hash of the attachment and payment destination is returned by the mapping server. You can check the transaction above using the XDR Viewer.
See Auth Server response for potential return values.
Example Response:
If the call to the AUTH_SERVER returned pending, BankA must resubmit the request again after the estimated number of seconds.
Once BankA has been given the dest_info from BankB, BankA does the sanction check using this AML info of Bogart. If the sanction check passes, BankA signs and submits the transaction to the HC Net.
Support Agent
*Powered by HashCash