You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by my...@apache.org on 2018/07/18 14:17:32 UTC

[fineract-cn-stellar-bridge] 02/03: Copied in and adjusted stellar mapping code from https://github.com/openMF/stellar-connector.

This is an automated email from the ASF dual-hosted git repository.

myrle pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/fineract-cn-stellar-bridge.git

commit 7b3437577d246d615b8c1398817fe1b5eee39aa4
Author: Myrle Krantz <my...@apache.org>
AuthorDate: Tue Jul 17 17:03:14 2018 +0200

    Copied in and adjusted stellar mapping code from
    https://github.com/openMF/stellar-connector.
---
 .../api/v1/events/EventConstants.java              |   6 +-
 .../cn/stellarbridge/SuiteTestEnvironment.java     |   2 +-
 .../cn/stellarbridge/TestBridgeConfiguration.java  |   5 +-
 .../listener/BridgeConfigurationEventListener.java |   2 +-
 service/build.gradle                               |   5 +-
 .../service/StellarBridgeApplication.java          |   1 +
 .../internal/accounting/AccountingAdapter.java     |  31 +++
 .../internal/accounting/AccountingListener.java    |  28 ++
 .../JournalEntryCreator.java}                      |  37 +--
 .../internal/command/FineractPaymentCommand.java   |  52 ++++
 .../internal/command/StellarPaymentCommand.java    |  28 ++
 .../handler/BridgeConfigurationCommandHandler.java |  11 +-
 .../handler/FineractPaymentCommandHandler.java     |  74 +++++
 .../handler/StellarPaymentCommandHandler.java      |  46 ++++
 .../config}/StellarBridgeConfiguration.java        |  15 +-
 .../internal/config/StellarBridgeProperties.java   |  57 ++++
 .../federation/ExternalFederationService.java      |  92 +++++++
 .../federation/FederationFailedException.java      |  37 +++
 .../federation/InvalidStellarAddressException.java |  17 ++
 .../internal/federation/StellarAccountId.java      |  48 ++++
 .../internal/federation/StellarAddress.java        | 119 ++++++++
 .../federation/StellarAddressResolver.java         |  21 ++
 .../HorizonServerEffectsListener.java              | 132 +++++++++
 .../HorizonServerPaymentObserver.java              |  82 ++++++
 .../horizonadapter/HorizonServerUtilities.java     | 301 +++++++++++++++++++++
 .../InvalidConfigurationException.java             |  20 ++
 .../horizonadapter/StellarAccountHelpers.java      | 184 +++++++++++++
 .../StellarPaymentFailedException.java             |  16 ++
 .../repository/BridgeConfigurationEntity.java      |  20 ++
 ...ory.java => BridgeConfigurationRepository.java} |   4 +-
 .../internal/repository/StellarCursorEntity.java   |  64 +++++
 .../repository/StellarCursorRepository.java        |  11 +
 .../service/BridgeConfigurationService.java        |  10 +-
 .../db/migrations/mariadb/V1__initial_setup.sql    |  19 +-
 shared.gradle                                      |   5 +-
 35 files changed, 1557 insertions(+), 45 deletions(-)

diff --git a/api/src/main/java/org/apache/fineract/cn/stellarbridge/api/v1/events/EventConstants.java b/api/src/main/java/org/apache/fineract/cn/stellarbridge/api/v1/events/EventConstants.java
index f3619ba..c644272 100644
--- a/api/src/main/java/org/apache/fineract/cn/stellarbridge/api/v1/events/EventConstants.java
+++ b/api/src/main/java/org/apache/fineract/cn/stellarbridge/api/v1/events/EventConstants.java
@@ -25,6 +25,10 @@ public interface EventConstants {
   String SELECTOR_NAME = "action";
   String INITIALIZE = "initialize";
   String PUT_CONFIG = "put-config";
+  String STELLAR_PAYMENT_PROCESSED = "bridge-stellar-payment";
+  String FINERACT_PAYMENT_PROCESSED = "bridge-fineract-payment";
   String SELECTOR_INITIALIZE = SELECTOR_NAME + " = '" + INITIALIZE + "'";
-  String SELECTOR_POST_SAMPLE = SELECTOR_NAME + " = '" + PUT_CONFIG + "'";
+  String SELECTOR_PUT_CONFIG = SELECTOR_NAME + " = '" + PUT_CONFIG + "'";
+  String SELECTOR_STELLAR_PAYMENT_PROCESSED = SELECTOR_NAME + " = '" + STELLAR_PAYMENT_PROCESSED + "'";
+  String SELECTOR_FINERACT_PAYMENT_PROCESSED = SELECTOR_NAME + " = '" + FINERACT_PAYMENT_PROCESSED + "'";
 }
diff --git a/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/SuiteTestEnvironment.java b/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/SuiteTestEnvironment.java
index 13e360b..9ce6265 100644
--- a/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/SuiteTestEnvironment.java
+++ b/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/SuiteTestEnvironment.java
@@ -34,7 +34,7 @@ import org.junit.rules.TestRule;
  */
 public class SuiteTestEnvironment {
   static final String APP_VERSION = "1";
-  static final String APP_NAME = "stellarbridge-v1" + APP_VERSION;
+  static final String APP_NAME = "stellarbridge-v" + APP_VERSION;
 
   static final TestEnvironment testEnvironment = new TestEnvironment(APP_NAME);
   static final CassandraInitializer cassandraInitializer = new CassandraInitializer();
diff --git a/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/TestBridgeConfiguration.java b/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/TestBridgeConfiguration.java
index 07b3c67..1a91fde 100644
--- a/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/TestBridgeConfiguration.java
+++ b/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/TestBridgeConfiguration.java
@@ -23,7 +23,7 @@ import org.apache.fineract.cn.api.context.AutoUserContext;
 import org.apache.fineract.cn.stellarbridge.api.v1.client.StellarBridgeManager;
 import org.apache.fineract.cn.stellarbridge.api.v1.domain.BridgeConfiguration;
 import org.apache.fineract.cn.stellarbridge.api.v1.events.EventConstants;
-import org.apache.fineract.cn.stellarbridge.service.StellarBridgeConfiguration;
+import org.apache.fineract.cn.stellarbridge.service.internal.config.StellarBridgeConfiguration;
 import org.apache.fineract.cn.test.fixture.TenantDataStoreContextTestRule;
 import org.apache.fineract.cn.test.listener.EnableEventRecording;
 import org.apache.fineract.cn.test.listener.EventRecorder;
@@ -48,7 +48,8 @@ import org.springframework.context.annotation.Import;
 import org.springframework.test.context.junit4.SpringRunner;
 
 @RunWith(SpringRunner.class)
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
+    properties = {"stellarBridge.user=homer", "stellarBridge.horizonAddress=https://horizon-testnet.stellar.org"})
 public class TestBridgeConfiguration extends SuiteTestEnvironment {
   private static final String LOGGER_NAME = "test-logger";
   private static final String TEST_USER = "homer";
diff --git a/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/listener/BridgeConfigurationEventListener.java b/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/listener/BridgeConfigurationEventListener.java
index 9500f2c..5e695c4 100644
--- a/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/listener/BridgeConfigurationEventListener.java
+++ b/component-test/src/main/java/org/apache/fineract/cn/stellarbridge/listener/BridgeConfigurationEventListener.java
@@ -41,7 +41,7 @@ public class BridgeConfigurationEventListener {
   @JmsListener(
       subscription = EventConstants.DESTINATION,
       destination = EventConstants.DESTINATION,
-      selector = EventConstants.SELECTOR_POST_SAMPLE
+      selector = EventConstants.SELECTOR_PUT_CONFIG
   )
   public void onChangeConfiguration(@Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
                              final String payload) {
diff --git a/service/build.gradle b/service/build.gradle
index 2b0a369..34ec58f 100644
--- a/service/build.gradle
+++ b/service/build.gradle
@@ -48,7 +48,7 @@ dependencies {
             [group: 'org.springframework.cloud', name: 'spring-cloud-starter-config'],
             [group: 'org.springframework.cloud', name: 'spring-cloud-starter-eureka'],
             [group: 'org.springframework.boot', name: 'spring-boot-starter-jetty'],
-            [group: 'org.apache.fineract.cn.identity', name: 'api', version: versions.identity],
+            [group: 'org.apache.fineract.cn.accounting', name: 'api', version: versions.accounting],
             [group: 'org.apache.fineract.cn.stellarbridge', name: 'api', version: project.version],
             [group: 'org.apache.fineract.cn.anubis', name: 'library', version: versions.frameworkanubis],
             [group: 'com.google.code.gson', name: 'gson'],
@@ -58,7 +58,8 @@ dependencies {
             [group: 'org.apache.fineract.cn', name: 'mariadb', version: versions.frameworkmariadb],
             [group: 'org.apache.fineract.cn', name: 'command', version: versions.frameworkcommand],
             [group: 'org.apache.fineract.cn.permitted-feign-client', name: 'library', version: versions.frameworkpermittedfeignclient],
-            [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator]
+            [group: 'org.hibernate', name: 'hibernate-validator', version: versions.validator],
+            [group: 'com.github.stellar', name: 'java-stellar-sdk', version: versions.stellar]
     )
 }
 
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/StellarBridgeApplication.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/StellarBridgeApplication.java
index ac5f8dd..32410ef 100644
--- a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/StellarBridgeApplication.java
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/StellarBridgeApplication.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.cn.stellarbridge.service;
 
+import org.apache.fineract.cn.stellarbridge.service.internal.config.StellarBridgeConfiguration;
 import org.springframework.boot.SpringApplication;
 
 public class StellarBridgeApplication {
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/accounting/AccountingAdapter.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/accounting/AccountingAdapter.java
new file mode 100644
index 0000000..6075143
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/accounting/AccountingAdapter.java
@@ -0,0 +1,31 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.accounting;
+
+import java.math.BigDecimal;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationEntity;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AccountingAdapter {
+  private final JournalEntryCreator journalEntryCreator;
+
+  @Autowired
+  public AccountingAdapter(
+      final JournalEntryCreator journalEntryCreator) {
+    this.journalEntryCreator = journalEntryCreator;
+  }
+
+  public String adjustFineractBalances(
+      final BridgeConfigurationEntity bridgeConfigurationEntity,
+      final BigDecimal amount,
+      final String assetCode) {
+    //journalEntryCreator.createJournalEntry(journalEntry);
+    return null;
+  }
+
+  public void tellFineractPaymentSucceeded(String fineractStagingAccountIdentifier,
+      String assetCode, BigDecimal amount)
+  {
+
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/accounting/AccountingListener.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/accounting/AccountingListener.java
new file mode 100644
index 0000000..b4ff467
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/accounting/AccountingListener.java
@@ -0,0 +1,28 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.accounting;
+
+import org.apache.fineract.cn.command.gateway.CommandGateway;
+import org.apache.fineract.cn.lang.config.TenantHeaderFilter;
+import org.apache.fineract.cn.stellarbridge.service.internal.command.FineractPaymentCommand;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AccountingListener {
+  private final CommandGateway commandGateway;
+
+  @Autowired
+  public AccountingListener(
+      final CommandGateway commandGateway) {
+    this.commandGateway = commandGateway;
+  }
+
+  public void onFineractPayment(
+      @Header(TenantHeaderFilter.TENANT_HEADER) final String tenant,
+      final String payload) {
+    final FineractPaymentCommand fineractPaymentCommand = new FineractPaymentCommand(tenant,
+        payload, null, null, null, null);
+    commandGateway.process(fineractPaymentCommand);
+  }
+
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/identity/ApplicationPermissionRequestCreator.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/accounting/JournalEntryCreator.java
similarity index 54%
rename from service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/identity/ApplicationPermissionRequestCreator.java
rename to service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/accounting/JournalEntryCreator.java
index b3c1e7f..5375cda 100644
--- a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/identity/ApplicationPermissionRequestCreator.java
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/accounting/JournalEntryCreator.java
@@ -16,19 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cn.stellarbridge.service.internal.identity;
+package org.apache.fineract.cn.stellarbridge.service.internal.accounting;
 
-import javax.validation.Valid;
+import org.apache.fineract.cn.accounting.api.v1.client.JournalEntryAlreadyExistsException;
+import org.apache.fineract.cn.accounting.api.v1.client.JournalEntryValidationException;
+import org.apache.fineract.cn.accounting.api.v1.domain.JournalEntry;
 import org.apache.fineract.cn.anubis.annotation.Permittable;
 import org.apache.fineract.cn.api.annotation.ThrowsException;
-import org.apache.fineract.cn.identity.api.v1.client.ApplicationPermissionAlreadyExistsException;
-import org.apache.fineract.cn.identity.api.v1.domain.Permission;
+import org.apache.fineract.cn.api.annotation.ThrowsExceptions;
 import org.apache.fineract.cn.permittedfeignclient.annotation.EndpointSet;
 import org.apache.fineract.cn.permittedfeignclient.annotation.PermittedFeignClientsConfiguration;
 import org.springframework.cloud.netflix.feign.FeignClient;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
-import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
@@ -36,16 +36,19 @@ import org.springframework.web.bind.annotation.RequestMethod;
 /**
  * @author Myrle Krantz
  */
-@EndpointSet(identifier = "stellarbridge__v1__identity__v1")
-@FeignClient(name="identity-v1", path="/identity/v1", configuration=PermittedFeignClientsConfiguration.class)
-public interface ApplicationPermissionRequestCreator {
-
-  @RequestMapping(value = "/applications/{applicationidentifier}/permissions", method = RequestMethod.POST,
-          consumes = {MediaType.APPLICATION_JSON_VALUE},
-          produces = {MediaType.ALL_VALUE})
-  @ThrowsException(status = HttpStatus.CONFLICT, exception = ApplicationPermissionAlreadyExistsException.class)
-  @Permittable(groupId = org.apache.fineract.cn.identity.api.v1.PermittableGroupIds.APPLICATION_SELF_MANAGEMENT)
-  void createApplicationPermission(
-      @PathVariable("applicationidentifier") String applicationIdentifier,
-      @RequestBody @Valid Permission permission);
+@EndpointSet(identifier = "stellarbridge__v1__accounting__v1")
+@FeignClient(name="accounting-v1", path="/accounting/v1", configuration=PermittedFeignClientsConfiguration.class)
+public interface JournalEntryCreator {
+  @RequestMapping(
+      value = "/journal",
+      method = RequestMethod.POST,
+      produces = {MediaType.APPLICATION_JSON_VALUE},
+      consumes = {MediaType.APPLICATION_JSON_VALUE}
+  )
+  @ThrowsExceptions({
+      @ThrowsException(status = HttpStatus.BAD_REQUEST, exception = JournalEntryValidationException.class),
+      @ThrowsException(status = HttpStatus.CONFLICT, exception = JournalEntryAlreadyExistsException.class)
+  })
+  @Permittable(groupId = org.apache.fineract.cn.accounting.api.v1.PermittableGroupIds.THOTH_JOURNAL)
+  void createJournalEntry(@RequestBody final JournalEntry journalEntry);
 }
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/FineractPaymentCommand.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/FineractPaymentCommand.java
new file mode 100644
index 0000000..d830386
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/FineractPaymentCommand.java
@@ -0,0 +1,52 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.command;
+
+import java.math.BigDecimal;
+
+public class FineractPaymentCommand {
+
+  final private String tenantIdentifer;
+  final private String transactionIdentifier;
+  final private String targetAccount;
+  final private String sinkDomain;
+  final private BigDecimal amount;
+  final private String assetCode;
+
+  public FineractPaymentCommand(
+      String tenantIdentifer,
+      String transactionIdentifier,
+      String targetAccount,
+      String sinkDomain,
+      BigDecimal amount,
+      String assetCode) {
+    this.tenantIdentifer = tenantIdentifer;
+    this.transactionIdentifier = transactionIdentifier;
+    this.targetAccount = targetAccount;
+    this.sinkDomain = sinkDomain;
+    this.amount = amount;
+    this.assetCode = assetCode;
+  }
+
+  public String getTenantIdentifier() {
+    return tenantIdentifer;
+  }
+
+  public String getTransactionIdentifier() {
+    return transactionIdentifier;
+  }
+
+  public String getTargetAccount() {
+    return targetAccount;
+  }
+
+  public String getSinkDomain() {
+    return sinkDomain;
+  }
+
+  public BigDecimal getAmount() {
+    return amount;
+  }
+
+  public String getAssetCode() {
+    return assetCode;
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/StellarPaymentCommand.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/StellarPaymentCommand.java
new file mode 100644
index 0000000..d1cd85a
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/StellarPaymentCommand.java
@@ -0,0 +1,28 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.command;
+
+import java.math.BigDecimal;
+
+public class StellarPaymentCommand {
+
+  private final String tenantIdentifier;
+  private final String assetCode;
+  private final BigDecimal amount;
+
+  public StellarPaymentCommand(String tenantIdentifier, String assetCode, BigDecimal amount) {
+    this.tenantIdentifier = tenantIdentifier;
+    this.assetCode = assetCode;
+    this.amount = amount;
+  }
+
+  public String getTenantIdentifier() {
+    return tenantIdentifier;
+  }
+
+  public String getAssetCode() {
+    return assetCode;
+  }
+
+  public BigDecimal getAmount() {
+    return amount;
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/handler/BridgeConfigurationCommandHandler.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/handler/BridgeConfigurationCommandHandler.java
index 9522086..973eeb4 100644
--- a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/handler/BridgeConfigurationCommandHandler.java
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/handler/BridgeConfigurationCommandHandler.java
@@ -25,7 +25,7 @@ import org.apache.fineract.cn.stellarbridge.api.v1.events.EventConstants;
 import org.apache.fineract.cn.stellarbridge.service.internal.command.ChangeConfigurationCommand;
 import org.apache.fineract.cn.stellarbridge.service.internal.mapper.BridgeConfigurationMapper;
 import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationEntity;
-import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationEntityRepository;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -33,16 +33,17 @@ import org.springframework.transaction.annotation.Transactional;
 @Aggregate
 public class BridgeConfigurationCommandHandler {
 
-  private final BridgeConfigurationEntityRepository bridgeConfigurationEntityRepository;
+  private final BridgeConfigurationRepository bridgeConfigurationRepository;
   private final EventHelper eventHelper;
 
   @Autowired
   public BridgeConfigurationCommandHandler(
-      final BridgeConfigurationEntityRepository bridgeConfigurationEntityRepository,
+      final BridgeConfigurationRepository bridgeConfigurationRepository,
       final EventHelper eventHelper) {
-    this.bridgeConfigurationEntityRepository = bridgeConfigurationEntityRepository;
+    this.bridgeConfigurationRepository = bridgeConfigurationRepository;
     this.eventHelper = eventHelper;
   }
+
   @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
   @Transactional
   public void handle(final ChangeConfigurationCommand changeConfigurationCommand) {
@@ -50,7 +51,7 @@ public class BridgeConfigurationCommandHandler {
     final BridgeConfigurationEntity entity = BridgeConfigurationMapper.map(
         changeConfigurationCommand.tenantIdentifier(),
         changeConfigurationCommand.instance());
-    this.bridgeConfigurationEntityRepository.save(entity);
+    this.bridgeConfigurationRepository.save(entity);
 
     eventHelper.sendEvent(EventConstants.PUT_CONFIG, changeConfigurationCommand.tenantIdentifier(), null);
   }
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/handler/FineractPaymentCommandHandler.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/handler/FineractPaymentCommandHandler.java
new file mode 100644
index 0000000..328a542
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/handler/FineractPaymentCommandHandler.java
@@ -0,0 +1,74 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.command.handler;
+
+import java.util.Optional;
+import org.apache.fineract.cn.command.annotation.Aggregate;
+import org.apache.fineract.cn.command.annotation.CommandHandler;
+import org.apache.fineract.cn.command.annotation.CommandLogLevel;
+import org.apache.fineract.cn.stellarbridge.api.v1.events.EventConstants;
+import org.apache.fineract.cn.stellarbridge.service.internal.accounting.AccountingAdapter;
+import org.apache.fineract.cn.stellarbridge.service.internal.command.FineractPaymentCommand;
+import org.apache.fineract.cn.stellarbridge.service.internal.federation.StellarAccountId;
+import org.apache.fineract.cn.stellarbridge.service.internal.federation.StellarAddress;
+import org.apache.fineract.cn.stellarbridge.service.internal.federation.StellarAddressResolver;
+import org.apache.fineract.cn.stellarbridge.service.internal.horizonadapter.HorizonServerUtilities;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationEntity;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Aggregate
+public class FineractPaymentCommandHandler {
+  private final BridgeConfigurationRepository bridgeConfigurationRepository;
+  private final StellarAddressResolver stellarAddressResolver;
+  private final HorizonServerUtilities horizonServerUtilities;
+  private final AccountingAdapter accountingAdapter;
+  private final EventHelper eventHelper;
+
+  @Autowired
+  public FineractPaymentCommandHandler(
+      final BridgeConfigurationRepository bridgeConfigurationRepository,
+      final StellarAddressResolver stellarAddressResolver,
+      final HorizonServerUtilities horizonServerUtilities,
+      AccountingAdapter accountingAdapter,
+      final EventHelper eventHelper) {
+    this.bridgeConfigurationRepository = bridgeConfigurationRepository;
+    this.stellarAddressResolver = stellarAddressResolver;
+    this.horizonServerUtilities = horizonServerUtilities;
+    this.accountingAdapter = accountingAdapter;
+    this.eventHelper = eventHelper;
+  }
+
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @Transactional
+  public void handle(final FineractPaymentCommand command) {
+
+    final Optional<BridgeConfigurationEntity> accountBridge =
+        bridgeConfigurationRepository.findByTenantIdentifier(command.getTenantIdentifier());
+
+    accountBridge.ifPresent(x -> pay(command, x));
+  }
+
+  private void pay(
+      final FineractPaymentCommand command,
+      final BridgeConfigurationEntity bridgeConfigurationEntity)
+  {
+    final StellarAccountId targetAccountId;
+    targetAccountId =  stellarAddressResolver.getAccountIdOfStellarAccount(
+        StellarAddress.forTenant(command.getTargetAccount(), command.getSinkDomain()));
+
+    final char[] decodedStellarPrivateKey =
+        bridgeConfigurationEntity.getStellarAccountPrivateKey();
+
+    horizonServerUtilities.findPathPay(
+        targetAccountId,
+        command.getAmount(), command.getAssetCode(),
+        decodedStellarPrivateKey);
+
+    accountingAdapter.tellFineractPaymentSucceeded(
+        bridgeConfigurationEntity.getFineractStagingAccountIdentifier(),
+        command.getAssetCode(),
+        command.getAmount());
+
+    eventHelper.sendEvent(EventConstants.FINERACT_PAYMENT_PROCESSED, command.getTenantIdentifier(), command.getTransactionIdentifier());
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/handler/StellarPaymentCommandHandler.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/handler/StellarPaymentCommandHandler.java
new file mode 100644
index 0000000..00ea780
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/command/handler/StellarPaymentCommandHandler.java
@@ -0,0 +1,46 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.command.handler;
+
+import java.util.Optional;
+import org.apache.fineract.cn.command.annotation.Aggregate;
+import org.apache.fineract.cn.command.annotation.CommandHandler;
+import org.apache.fineract.cn.command.annotation.CommandLogLevel;
+import org.apache.fineract.cn.stellarbridge.api.v1.events.EventConstants;
+import org.apache.fineract.cn.stellarbridge.service.internal.accounting.AccountingAdapter;
+import org.apache.fineract.cn.stellarbridge.service.internal.command.StellarPaymentCommand;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationEntity;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Aggregate
+public class StellarPaymentCommandHandler {
+  private final BridgeConfigurationRepository bridgeConfigurationRepository;
+  private final AccountingAdapter accountingAdapter;
+  private final EventHelper eventHelper;
+
+  @Autowired
+  public StellarPaymentCommandHandler(
+      final BridgeConfigurationRepository bridgeConfigurationRepository,
+      final AccountingAdapter accountingAdapter,
+      final EventHelper eventHelper) {
+    this.bridgeConfigurationRepository = bridgeConfigurationRepository;
+    this.accountingAdapter = accountingAdapter;
+    this.eventHelper = eventHelper;
+  }
+
+
+  @CommandHandler(logStart = CommandLogLevel.INFO, logFinish = CommandLogLevel.INFO)
+  @Transactional
+  public void handle(final StellarPaymentCommand command) {
+
+    final Optional<BridgeConfigurationEntity> accountBridge =
+        bridgeConfigurationRepository.findByTenantIdentifier(command.getTenantIdentifier());
+
+    final Optional<String> transactionIdentifier = accountBridge.map(x -> accountingAdapter.adjustFineractBalances(
+        x, command.getAmount(), command.getAssetCode()));
+
+    transactionIdentifier.ifPresent(x ->
+        eventHelper.sendEvent(EventConstants.STELLAR_PAYMENT_PROCESSED, command.getTenantIdentifier(), x));
+  }
+
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/StellarBridgeConfiguration.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/config/StellarBridgeConfiguration.java
similarity index 82%
rename from service/src/main/java/org/apache/fineract/cn/stellarbridge/service/StellarBridgeConfiguration.java
rename to service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/config/StellarBridgeConfiguration.java
index 74398be..fec205f 100644
--- a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/StellarBridgeConfiguration.java
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/config/StellarBridgeConfiguration.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.cn.stellarbridge.service;
+package org.apache.fineract.cn.stellarbridge.service.internal.config;
 
 import org.apache.fineract.cn.anubis.config.EnableAnubis;
 import org.apache.fineract.cn.api.config.EnableApiFactory;
@@ -28,7 +28,8 @@ import org.apache.fineract.cn.lang.config.EnableServiceException;
 import org.apache.fineract.cn.lang.config.EnableTenantContext;
 import org.apache.fineract.cn.mariadb.config.EnableMariaDB;
 import org.apache.fineract.cn.permittedfeignclient.config.EnablePermissionRequestingFeignClient;
-import org.apache.fineract.cn.stellarbridge.service.internal.identity.ApplicationPermissionRequestCreator;
+import org.apache.fineract.cn.stellarbridge.service.ServiceConstants;
+import org.apache.fineract.cn.stellarbridge.service.internal.accounting.JournalEntryCreator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -50,18 +51,22 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
 @EnableAsync
 @EnableTenantContext
 @EnableCassandra
-@EnableMariaDB
+@EnableMariaDB(forTenantContext = false)
 @EnableCommandProcessing
 @EnableAnubis
 @EnableServiceException
-@EnablePermissionRequestingFeignClient(feignClasses = {ApplicationPermissionRequestCreator.class})
+@EnablePermissionRequestingFeignClient(feignClasses = {JournalEntryCreator.class})
 @RibbonClient(name = "rhythm-v1")
 @EnableApplicationName
-@EnableFeignClients(clients = {ApplicationPermissionRequestCreator.class})
+@EnableFeignClients(clients = {JournalEntryCreator.class})
 @ComponentScan({
     "org.apache.fineract.cn.stellarbridge.service.rest",
     "org.apache.fineract.cn.stellarbridge.service.internal.service",
+    "org.apache.fineract.cn.stellarbridge.service.internal.config",
     "org.apache.fineract.cn.stellarbridge.service.internal.repository",
+    "org.apache.fineract.cn.stellarbridge.service.internal.federation",
+    "org.apache.fineract.cn.stellarbridge.service.internal.horizonadapter",
+    "org.apache.fineract.cn.stellarbridge.service.internal.accounting",
     "org.apache.fineract.cn.stellarbridge.service.internal.command.handler"
 })
 @EnableJpaRepositories({
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/config/StellarBridgeProperties.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/config/StellarBridgeProperties.java
new file mode 100644
index 0000000..5452b0c
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/config/StellarBridgeProperties.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.cn.stellarbridge.service.internal.config;
+
+import org.apache.fineract.cn.lang.validation.constraints.ValidIdentifier;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * @author Myrle Krantz
+ */
+@Component
+@ConfigurationProperties(prefix="stellarBridge")
+@Validated
+public class StellarBridgeProperties {
+  @ValidIdentifier
+  private String user;
+
+  private String horizonAddress;
+
+
+  public StellarBridgeProperties() {
+  }
+
+  public void setUser(String user) {
+    this.user = user;
+  }
+
+  public String getUser() {
+    return user;
+  }
+
+  public String getHorizonAddress() {
+    return horizonAddress;
+  }
+
+  public void setHorizonAddress(String horizonAddress) {
+    this.horizonAddress = horizonAddress;
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/ExternalFederationService.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/ExternalFederationService.java
new file mode 100644
index 0000000..0fd46b7
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/ExternalFederationService.java
@@ -0,0 +1,92 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.federation;
+
+import java.io.IOException;
+import org.springframework.stereotype.Service;
+import org.stellar.sdk.federation.Federation;
+import org.stellar.sdk.federation.FederationResponse;
+import org.stellar.sdk.federation.MalformedAddressException;
+
+@Service
+public class ExternalFederationService {
+
+  class StellarResolver
+  { //To make a static function mockable.
+    FederationResponse resolve(final String address)
+        throws IOException, MalformedAddressException {
+      return Federation.resolve(address);
+    }
+  }
+
+  private StellarResolver stellarResolver;
+
+  ExternalFederationService()
+  {
+    this.stellarResolver = new StellarResolver();
+  }
+
+  ExternalFederationService(final StellarResolver stellarResolver)
+  {
+    this.stellarResolver = stellarResolver;
+  }
+
+  /**
+   * Based on the stellar address, finds the stellar account id.  Resolves the domain, and calls
+   * the federation service to do so.  This only returns an account id if the memo type is id or
+   * there is no memo type.
+   *
+   * @param stellarAddress The stellar address for which to return a stellar account id.
+   * @return The corresponding stellar account id.
+   *
+   * @throws FederationFailedException for the following cases:
+   * * domain server not reachable,
+   * * stellar.toml not parseable for federation server,
+   * * federation server not reachable,
+   * * federation server response does not match expected format.
+   * * memo type is not id.
+   */
+  public StellarAccountId getAccountId(final StellarAddress stellarAddress)
+      throws FederationFailedException
+  {
+    final org.stellar.sdk.federation.FederationResponse federationResponse;
+    try {
+      federationResponse = stellarResolver.resolve(stellarAddress.toString());
+    }
+    catch (final MalformedAddressException e)
+    {
+      throw FederationFailedException.malformedAddress(stellarAddress.toString());
+    }
+    catch (final IOException e)
+    {
+      throw FederationFailedException
+          .domainDoesNotReferToValidFederationServer(stellarAddress.getDomain().toString());
+    }
+
+    if (federationResponse == null)
+    {
+      throw FederationFailedException.addressNameNotFound(stellarAddress.toString());
+    }
+    if (federationResponse.getAccountId() == null)
+    {
+      throw FederationFailedException.addressNameNotFound(stellarAddress.toString());
+    }
+
+    return convertFederationResponseToStellarAddress(federationResponse);
+  }
+
+  private StellarAccountId convertFederationResponseToStellarAddress(
+      final org.stellar.sdk.federation.FederationResponse response)
+  {
+    if (response.getMemoType().equalsIgnoreCase("text"))
+    {
+      return StellarAccountId.subAccount(response.getAccountId(), response.getMemo());
+    }
+    else if (response.getMemoType() == null || response.getMemoType().isEmpty())
+    {
+      return StellarAccountId.mainAccount(response.getAccountId());
+    }
+    else
+    {
+      throw FederationFailedException.addressRequiresUnsupportedMemoType(response.getMemoType());
+    }
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/FederationFailedException.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/FederationFailedException.java
new file mode 100644
index 0000000..09effbd
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/FederationFailedException.java
@@ -0,0 +1,37 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.federation;
+
+public class FederationFailedException extends RuntimeException {
+  private FederationFailedException(final String message) { super(message);
+  }
+
+  static FederationFailedException domainDoesNotReferToValidFederationServer
+      (final String domain)
+  {
+    return new FederationFailedException(
+        "The federation server for the given domain could not be reached: " + domain);
+  }
+
+  static FederationFailedException addressRequiresUnsupportedMemoType(final String memoType)
+  {
+    return new FederationFailedException(
+        "The given federation address returned an unsupported memo type: " + memoType);
+  }
+
+  static FederationFailedException wrongDomain(final String domain) {
+    return new FederationFailedException("Wrong domain: " + domain);
+  }
+
+  static FederationFailedException addressNameNotFound(final String address) {
+    return new FederationFailedException("The address name is not found: " + address);
+  }
+
+  static FederationFailedException malformedAddress(final String address) {
+    return new FederationFailedException("The address is not a valid stellar address: " + address);
+  }
+
+  static FederationFailedException needTopLevelStellarAccount
+      (final String address) {
+    return new FederationFailedException(
+        "Need top level Stellar account: " + address);
+  }
+}
\ No newline at end of file
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/InvalidStellarAddressException.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/InvalidStellarAddressException.java
new file mode 100644
index 0000000..8effca3
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/InvalidStellarAddressException.java
@@ -0,0 +1,17 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.federation;
+
+public class InvalidStellarAddressException extends RuntimeException {
+  private InvalidStellarAddressException(final String message) {
+    super(message);
+  }
+
+  static InvalidStellarAddressException invalidDomainName(final String domainName) {
+    return new InvalidStellarAddressException("Domain name is not valid: " + domainName);
+  }
+
+  static InvalidStellarAddressException nonConformantStellarAddress(final String address) {
+    return new InvalidStellarAddressException(
+        "Non-conformant stellar address: " + address
+    );
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/StellarAccountId.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/StellarAccountId.java
new file mode 100644
index 0000000..d9eb45a
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/StellarAccountId.java
@@ -0,0 +1,48 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.federation;
+
+import java.util.Objects;
+import java.util.Optional;
+
+public class StellarAccountId {
+  private final String publicKey;
+  private final Optional<String> subAccount;
+
+  static public StellarAccountId mainAccount(final String publicKey)
+  {
+    return new StellarAccountId(publicKey, Optional.empty());
+  }
+
+  static public StellarAccountId subAccount(final String mainAccountPublicKey,
+      final String subAccountId)
+  {
+    //TODO: Check what the correct form of the memo should be here...
+    return new StellarAccountId(mainAccountPublicKey, Optional.of(subAccountId));
+  }
+
+  private StellarAccountId(final String publicKey, final Optional<String> subAccount)
+  {
+    this.publicKey = publicKey;
+    this.subAccount = subAccount;
+  }
+
+  public String getPublicKey() {
+    return publicKey;
+  }
+
+  public Optional<String> getSubAccount() {
+    return subAccount;
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o)
+      return true;
+    if (o == null || getClass() != o.getClass())
+      return false;
+    StellarAccountId that = (StellarAccountId) o;
+    return Objects.equals(publicKey, that.publicKey) && Objects.equals(subAccount, that.subAccount);
+  }
+
+  @Override public int hashCode() {
+    return Objects.hash(publicKey, subAccount);
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/StellarAddress.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/StellarAddress.java
new file mode 100644
index 0000000..626f964
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/StellarAddress.java
@@ -0,0 +1,119 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.federation;
+
+import com.google.common.net.InternetDomainName;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class StellarAddress {
+
+  private final InternetDomainName domain;
+  private final String tenantName;
+  private final Optional<String> userAccountId;
+  private final boolean isVaultAddress;
+
+  public static StellarAddress forTenant(final String tenantName, final String domain)
+  {
+    return new StellarAddress(InternetDomainName.from(domain), tenantName, Optional.empty());
+  }
+
+  public static StellarAddress parse(final String address)
+      throws InvalidStellarAddressException
+  {
+    //I chose not to use Pattern.UNICODE_CHARACTER_CLASS because of potential performance issues and
+    //no current knowledge of use cases which require unicode addresses.  Certainly the domain
+    //name can't contain unicode characters.  According to the federation servers specs at
+    //Stellar, the part before the * might contain them.  Depending on what use cases we encounter,
+    //we may need to adjust this.
+    final Pattern stellarAddressPattern = Pattern.compile(
+        "(?<name>^[^\\:\\*@\\p{Space}]+)(:(?<subname>[^\\:\\*@\\p{Space}]+))?+\\*(?<domain>[\\p{Alnum}-\\.]+)$");
+
+    final Matcher addressMatcher = stellarAddressPattern.matcher(address);
+    if (!addressMatcher.matches()) {
+      throw InvalidStellarAddressException.nonConformantStellarAddress(address);
+    }
+
+    if (addressMatcher.group("subname") != null)
+    {
+      return new StellarAddress(getInternetDomainName(addressMatcher.group("domain")),
+          addressMatcher.group("name"), Optional.of(addressMatcher.group("subname")));
+    }
+    else
+    {
+      return new StellarAddress(getInternetDomainName(addressMatcher.group("domain")),
+          addressMatcher.group("name"), Optional.empty());
+    }
+  }
+
+  private static InternetDomainName getInternetDomainName(final String domain)
+      throws InvalidStellarAddressException
+  {
+    try {
+      return InternetDomainName.from(domain);
+    }
+    catch (final IllegalArgumentException e)
+    {
+      throw InvalidStellarAddressException.invalidDomainName(domain);
+    }
+  }
+
+  private StellarAddress(
+      final InternetDomainName domain,
+      final String tenantName,
+      final Optional<String> userAccountId)
+  {
+    this.domain = domain;
+    this.tenantName = tenantName;
+    if (userAccountId.orElse("").equals("vault")) {
+      isVaultAddress = true;
+      this.userAccountId = Optional.empty();
+    }
+    else
+    {
+      isVaultAddress = false;
+      this.userAccountId = userAccountId;
+    }
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o)
+      return true;
+    if (!(o instanceof StellarAddress))
+      return false;
+    StellarAddress that = (StellarAddress) o;
+    return isVaultAddress == that.isVaultAddress &&
+        Objects.equals(domain, that.domain) &&
+        Objects.equals(tenantName, that.tenantName) &&
+        Objects.equals(userAccountId, that.userAccountId);
+  }
+
+  @Override public int hashCode() {
+    return Objects.hash(domain, tenantName, userAccountId, isVaultAddress);
+  }
+
+  public String toString() {
+    if (isVaultAddress) {
+      return tenantName + ":" + "vault" + "*" + domain.toString();
+    }
+    else
+      return userAccountId.map(s -> tenantName + ":" + s + "*" + domain.toString())
+          .orElseGet(() -> tenantName + "*" + domain.toString());
+  }
+
+  public InternetDomainName getDomain() {
+    return domain;
+  }
+
+  public String getTenantName() {
+    return tenantName;
+  }
+
+  public Optional<String> getUserAccountId() {
+    return userAccountId;
+  }
+
+  public boolean isVaultAddress() {
+    return isVaultAddress;
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/StellarAddressResolver.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/StellarAddressResolver.java
new file mode 100644
index 0000000..f8b404f
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/federation/StellarAddressResolver.java
@@ -0,0 +1,21 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.federation;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class StellarAddressResolver {
+  private final ExternalFederationService externalFederationService;
+
+  @Autowired
+  public StellarAddressResolver(
+      final ExternalFederationService externalFederationService) {
+    this.externalFederationService = externalFederationService;
+  }
+
+  public StellarAccountId getAccountIdOfStellarAccount(final StellarAddress stellarAddress)
+      throws FederationFailedException
+  {
+      return externalFederationService.getAccountId(stellarAddress);
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/HorizonServerEffectsListener.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/HorizonServerEffectsListener.java
new file mode 100644
index 0000000..f9726a9
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/HorizonServerEffectsListener.java
@@ -0,0 +1,132 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.horizonadapter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.Optional;
+import org.apache.fineract.cn.command.gateway.CommandGateway;
+import org.apache.fineract.cn.stellarbridge.service.ServiceConstants;
+import org.apache.fineract.cn.stellarbridge.service.internal.command.StellarPaymentCommand;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationEntity;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationRepository;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.StellarCursorEntity;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.StellarCursorRepository;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+import org.stellar.sdk.Asset;
+import org.stellar.sdk.AssetTypeCreditAlphaNum;
+import org.stellar.sdk.requests.EventListener;
+import org.stellar.sdk.responses.effects.AccountCreditedEffectResponse;
+import org.stellar.sdk.responses.effects.AccountDebitedEffectResponse;
+import org.stellar.sdk.responses.effects.EffectResponse;
+
+@Component
+public class HorizonServerEffectsListener implements EventListener<EffectResponse> {
+
+  private final BridgeConfigurationRepository accountBridgeRepository;
+  private final StellarCursorRepository stellarCursorRepository;
+  private final CommandGateway commandGateway;
+
+  private final Logger logger;
+
+
+  @Autowired
+  HorizonServerEffectsListener(
+      final BridgeConfigurationRepository accountBridgeRepository,
+      final StellarCursorRepository stellarCursorRepository,
+      final CommandGateway commandGateway,
+      @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger)
+  {
+    this.accountBridgeRepository = accountBridgeRepository;
+    this.stellarCursorRepository = stellarCursorRepository;
+    this.commandGateway = commandGateway;
+    this.logger = logger;
+  }
+
+  @Override public void onEvent(final EffectResponse operation) {
+    final String pagingToken = operation.getPagingToken();
+
+    //This is important, because an event can be sent twice if we are managing both the sending and
+    //receiving account.  We need to be certain we process it only once.
+    final StellarCursorEntity cursorPersistency = markPlace(pagingToken);
+    if (cursorPersistency.getProcessed())
+      return;
+
+    logger.info("Operation with cursor {}", pagingToken);
+
+    handleOperation(operation);
+
+    cursorPersistency.setProcessed(true);
+    stellarCursorRepository.save(cursorPersistency);
+  }
+
+  StellarCursorEntity markPlace(final String pagingToken)
+  {
+    synchronized (stellarCursorRepository) {
+      final Optional<StellarCursorEntity> entry =
+          stellarCursorRepository.findByCursor(pagingToken);
+
+      return entry.orElse(
+          stellarCursorRepository.save(new StellarCursorEntity(pagingToken, new Date())));
+    }
+  }
+
+  private void handleOperation(final EffectResponse effect) {
+
+    if (effect instanceof AccountCreditedEffectResponse)
+    {
+      final AccountCreditedEffectResponse accountCreditedEffect = (AccountCreditedEffectResponse) effect;
+      final BridgeConfigurationEntity toAccount
+          = accountBridgeRepository.findByStellarAccountIdentifier(effect.getAccount().getAccountId());
+      if (toAccount == null)
+        return; //Nothing to do.  Not one of ours.
+
+      final BigDecimal amount
+          = StellarAccountHelpers.stellarBalanceToBigDecimal(accountCreditedEffect.getAmount());
+      final Asset asset = accountCreditedEffect.getAsset();
+      final String assetCode = StellarAccountHelpers.getAssetCode(asset);
+      final String issuer = StellarAccountHelpers.getIssuer(asset);
+
+      logger.info("Credit to {} of {}, in currency {}@{}",
+          toAccount.getTenantIdentifier(), amount, assetCode, issuer);
+
+      //TODO: This will prevent lumens from being registered in the mifos account (likewise below in debit)...
+      if (!(asset instanceof AssetTypeCreditAlphaNum))
+        return;
+
+      final StellarPaymentCommand receivePaymentCommand =
+          new StellarPaymentCommand(toAccount.getTenantIdentifier(), assetCode, amount);
+      commandGateway.process(receivePaymentCommand);
+    }
+    else if (effect instanceof AccountDebitedEffectResponse)
+    {
+      final AccountDebitedEffectResponse accountDebitedEffect = (AccountDebitedEffectResponse)effect;
+
+      final BridgeConfigurationEntity toAccount = accountBridgeRepository
+          .findByStellarAccountIdentifier(accountDebitedEffect.getAccount().getAccountId());
+      if (toAccount == null)
+        return; //Nothing to do.  Not one of ours.
+
+      final BigDecimal amount
+          = StellarAccountHelpers.stellarBalanceToBigDecimal(accountDebitedEffect.getAmount());
+      final Asset asset = accountDebitedEffect.getAsset();
+      final String assetCode = StellarAccountHelpers.getAssetCode(asset);
+      final String issuer = StellarAccountHelpers.getIssuer(asset);
+
+      logger.info("Debit to {} of {}, in currency {}@{}",
+          toAccount.getTenantIdentifier(), amount, assetCode, issuer);
+
+      if (!(asset instanceof AssetTypeCreditAlphaNum))
+        return;
+
+      final StellarPaymentCommand receivePaymentCommand =
+          new StellarPaymentCommand(toAccount.getTenantIdentifier(), assetCode, amount.negate());
+      commandGateway.process(receivePaymentCommand);
+    }
+    else
+    {
+      logger.info("Effect of type {}", effect.getType());
+    }
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/HorizonServerPaymentObserver.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/HorizonServerPaymentObserver.java
new file mode 100644
index 0000000..53e9acd
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/HorizonServerPaymentObserver.java
@@ -0,0 +1,82 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.horizonadapter;
+
+import java.net.URI;
+import java.util.Optional;
+import javax.annotation.PostConstruct;
+import javax.validation.constraints.NotNull;
+import org.apache.fineract.cn.stellarbridge.service.ServiceConstants;
+import org.apache.fineract.cn.stellarbridge.service.internal.config.StellarBridgeProperties;
+import org.apache.fineract.cn.stellarbridge.service.internal.federation.StellarAccountId;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationRepository;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.StellarCursorEntity;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.StellarCursorRepository;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+import org.stellar.sdk.KeyPair;
+import org.stellar.sdk.requests.EffectsRequestBuilder;
+
+@Component
+public class HorizonServerPaymentObserver {
+  private final StellarBridgeProperties stellarBridgeProperties;
+
+  private final BridgeConfigurationRepository bridgeConfigurationRepository;
+  private final StellarCursorRepository stellarCursorRepository;
+  private final HorizonServerEffectsListener listener;
+  private final Logger logger;
+
+  @PostConstruct
+  void init()
+  {
+    final Optional<String> cursor = getCurrentCursor();
+
+    bridgeConfigurationRepository.findAll()
+        .forEach(config -> setupListeningForAccount(
+            StellarAccountId.mainAccount(config.getStellarAccountIdentifier()), cursor));
+  }
+
+  @Autowired
+  HorizonServerPaymentObserver(
+      final StellarBridgeProperties stellarBridgeProperties,
+      final BridgeConfigurationRepository bridgeConfigurationRepository,
+      final StellarCursorRepository stellarCursorRepository,
+      final HorizonServerEffectsListener listener,
+      @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger)
+  {
+    this.stellarBridgeProperties = stellarBridgeProperties;
+    this.bridgeConfigurationRepository = bridgeConfigurationRepository;
+    this.stellarCursorRepository = stellarCursorRepository;
+
+    this.listener = listener;
+
+    this.logger = logger;
+  }
+
+  public void setupListeningForAccount(final StellarAccountId stellarAccountId)
+  {
+    setupListeningForAccount(stellarAccountId, Optional.empty());
+  }
+
+  private Optional<String> getCurrentCursor() {
+    final Optional<StellarCursorEntity> cursorPersistency
+        = stellarCursorRepository.findTopByProcessedTrueOrderByCreatedOnDesc();
+
+    return cursorPersistency.map(StellarCursorEntity::getCursor);
+  }
+
+  private void setupListeningForAccount(
+      @NotNull final StellarAccountId stellarAccountId, @NotNull final Optional<String> cursor)
+  {
+    logger.info("HorizonServerPaymentObserver.setupListeningForAccount {}, cursor {}",
+        stellarAccountId.getPublicKey(), cursor);
+
+    final EffectsRequestBuilder effectsRequestBuilder
+        = new EffectsRequestBuilder(URI.create(stellarBridgeProperties.getHorizonAddress()));
+    effectsRequestBuilder.forAccount(KeyPair.fromAccountId(stellarAccountId.getPublicKey()));
+    cursor.ifPresent(effectsRequestBuilder::cursor);
+
+    effectsRequestBuilder.stream(listener);
+  }
+
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/HorizonServerUtilities.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/HorizonServerUtilities.java
new file mode 100644
index 0000000..cb86bda
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/HorizonServerUtilities.java
@@ -0,0 +1,301 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.horizonadapter;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.net.URISyntaxException;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import javax.annotation.PostConstruct;
+import org.apache.fineract.cn.stellarbridge.service.ServiceConstants;
+import org.apache.fineract.cn.stellarbridge.service.internal.config.StellarBridgeProperties;
+import org.apache.fineract.cn.stellarbridge.service.internal.federation.StellarAccountId;
+import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+import org.stellar.sdk.Account;
+import org.stellar.sdk.Asset;
+import org.stellar.sdk.KeyPair;
+import org.stellar.sdk.Memo;
+import org.stellar.sdk.PathPaymentOperation;
+import org.stellar.sdk.Server;
+import org.stellar.sdk.Transaction;
+import org.stellar.sdk.responses.AccountResponse;
+import org.stellar.sdk.responses.Page;
+import org.stellar.sdk.responses.PathResponse;
+import org.stellar.sdk.responses.SubmitTransactionResponse;
+
+@Component
+public class HorizonServerUtilities {
+  private final StellarBridgeProperties stellarBridgeProperties;
+  private final Logger logger;
+
+  private Server server;
+
+  private final LoadingCache<String, Account> accounts;
+
+  @Autowired
+  HorizonServerUtilities(
+      final StellarBridgeProperties stellarBridgeProperties,
+      @Qualifier(ServiceConstants.LOGGER_NAME) final Logger logger)
+  {
+    this.stellarBridgeProperties = stellarBridgeProperties;
+    this.logger = logger;
+
+    accounts = CacheBuilder.newBuilder().build(
+        new CacheLoader<String, Account>() {
+          public Account load(final String accountId)
+              throws InvalidConfigurationException {
+            final KeyPair accountKeyPair = KeyPair.fromAccountId(accountId);
+            final StellarAccountHelpers accountHelper = getAccount(accountKeyPair);
+            final Long sequenceNumber = accountHelper.get().getSequenceNumber();
+            return new Account(accountKeyPair, sequenceNumber);
+          }
+        });
+  }
+
+  @PostConstruct
+  void init()
+  {
+    server = new Server(stellarBridgeProperties.getHorizonAddress());
+  }
+
+  public void simplePay(
+      final StellarAccountId targetAccountId,
+      final BigDecimal amount,
+      final String assetCode,
+      final StellarAccountId issuingAccountId,
+      final char[] stellarAccountPrivateKey)
+      throws InvalidConfigurationException, StellarPaymentFailedException
+  {
+    logger.info("HorizonServerUtilities.simplePay");
+    final Asset asset = StellarAccountHelpers.getAsset(assetCode, issuingAccountId);
+
+    pay(targetAccountId, amount, asset, asset, stellarAccountPrivateKey);
+  }
+
+  private void pay(
+      final StellarAccountId targetAccountId,
+      final BigDecimal amount,
+      final Asset sendAsset,
+      final Asset receiveAsset,
+      final char[] stellarAccountPrivateKey)
+      throws InvalidConfigurationException, StellarPaymentFailedException
+  {
+    final KeyPair sourceAccountKeyPair = KeyPair.fromSecretSeed(stellarAccountPrivateKey);
+    final KeyPair targetAccountKeyPair = KeyPair.fromAccountId(targetAccountId.getPublicKey());
+
+    final Account sourceAccount = accounts.getUnchecked(sourceAccountKeyPair.getAccountId());
+
+    final Transaction.Builder transferTransactionBuilder
+        = new Transaction.Builder(sourceAccount);
+    final PathPaymentOperation paymentOperation =
+        new PathPaymentOperation.Builder(
+            sendAsset,
+            StellarAccountHelpers.bigDecimalToStellarBalance(amount),
+            targetAccountKeyPair,
+            receiveAsset,
+            StellarAccountHelpers.bigDecimalToStellarBalance(amount))
+            .setSourceAccount(sourceAccountKeyPair).build();
+
+    transferTransactionBuilder.addOperation(paymentOperation);
+
+    if (targetAccountId.getSubAccount().isPresent())
+    {
+      final Memo subAccountMemo = Memo.text(targetAccountId.getSubAccount().get());
+      transferTransactionBuilder.addMemo(subAccountMemo);
+    }
+
+    submitTransaction(sourceAccount, transferTransactionBuilder, sourceAccountKeyPair,
+        StellarPaymentFailedException::transactionFailed);
+  }
+
+  public BigDecimal getBalance(
+      final StellarAccountId stellarAccountId,
+      final String assetCode)
+  {
+    logger.info("HorizonServerUtilities.getBalance");
+    return getAccount(KeyPair.fromAccountId(stellarAccountId.getPublicKey())).getBalance(assetCode);
+  }
+
+  public BigDecimal getBalanceByIssuer(
+      final StellarAccountId stellarAccountId,
+      final String assetCode,
+      final StellarAccountId accountIdOfIssuingStellarAddress)
+      throws InvalidConfigurationException
+  {
+    logger.info("HorizonServerUtilities.getBalanceByIssuer");
+
+    final Asset asset = StellarAccountHelpers.getAsset(assetCode, accountIdOfIssuingStellarAddress);
+
+    return getAccount(KeyPair.fromAccountId(stellarAccountId.getPublicKey()))
+        .getBalanceOfAsset(asset);
+  }
+
+  private StellarAccountHelpers getAccount(final KeyPair installationAccountKeyPair)
+      throws InvalidConfigurationException
+  {
+    final AccountResponse installationAccount;
+    try {
+      installationAccount = server.accounts().account(installationAccountKeyPair);
+    }
+    catch (final IOException e) {
+      throw InvalidConfigurationException.unreachableStellarServerAddress(stellarBridgeProperties.getHorizonAddress());
+    }
+
+    if (installationAccount == null)
+    {
+      throw InvalidConfigurationException.invalidInstallationAccountSecretSeed();
+    }
+
+    return new StellarAccountHelpers(installationAccount);
+  }
+
+
+  public void findPathPay(
+      final StellarAccountId targetAccountId,
+      final BigDecimal amount,
+      final String assetCode,
+      final char[] stellarAccountPrivateKey)
+      throws InvalidConfigurationException, StellarPaymentFailedException
+  {
+    logger.info("HorizonServerUtilities.findPathPay");
+    final KeyPair sourceAccountKeyPair = KeyPair.fromSecretSeed(stellarAccountPrivateKey);
+    final KeyPair targetAccountKeyPair = KeyPair.fromAccountId(targetAccountId.getPublicKey());
+
+    final StellarAccountHelpers sourceAccount = getAccount(sourceAccountKeyPair);
+    final StellarAccountHelpers targetAccount = getAccount(targetAccountKeyPair);
+
+    final Set<Asset> targetAssets = targetAccount.findAssetsWithTrust(amount, assetCode);
+    final Set<Asset> sourceAssets = sourceAccount.findAssetsWithBalance(amount, assetCode);
+
+    final Optional<MatchingAssetPair> assetPair = findAnyMatchingAssetPair(
+        amount, sourceAssets, targetAssets, sourceAccountKeyPair, targetAccountKeyPair);
+    if (!assetPair.isPresent())
+      throw StellarPaymentFailedException.noPathExists(assetCode);
+
+    pay(targetAccountId, amount,
+        assetPair.get().asset1, assetPair.get().asset2,
+        stellarAccountPrivateKey);
+  }
+
+  static class MatchingAssetPair {
+    final Asset asset1;
+    final Asset asset2;
+
+    MatchingAssetPair(Asset asset1, Asset asset2) {
+      this.asset1 = asset1;
+      this.asset2 = asset2;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      MatchingAssetPair that = (MatchingAssetPair) o;
+      return Objects.equals(asset1, that.asset1) &&
+          Objects.equals(asset2, that.asset2);
+    }
+
+    @Override
+    public int hashCode() {
+
+      return Objects.hash(asset1, asset2);
+    }
+  }
+
+  private Optional<MatchingAssetPair> findAnyMatchingAssetPair(
+      final BigDecimal amount,
+      final Set<Asset> sourceAssets,
+      final Set<Asset> targetAssets,
+      final KeyPair sourceAccountKeyPair,
+      final KeyPair targetAccountKeyPair) {
+    if (sourceAssets.isEmpty())
+      return Optional.empty();
+
+    for (final Asset targetAsset : targetAssets) {
+      Page<PathResponse> paths;
+      try {
+        paths = server.paths()
+            .sourceAccount(sourceAccountKeyPair)
+            .destinationAccount(targetAccountKeyPair)
+            .destinationAsset(targetAsset)
+            .destinationAmount(StellarAccountHelpers.bigDecimalToStellarBalance(amount))
+            .execute();
+      } catch (final IOException e) {
+        return Optional.empty();
+      }
+
+      while (paths != null && paths.getRecords() != null) {
+        for (final PathResponse path : paths.getRecords())
+        {
+          if (StellarAccountHelpers.stellarBalanceToBigDecimal(path.getSourceAmount()).compareTo(amount) <= 0)
+          {
+            if (sourceAssets.contains(path.getSourceAsset()))
+            {
+              return Optional.of(new MatchingAssetPair(path.getSourceAsset(), targetAsset));
+            }
+          }
+        }
+
+        try {
+          paths = ((paths.getLinks() == null) || (paths.getLinks().getNext() == null)) ?
+              null : paths.getNextPage();
+        } catch (final URISyntaxException | IOException e) {
+          return Optional.empty();
+        }
+      }
+    }
+
+    return Optional.empty();
+  }
+
+  private <T extends Exception> void submitTransaction(
+      final Account transactionSubmitter,
+      final Transaction.Builder transactionBuilder,
+      final KeyPair signingKeyPair,
+      final Supplier<T> failureHandler)
+      throws T
+  {
+    try {
+      //final Long sequenceNumberSubmitted = account.getSequenceNumber();
+
+      //noinspection SynchronizationOnLocalVariableOrMethodParameter
+      synchronized (transactionSubmitter) {
+        final Transaction transaction = transactionBuilder.build();
+        transaction.sign(signingKeyPair);
+        final SubmitTransactionResponse transactionResponse = server.submitTransaction(transaction);
+        if (!transactionResponse.isSuccess()) {
+          if (transactionResponse.getExtras() != null) {
+            logger.info("Stellar transaction failed, request: {}", transactionResponse.getExtras().getEnvelopeXdr());
+            logger.info("Stellar transaction failed, response: {}", transactionResponse.getExtras().getResultXdr());
+          }
+          else
+          {
+            logger.info("Stellar transaction failed.  No extra information available.");
+          }
+          //TODO: resend transaction if you get a bad sequence.
+              /*Thread.sleep(6000); //Wait for ledger to close.
+              Long sequenceNumberShouldHaveBeen =
+                  server.accounts().account(account.getKeypair()).getSequenceNumber();
+              if (sequenceNumberSubmitted != sequenceNumberShouldHaveBeen) {
+                logger.info("Sequence number submitted: {}, Sequence number should have been: {}",
+                    sequenceNumberSubmitted, sequenceNumberShouldHaveBeen);
+              }*/
+          throw failureHandler.get();
+        }
+      }
+    } catch (final IOException e) {
+      throw InvalidConfigurationException.unreachableStellarServerAddress(stellarBridgeProperties.getHorizonAddress());
+    }
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/InvalidConfigurationException.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/InvalidConfigurationException.java
new file mode 100644
index 0000000..f0417cb
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/InvalidConfigurationException.java
@@ -0,0 +1,20 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.horizonadapter;
+
+public class InvalidConfigurationException extends RuntimeException {
+  private InvalidConfigurationException(final String message)
+  {
+    super(message);
+  }
+
+  static InvalidConfigurationException invalidInstallationAccountSecretSeed() {
+    return new InvalidConfigurationException(
+        "Invalid installation account secret seed.  Have your admin check configuration.");
+  }
+
+  static InvalidConfigurationException unreachableStellarServerAddress(
+      final String serverAddress) {
+    return new InvalidConfigurationException(
+        "Unreachable stellar server address: " + serverAddress +
+            ". Have your admin check configuration.");
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/StellarAccountHelpers.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/StellarAccountHelpers.java
new file mode 100644
index 0000000..1ad52a0
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/StellarAccountHelpers.java
@@ -0,0 +1,184 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.horizonadapter;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.fineract.cn.stellarbridge.service.internal.federation.StellarAccountId;
+import org.stellar.sdk.Asset;
+import org.stellar.sdk.AssetTypeCreditAlphaNum;
+import org.stellar.sdk.AssetTypeNative;
+import org.stellar.sdk.KeyPair;
+import org.stellar.sdk.responses.AccountResponse;
+import org.stellar.sdk.responses.AccountResponse.Balance;
+
+class StellarAccountHelpers {
+  static String getAssetCode(final Asset asset) {
+    if (asset instanceof AssetTypeCreditAlphaNum)
+    {
+      return ((AssetTypeCreditAlphaNum)asset).getCode();
+    }
+    else
+    {
+      return "XLM";
+    }
+  }
+
+  static String getIssuer(final Asset asset) {
+    if (asset instanceof AssetTypeCreditAlphaNum)
+    {
+      return ((AssetTypeCreditAlphaNum)asset).getIssuer().getAccountId();
+    }
+    else
+    {
+      return "stellar";
+    }
+  }
+
+  static boolean balanceIsInAsset(final AccountResponse.Balance balance, final String assetCode)
+  {
+    if (balance.getAssetType() == null)
+      return false;
+
+    if (balance.getAssetCode() == null) {
+      return assetCode.equals("XLM") && balance.getAssetType().equals("native");
+    }
+
+    return balance.getAssetCode().equals(assetCode);
+  }
+
+  static Asset getAssetOfBalance(final AccountResponse.Balance balance)
+  {
+    if (balance.getAssetCode() == null)
+      return new AssetTypeNative();
+    else
+      return Asset.createNonNativeAsset(balance.getAssetCode(),
+          KeyPair.fromAccountId(balance.getAssetIssuer()));
+  }
+
+  static BigDecimal stellarBalanceToBigDecimal(final String balance)
+  {
+    return BigDecimal.valueOf(Double.parseDouble(balance));
+  }
+
+  static String bigDecimalToStellarBalance(final BigDecimal balance)
+  {
+    return balance.toString();
+  }
+
+
+  static Asset getAsset(final String assetCode, final StellarAccountId targetIssuer) {
+    return Asset.createNonNativeAsset(assetCode, KeyPair.fromAccountId(targetIssuer.getPublicKey()));
+  }
+
+  static BigDecimal remainingTrustInBalance(final AccountResponse.Balance balance)
+  {
+    return stellarBalanceToBigDecimal(balance.getLimit())
+        .subtract(stellarBalanceToBigDecimal(balance.getBalance()));
+  }
+
+  private final AccountResponse account;
+
+  StellarAccountHelpers(final AccountResponse account)
+  {
+    this.account = account;
+  }
+
+  AccountResponse get()
+  {
+    return account;
+  }
+
+  BigDecimal getBalanceOfAsset(final Asset asset)
+  {
+    return getNumericAspectOfAsset(asset,
+        balance -> stellarBalanceToBigDecimal(balance.getBalance()));
+  }
+
+  BigDecimal getNumericAspectOfAsset(
+      final Asset asset,
+      final Function<Balance, BigDecimal> aspect)
+  {
+    final Optional<BigDecimal> balanceOfGivenAsset
+        = Arrays.stream(account.getBalances())
+        .filter(balance -> getAssetOfBalance(balance).equals(asset))
+        .map(aspect)
+        .max(BigDecimal::compareTo);
+
+    //Theoretically there shouldn't be more than one balance, but if this should turn out to be
+    //incorrect, we return the largest one, rather than adding them together.
+
+    return balanceOfGivenAsset.orElse(BigDecimal.ZERO);
+  }
+
+  BigDecimal getBalance(final String assetCode) {
+    final AccountResponse.Balance[] balances = account.getBalances();
+
+    return Arrays.stream(balances)
+        .filter(balance -> balanceIsInAsset(balance, assetCode))
+        .map(balance -> stellarBalanceToBigDecimal(balance.getBalance()))
+        .reduce(BigDecimal.ZERO, BigDecimal::add);
+  }
+
+  Set<Asset> findAssetsWithBalance(
+      final BigDecimal amount,
+      final String assetCode) {
+
+    return findAssetsWithAspect(amount, assetCode,
+        balance -> stellarBalanceToBigDecimal(balance.getBalance()));
+  }
+
+  Set<Asset> findAssetsWithTrust(
+      final BigDecimal amount,
+      final String assetCode) {
+
+    return findAssetsWithAspect(amount, assetCode,
+        StellarAccountHelpers::remainingTrustInBalance);
+  }
+
+  private Set<Asset> findAssetsWithAspect(
+      final BigDecimal amount,
+      final String assetCode,
+      final Function<AccountResponse.Balance, BigDecimal> numericAspect)
+  {
+    return Arrays.stream(account.getBalances())
+        .filter(balance -> balanceIsInAsset(balance, assetCode))
+        .filter(balance -> numericAspect.apply(balance).compareTo(amount) >= 0)
+        .sorted(Comparator.comparing(numericAspect::apply))
+        .map(StellarAccountHelpers::getAssetOfBalance)
+        .collect(Collectors.toSet());
+  }
+
+  Stream<Balance> getAllNonnativeBalancesStream(final String assetCode, final Asset vaultAsset)
+  {
+    return Arrays.stream(account.getBalances())
+        .filter(balance -> balanceIsInAsset(balance, assetCode))
+        .filter(balance -> !getAssetOfBalance(balance).equals(vaultAsset));
+  }
+
+  public BigDecimal getRemainingTrustInAsset(final Asset asset) {
+    return getTrustInAsset(asset).subtract(getBalanceOfAsset(asset));
+  }
+
+  public BigDecimal getTrustInAsset(final Asset asset) {
+    return getNumericAspectOfAsset(asset,
+        balance -> stellarBalanceToBigDecimal(balance.getLimit()));
+  }
+
+  public Stream<AccountResponse.Balance> getVaultBalancesStream(final String stellarVaultAccountId) {
+    return Arrays.stream(account.getBalances())
+        .filter(balance -> balance.getAssetIssuer() != null)
+        .filter(balance -> balance.getAssetIssuer().equals(stellarVaultAccountId))
+        .filter(balance -> stellarBalanceToBigDecimal(balance.getLimit()).compareTo(BigDecimal.ZERO) != 0);
+  }
+
+  public Stream<AccountResponse.Balance> getAllNonnativeBalancesStream() {
+    return Arrays.stream(account.getBalances())
+        .filter(balance -> balance.getAssetIssuer() != null)
+        .filter(balance -> stellarBalanceToBigDecimal(balance.getBalance()).compareTo(BigDecimal.ZERO) != 0);
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/StellarPaymentFailedException.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/StellarPaymentFailedException.java
new file mode 100644
index 0000000..d9f4b3e
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/horizonadapter/StellarPaymentFailedException.java
@@ -0,0 +1,16 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.horizonadapter;
+
+public class StellarPaymentFailedException extends RuntimeException {
+  private StellarPaymentFailedException(final String msg) {
+    super(msg);
+  }
+
+  static StellarPaymentFailedException noPathExists(final String assetCode) {
+    return new StellarPaymentFailedException("No path exists in the given currency: " + assetCode);
+  }
+
+  static StellarPaymentFailedException transactionFailed() {
+    return new StellarPaymentFailedException(
+        "Stellar Horizon server did not accept payment for unknown reason.");
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/BridgeConfigurationEntity.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/BridgeConfigurationEntity.java
index 544d076..9c582eb 100644
--- a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/BridgeConfigurationEntity.java
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/BridgeConfigurationEntity.java
@@ -36,8 +36,12 @@ public class BridgeConfigurationEntity {
   private String fineractIncomingAccountIdentifier;
   @Column(name = "fineract_outgoing_identifier")
   private String fineractOutgoingAccountIdentifier;
+  @Column(name = "fineract_staging_identifier")
+  private String fineractStagingAccountIdentifier;
   @Column(name = "stellar_identifier")
   private String stellarAccountIdentifier;
+  @Column(name = "stellar_account_private_key")
+  private char[] stellarAccountPrivateKey;
 
   public BridgeConfigurationEntity() {
     super();
@@ -75,6 +79,14 @@ public class BridgeConfigurationEntity {
     this.fineractOutgoingAccountIdentifier = fineractOutgoingAccountIdentifier;
   }
 
+  public String getFineractStagingAccountIdentifier() {
+    return fineractStagingAccountIdentifier;
+  }
+
+  public void setFineractStagingAccountIdentifier(String fineractStagingAccountIdentifier) {
+    this.fineractStagingAccountIdentifier = fineractStagingAccountIdentifier;
+  }
+
   public String getStellarAccountIdentifier() {
     return stellarAccountIdentifier;
   }
@@ -83,6 +95,14 @@ public class BridgeConfigurationEntity {
     this.stellarAccountIdentifier = stellarAccountIdentifier;
   }
 
+  public char[] getStellarAccountPrivateKey() {
+    return stellarAccountPrivateKey;
+  }
+
+  public void setStellarAccountPrivateKey(char[] stellarAccountPrivateKey) {
+    this.stellarAccountPrivateKey = stellarAccountPrivateKey;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/BridgeConfigurationEntityRepository.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/BridgeConfigurationRepository.java
similarity index 85%
rename from service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/BridgeConfigurationEntityRepository.java
rename to service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/BridgeConfigurationRepository.java
index 28839ad..358d064 100644
--- a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/BridgeConfigurationEntityRepository.java
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/BridgeConfigurationRepository.java
@@ -23,6 +23,8 @@ import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 
 @Repository
-public interface BridgeConfigurationEntityRepository extends JpaRepository<BridgeConfigurationEntity, Long> {
+public interface BridgeConfigurationRepository extends JpaRepository<BridgeConfigurationEntity, Long> {
   Optional<BridgeConfigurationEntity> findByTenantIdentifier(String identifier);
+
+  BridgeConfigurationEntity findByStellarAccountIdentifier(String accountId);
 }
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/StellarCursorEntity.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/StellarCursorEntity.java
new file mode 100644
index 0000000..c69bc17
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/StellarCursorEntity.java
@@ -0,0 +1,64 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.repository;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import java.util.Date;
+
+@SuppressWarnings("unused")
+@Entity
+@Table(name = "nenet_stellar_cursor")
+public class StellarCursorEntity {
+
+  @Id
+  @GeneratedValue(strategy = GenerationType.IDENTITY)
+  @Column(name = "id")
+  private Long id;
+
+  @Column(name = "cursor")
+  private String cursor;
+
+  @SuppressWarnings("unused")
+  @Column(name = "processed")
+  private Boolean processed;
+
+  @Column(name = "created_on")
+  @Temporal(TemporalType.TIMESTAMP)
+  private Date createdOn;
+
+  @SuppressWarnings("unused")
+  public StellarCursorEntity() { }
+
+  public StellarCursorEntity(final String cursor, final Date createdOn) {
+    this.cursor = cursor;
+    this.processed = false;
+    this.createdOn = createdOn;
+  }
+
+  public String getCursor() {
+    return cursor;
+  }
+
+  public void setProcessed(Boolean processed) {
+    this.processed = processed;
+  }
+
+  public Boolean getProcessed() {
+    return processed;
+  }
+
+  @SuppressWarnings("unused")
+  public Date getCreatedOn() {
+    return createdOn;
+  }
+
+  @SuppressWarnings("unused")
+  public void setCreatedOn(final Date createdOn) {
+    this.createdOn = createdOn;
+  }
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/StellarCursorRepository.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/StellarCursorRepository.java
new file mode 100644
index 0000000..3ead558
--- /dev/null
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/repository/StellarCursorRepository.java
@@ -0,0 +1,11 @@
+package org.apache.fineract.cn.stellarbridge.service.internal.repository;
+
+import java.util.Optional;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface StellarCursorRepository extends CrudRepository<StellarCursorEntity, Long> {
+  Optional<StellarCursorEntity> findTopByProcessedTrueOrderByCreatedOnDesc();
+  Optional<StellarCursorEntity> findByCursor(String cursor);
+}
diff --git a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/service/BridgeConfigurationService.java b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/service/BridgeConfigurationService.java
index b6ccc5f..4eb621e 100644
--- a/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/service/BridgeConfigurationService.java
+++ b/service/src/main/java/org/apache/fineract/cn/stellarbridge/service/internal/service/BridgeConfigurationService.java
@@ -21,22 +21,22 @@ package org.apache.fineract.cn.stellarbridge.service.internal.service;
 import java.util.Optional;
 import org.apache.fineract.cn.stellarbridge.api.v1.domain.BridgeConfiguration;
 import org.apache.fineract.cn.stellarbridge.service.internal.mapper.BridgeConfigurationMapper;
-import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationEntityRepository;
+import org.apache.fineract.cn.stellarbridge.service.internal.repository.BridgeConfigurationRepository;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 @Service
 public class BridgeConfigurationService {
 
-  private final BridgeConfigurationEntityRepository bridgeConfigurationEntityRepository;
+  private final BridgeConfigurationRepository bridgeConfigurationRepository;
 
   @Autowired
-  public BridgeConfigurationService(final BridgeConfigurationEntityRepository bridgeConfigurationEntityRepository) {
+  public BridgeConfigurationService(final BridgeConfigurationRepository bridgeConfigurationRepository) {
     super();
-    this.bridgeConfigurationEntityRepository = bridgeConfigurationEntityRepository;
+    this.bridgeConfigurationRepository = bridgeConfigurationRepository;
   }
 
   public Optional<BridgeConfiguration> findByTenantIdentifier(final String tenantIdentifier) {
-    return this.bridgeConfigurationEntityRepository.findByTenantIdentifier(tenantIdentifier).map(BridgeConfigurationMapper::map);
+    return this.bridgeConfigurationRepository.findByTenantIdentifier(tenantIdentifier).map(BridgeConfigurationMapper::map);
   }
 }
diff --git a/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
index d1614c6..d5d568e 100644
--- a/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
+++ b/service/src/main/resources/db/migrations/mariadb/V1__initial_setup.sql
@@ -20,9 +20,20 @@
 CREATE TABLE nenet_configuration (
   id BIGINT NOT NULL AUTO_INCREMENT,
   tenant_identifier        VARCHAR(32)  NOT NULL,
-  fineract_incoming_identifier VARCHAR(512) NOT NULL,
-  fineract_outgoing_identifier VARCHAR(512) NOT NULL,
+  fineract_incoming_ledger VARCHAR(512) NOT NULL,
+  fineract_outgoing_ledger VARCHAR(512) NOT NULL,
+  fineract_stellar_ledger VARCHAR(512) NOT NULL,
   stellar_identifier VARCHAR(512) NULL,
-  CONSTRAINT nenet_uq UNIQUE (tenant_identifier),
-  CONSTRAINT stellar_identifier PRIMARY KEY (id)
+  stellar_private_key VARCHAR(512) NULL,
+  CONSTRAINT nenet_configuration_uq UNIQUE (tenant_identifier),
+  CONSTRAINT nenet_configuration_pk PRIMARY KEY (id)
 );
+
+CREATE TABLE nenet_stellar_cursor (
+  id BIGINT NOT NULL AUTO_INCREMENT,
+  xcursor            VARCHAR(50)  NOT NULL,
+  processed          BOOLEAN      NOT NULL,
+  created_on         TIMESTAMP    NOT NULL,
+  CONSTRAINT nenet_stellar_cursor_uq UNIQUE (xcursor),
+  CONSTRAINT nenet_stellar_cursor_pk PRIMARY KEY (id)
+);
\ No newline at end of file
diff --git a/shared.gradle b/shared.gradle
index 778c355..7d08b55 100644
--- a/shared.gradle
+++ b/shared.gradle
@@ -28,7 +28,8 @@ ext.versions = [
         frameworktest: '0.1.0-BUILD-SNAPSHOT',
         frameworkanubis: '0.1.0-BUILD-SNAPSHOT',
         frameworkpermittedfeignclient: '0.1.0-BUILD-SNAPSHOT',
-        identity:  '0.1.0-BUILD-SNAPSHOT',
+        accounting :  '0.1.0-BUILD-SNAPSHOT',
+        stellar : '0.1.6',
         validator : '5.3.0.Final'
 ]
 
@@ -45,6 +46,8 @@ tasks.withType(JavaCompile) {
 repositories {
     jcenter()
     mavenLocal()
+    mavenCentral()
+    maven { url "https://jitpack.io" }
 }
 
 dependencyManagement {