Building a mobile wallet application

Mobile wallets, reward wallets, and other types of wallet applications are easy to build on top of Sequence. These applications tend to have a few things in common — consumers accumulate balances, which they use to pay other consumers or checkout at merchants, the company offering the wallet often provides a currency exchange function, and consumers and merchants periodically withdraw currencies from their accounts.

In this guide, we explore how to build a mobile wallet application on top of Sequence.

Overview

In our example mobile wallet system, there are two types of users: consumers and merchants. These will each be represented as accounts in the ledger. Additionally, we will create an account for the company, where fees are collected from merchants.

There are two currencies, USD and EUR, as well as loyalty points. These will each be represented as assets in the ledger. (This can be extended to any number of different currencies.)

Currencies can be deposited by consumers, transferred between consumers, paid to merchants by consumers, and withdrawn by merchants and consumers. Additionally, loyalty points are earned by consumers when making purchases at merchants. All of these interactions will be represented as transactions in the ledger.

Setup

To set up our ledger, we will create several keys, assets, and accounts.

Keys

Authority to create transactions in the ledger is assigned to four distinct systems:

  1. Treasury - responsible for processing deposits and withdrawals, collecting fees from merchants, and performing currency exchange
  2. Consumer - responsible for managing withdrawals and transfers from consumer accounts
  3. Merchant - responsible for managing withdrawals from merchant accounts
  4. Loyalty - responsible for distributing loyalty points

Each system will have a key that will be used to perform their actions in the ledger. Let's create a key for each system:

new Key.Builder()
  .setAlias("treasury")
  .create(ledger);

new Key.Builder()
  .setAlias("consumer")
  .create(ledger);

new Key.Builder()
  .setAlias("merchant")
  .create(ledger);

new Key.Builder()
  .setAlias("loyalty")
  .create(ledger);
ledger.keys.create({alias: 'treasury'})
ledger.keys.create({alias: 'consumer'})
ledger.keys.create({alias: 'merchant'})
ledger.keys.create({alias: 'loyalty'})
ledger.keys.create(alias: 'treasury')
ledger.keys.create(alias: 'consumer')
ledger.keys.create(alias: 'merchant')
ledger.keys.create(alias: 'loyalty')

Assets

Assets represent the different types of balances in merchant and consumer accounts. We will create assets for USD, EUR, and points.

First, we create the currency assets using the treasury key:

new Asset.Builder()
  .setAlias("usd")
  .addKeyByAlias("treasury")
  .addTag("type", "currency")
  .create(ledger);

new Asset.Builder()
  .setAlias("eur")
  .addKeyByAlias("treasury")
  .addTag("type", "currency")
  .create(ledger);
ledger.assets.create({
  alias: 'usd',
  keys: [{alias: 'treasury'}],
  tags: {type: 'currency'}
})

ledger.assets.create({
  alias: 'eur',
  keys: [{alias: 'treasury'}],
  tags: {type: 'currency'}
})
ledger.assets.create(
  alias: 'usd',
  keys: [{alias: 'treasury'}],
  tags: {type: 'currency'}
)

ledger.assets.create(
  alias: 'eur',
  keys: [{alias: 'treasury'}],
  tags: {type: 'currency'}
)

Next, we create the points asset using the loyalty key:

new Asset.Builder()
  .setAlias("points")
  .addKeyByAlias("loyalty")
  .create(ledger);
ledger.assets.create({
  alias: 'points',
  keys: [{alias: 'loyalty'}]
})
ledger.assets.create(
  alias: 'points',
  keys: [{alias: 'loyalty'}]
)

Accounts

For each user or merchant that signs up for the wallet application, we will need an account in the ledger. Although these accounts would actually be created by the wallet application in real-time, for this example, we'll assume we have two consumers and two merchants and create them as part of the setup. We also need the company account where we will collect fees.

We will use tags to differentiate between the types of accounts.

First, we create the consumer accounts using the consumer key:

new Account.Builder()
  .setAlias("alice")
  .addKeyByAlias("consumer")
  .addTag("type", "consumer")
  .create(ledger);

new Account.Builder()
  .setAlias("bob")
  .addKeyByAlias("consumer")
  .addTag("type", "consumer")
  .create(ledger);
ledger.accounts.create({
  alias: 'alice',
  keys: [{alias: 'consumer'}],
  tags: {type: 'consumer'}
})

ledger.accounts.create({
  alias: 'bob',
  keys: [{alias: 'consumer'}],
  tags: {type: 'consumer'}
})
ledger.accounts.create(
  alias: 'alice',
  keys: [{alias: 'consumer'}],
  tags: {type: 'consumer'}
)

ledger.accounts.create(
  alias: 'bob',
  keys: [{alias: 'consumer'}],
  tags: {type: 'consumer'}
)

Next, we create the merchant accounts using the merchant key:

new Account.Builder()
  .setAlias("merchant1")
  .addKeyByAlias("merchant")
  .addTag("type", "merchant")
  .create(ledger);

new Account.Builder()
  .setAlias("merchant2")
  .addKeyByAlias("merchant")
  .addTag("type", "merchant")
  .create(ledger);
ledger.accounts.create({
  alias: 'merchant1',
  keys: [{alias: 'merchant'}],
  tags: {type: 'merchant'}
})

ledger.accounts.create({
  alias: 'merchant2',
  keys: [{alias: 'merchant'}],
  tags: {type: 'merchant'}
})
ledger.accounts.create(
  alias: 'merchant1',
  keys: [{alias: 'merchant'}],
  tags: {type: 'merchant'}
)

ledger.accounts.create(
  alias: 'merchant2',
  keys: [{alias: 'merchant'}],
  tags: {type: 'merchant'}
)

Finally, we create the company account using the treasury key:

new Account.Builder()
  .setAlias("company")
  .addKeyByAlias("treasury")
  .addTag("type", "company")
  .create(ledger);
ledger.accounts.create({
  alias: 'company',
  keys: [{alias: 'treasury'}],
  tags: {type: 'company'}
})
ledger.accounts.create(
  alias: 'company',
  keys: [{alias: 'treasury'}],
  tags: {type: 'company'}
)

Transaction Types

Now that we have created our assets and accounts, we can model the different types of transactions.

Deposit

When a consumer deposits money, we create a transaction containing an issue action to issue the amount of the deposited currency into their account.

We can use action reference data to record details about the deposit, such as the deposit method and associated transaction ID in that external system.

For this example, we assume that Alice deposits $100.00 via ACH. Note that the amount of issuance is 10000, since the fundamental unit of the USD asset is a cent.

new Transaction.Builder()
  .addAction(new Transaction.Builder.Action.Issue()
    .setAssetAlias("usd")
    .setAmount(10000)
    .setDestinationAccountAlias("alice")
    .addReferenceDataField("type", "deposit")
    .addReferenceDataField("system", "ach")
    .addReferenceDataField("ach_transaction_id", "11111")
  ).transact(ledger);
ledger.transactions.transact(builder => {
  builder.issue({
    assetAlias: 'usd',
    amount: 10000,
    destinationAccountAlias: 'alice',
    referenceData: {
      type: 'deposit',
      system: 'ach',
      ach_transaction_id: '11111'
    }
  })
})
ledger.transactions.transact do |builder|
  builder.issue(
    asset_alias: 'usd',
    amount: 10000,
    destination_account_alias: 'alice',
    reference_data: {
      type: 'deposit',
      system: 'ach',
      ach_transaction_id: '11111'
    }
  )
end

Since this transaction issues the USD asset, it must be signed by the treasury key. This is handled automatically by the transact SDK method (because we added that key when we created the asset).

P2P payment (consumer-to-consumer)

When a consumer transfers money to another consumer, we create a transaction containing a transfer action to transfer the amount of the requested currency from the sender to the recipient.

We can use action reference data to record the reason for the transfer.

For this example, we assume that Alice transfers $25.50 to Bob. Note that the amount of issuance is 2550, since the fundamental unit of the USD asset is a cent.

new Transaction.Builder()
  .addAction(new Transaction.Builder.Action.Transfer()
    .setAssetAlias("usd")
    .setAmount(2550)
    .setSourceAccountAlias("alice")
    .setDestinationAccountAlias("bob")
    .addReferenceDataField("type", "p2p_payment")
  ).transact(ledger);
ledger.transactions.transact(builder => {
  builder.transfer({
    assetAlias: 'usd',
    amount: 2550,
    sourceAccountAlias: 'alice',
    destinationAccountAlias: 'bob',
    referenceData: {type: 'p2p_payment'}
  })
})
ledger.transactions.transact do |builder|
  builder.transfer(
    asset_alias: 'usd',
    amount: 2550,
    source_account_alias: 'alice',
    destination_account_alias: 'bob',
    reference_data: {type: 'p2p_payment'}
  )
end

Since this transaction transfers from Alice's account, it must be signed by the consumer key. This is handled automatically by the transact SDK method.

Merchant payment

When a consumer pays a merchant, the company keeps a portion as a fee. Additionally, the consumer receives loyalty points in return.

We model this as a single atomic transaction with three actions:

  • transfer - payment amount of currency from consumer to merchant
  • transfer - fee amount of currency from merchant to company
  • issue - amount of loyalty points earned to consumer

For this example, we will assume a $10 payment from Alice to Merchant 1, with a fee rate of 2%, and a loyalty point earning rate of 1 point per cent spent. Note that the amount of the payment is 1000 and the amount of the fee is 20, since the fundamental unit of the USD asset is a cent.

new Transaction.Builder()
  .addAction(new Transaction.Builder.Action.Transfer()
    .setAssetAlias("usd")
    .setAmount(1000)
    .setSourceAccountAlias("alice")
    .setDestinationAccountAlias("merchant1")
    .addReferenceDataField("type", "merchant_payment")
  ).addAction(new Transaction.Builder.Action.Transfer()
    .setAssetAlias("usd")
    .setAmount(20)
    .setSourceAccountAlias("merchant1")
    .setDestinationAccountAlias("company")
    .addReferenceDataField("type", "company_fee")
  ).addAction(new Transaction.Builder.Action.Issue()
    .setAssetAlias("points")
    .setAmount(1000)
    .setDestinationAccountAlias("alice")
    .addReferenceDataField("type", "points_earned")
  ).transact(ledger);
ledger.transactions.transact(builder => {
  builder.transfer({
    assetAlias: 'usd',
    amount: 1000,
    sourceAccountAlias: 'alice',
    destinationAccountAlias: 'merchant1',
    referenceData: {type: 'merchant_payment'}
  })
  builder.transfer({
    assetAlias: 'usd',
    amount: 20,
    sourceAccountAlias: 'merchant1',
    destinationAccountAlias: 'company',
    referenceData: {type: 'company_fee'}
  })
  builder.issue({
    assetAlias: 'points',
    amount: 1000,
    destinationAccountAlias: 'alice',
    referenceData: {type: 'points_earned'}
  })
})
ledger.transactions.transact do |builder|
  builder.transfer(
    asset_alias: 'usd',
    amount: 1000,
    source_account_alias: 'alice',
    destination_account_alias: 'merchant1',
    reference_data: {type: 'merchant_payment'}
  )
  builder.transfer(
    asset_alias: 'usd',
    amount: 20,
    source_account_alias: 'merchant1',
    destination_account_alias: 'company',
    reference_data: {type: 'company_fee'}
  )
  builder.issue(
    asset_alias: 'points',
    amount: 1000,
    destination_account_alias: 'alice',
    reference_data: {type: 'points_earned'}
  )
end

Since this transaction transfers from Alice's account, transfers from Merchant 1's account, and issues points, it must be signed by the consumer key, the merchant key, and the loyalty key. This is handled automatically by the transact SDK method.

Note that although the total payment amount is initially transfered from the consumer to the merchant in the first action, the fee is paid to the company within another action in the same transaction. Therefore, only 980 ever shows up as a balance in the merchant's account. This allows us to query the total amount of sales of the merchant, distinct from the total amount of revenue collected in the merchant's account.

Merchant FX (foreign exchange) payment

If a user holds one currency, but a merchant accepts a different currency, we need to have a process by which currency is exchanged. To facilitate this, the company will act as a currency exchange within the transaction.

We model this currency exchange as single atomic transaction with two actions:

  • transfer - payment amount of consumer currency from consumer to company
  • transfer - converted amount (based on company fx rate) of merchant currency from company to merchant

The currency exchange rate would be determined by the company at the time of transaction, and the consumer would be presented with the amount of their currency required to pay the amount of the merchant's currency.

Additionally, we add the two other actions as shown in the standard merchant payment example above:

  • transfer - fee amount of merchant's currency from merchant to company
  • issue - amount of loyalty points earned to consumer

In this example, we assume that Alice needs to pay 20.00 EUR to Merchant 2, which the company can provide for 26.50 USD. Additionally, the company will take a fee of 0.40 EUR as a fee from Merchant 2 (2% of the payment amount in EUR) and Alice will earn 1 loyalty point per cent spent in USD.

The company will need to have enough EUR in it's account to facilitate the payment, so first let's issue some EUR to the company account.

new Transaction.Builder()
  .addAction(new Transaction.Builder.Action.Issue()
    .setAssetAlias("eur")
    .setAmount(50000)
    .setDestinationAccountAlias("company")
    .addReferenceDataField("type", "fx_deposit")
  ).transact(ledger);
ledger.transactions.transact(builder => {
  builder.issue({
    assetAlias: 'eur',
    amount: 50000,
    destinationAccountAlias: 'company',
    referenceData: {type: 'fx_deposit'}
  })
})
ledger.transactions.transact do |builder|
  builder.issue(
    asset_alias: 'eur',
    amount: 50000,
    destination_account_alias: 'company',
    reference_data: {type: 'fx_deposit'}
  )
end

Now that the company account has enough EUR, we can proceed with our FX payment.

new Transaction.Builder()
  .addAction(new Transaction.Builder.Action.Transfer()
    .setAssetAlias("usd")
    .setAmount(2650)
    .setSourceAccountAlias("alice")
    .setDestinationAccountAlias("company")
    .addReferenceDataField("type", "merchant_payment")
    .addReferenceDataField("sub_type", "fx_payment")
  ).addAction(new Transaction.Builder.Action.Transfer()
    .setAssetAlias("eur")
    .setAmount(2000)
    .setSourceAccountAlias("company")
    .setDestinationAccountAlias("merchant2")
    .addReferenceDataField("type", "fx_payment")
  ).addAction(new Transaction.Builder.Action.Transfer()
    .setAssetAlias("eur")
    .setAmount(40)
    .setSourceAccountAlias("merchant2")
    .setDestinationAccountAlias("company")
    .addReferenceDataField("type", "company_fee")
  ).addAction(new Transaction.Builder.Action.Issue()
    .setAssetAlias("points")
    .setAmount(2650)
    .setDestinationAccountAlias("alice")
    .addReferenceDataField("type", "points_earned")
  ).transact(ledger);
ledger.transactions.transact(builder => {
  builder.transfer({
    assetAlias: 'usd',
    amount: 2650,
    sourceAccountAlias: 'alice',
    destinationAccountAlias: 'company',
    referenceData: {
      type: 'merchant_payment',
      sub_type: 'fx_payment'
    }
  })
  builder.transfer({
    assetAlias: 'eur',
    amount: 40,
    sourceAccountAlias: 'company',
    destinationAccountAlias: 'merchant2',
    referenceData: {type: 'fx_payment'}
  })
  builder.transfer({
    assetAlias: 'eur',
    amount: 40,
    sourceAccountAlias: 'merchant2',
    destinationAccountAlias: 'company',
    referenceData: {type: 'company_fee'}
  })
  builder.issue({
    assetAlias: 'points',
    amount: 2650,
    destinationAccountAlias: 'alice',
    referenceData: {type: 'points_earned'}
  })
})
ledger.transactions.transact do |builder|
  builder.transfer(
    asset_alias: 'usd',
    amount: 2650,
    source_account_alias: 'alice',
    destination_account_alias: 'company',
    reference_data: {
      type: 'merchant_payment',
      sub_type: 'fx_payment'
    }
  )
  builder.transfer(
    asset_alias: 'eur',
    amount: 2000,
    source_account_alias: 'company',
    destination_account_alias: 'merchant2',
    reference_data: {type: 'fx_payment'}
  )
  builder.transfer(
    asset_alias: 'eur',
    amount: 40,
    source_account_alias: 'merchant2',
    destination_account_alias: 'company',
    reference_data: {type: 'company_fee'}
  )
  builder.issue(
    asset_alias: 'points',
    amount: 2000,
    destination_account_alias: 'alice',
    reference_data: {type: 'points_earned'}
  )
end

Since this transaction transfers from Alice's account, transfers from the company account, transfers from Merchant 2's account, and issues points, it must be signed by the consumer key, the treasury key, the merchant key, and the loyalty key. This is handled automatically by the transact SDK method.

Withdrawal

When a consumer or merchant withdraws money, we create a transaction containing a retire action to retire the withdrawal amount of the currency asset from their account.

We can use action reference data to record details about the withdrawal, such as the withdrawal method and associated transaction ID in that external system.

For this example, we'll assume that Merchant1 withdraws $5 via ACH. Note that the amount being retired is 500, since the fundamental unit of the USD asset is a cent.

Transaction tx = new Transaction.Builder()
  .addAction(new Transaction.Builder.Action.Retire()
    .setAssetAlias("usd")
    .setAmount(500)
    .setSourceAccountAlias("merchant1")
    .addReferenceDataField("type", "withdrawal")
    .addReferenceDataField("system", "ach")
    .addReferenceDataField("ach_transaction_id", "22222")
  ).transact(ledger);
ledger.transactions.transact(builder => {
  builder.retire({
    assetAlias: 'usd',
    amount: 500,
    sourceAccountAlias: 'merchant1',
    referenceData: {
      type: 'withdrawal',
      system: 'ach',
      ach_transaction_id: '22222'
    }
  })
})
tx = ledger.transactions.transact do |builder|
  builder.retire(
    asset_alias: 'usd',
    amount: 500,
    source_account_alias: 'merchant1',
    reference_data: {
      type: 'withdrawal',
      system: 'ach',
      ach_transaction_id: '22222'
    }
  )
end

Since this transaction retires from a merchant account, it must be signed by the merchant key. This is handled automatically by the transact SDK method.

Queries

Now that we have created several transactions, we can query the ledger in various ways.

Balances in an account

If we want to know the amount of each asset in an account, we perform a balance query, filtering to the account alias and summing the results by asset alias.

For example, let's list the balances in Alice's account.

Balance.ItemIterable balances = new Balance.QueryBuilder()
  .setFilter("account_alias=$1")
  .addFilterParameter("alice")
  .addSumByField("asset_alias")
  .getIterable(ledger);
for (Balance balance : balances) {
  System.out.println("amount: " + balance.amount );
  System.out.println("asset: " + balance.sumBy.get("asset_alias"));
  System.out.println("");
}
ledger.balances.queryAll({
  filter: 'account_alias=$1',
  filterParams: ['alice'],
  sumBy: ['asset_alias']
}).then(balances => {
  for (let i in balances) {
    const balance = balances[i];
    console.log('amount: ' + balance.amount )
    console.log('asset: ' + balance.sumBy.assetAlias )
    console.log('')
  }
})
ledger.balances.query(
  filter: 'account_alias=$1',
  filter_params: ['alice'],
  sum_by: ['asset_alias']
).each do |balance|
  puts 'amount: ' + balance.amount
  puts 'asset: ' + balance.sum_by.asset_alias
  puts ''
end

Which will output:

amount: x
asset: usd

amount: y
asset: points

Total amount of USD in the ledger

If we want to know the total amount of USD in the ledger across all accounts (merchants, consumers, and company), we perform a balance query, filtering to the USD asset alias and summing the results by asset alias.

Balance.ItemIterable balances = new Balance.QueryBuilder()
  .setFilter("asset_alias=$1")
  .addFilterParameter("usd")
  .addSumByField("asset_alias")
  .getIterable(ledger);
for (Balance balance : balances) {
  System.out.println("amount: " + balance.amount);
  System.out.println("asset: " + balance.sumBy.get("asset_alias"));
  System.out.println("");
}
ledger.balances.queryAll({
  filter: 'asset_alias=$1',
  filterParams: ['usd'],
  sumBy: ['asset_alias']
}).then(balances => {
  for (let i in balances) {
    const balance = balances[i];
    console.log('amount: ' + balance.amount )
    console.log('asset: ' + balance.sumBy.assetAlias )
    console.log('')
  }
})
ledger.balances.query(
  filter: 'asset_alias=$1',
  filter_params: ['usd'],
  sum_by: ['asset_alias']
).each do |balance|
  puts 'amount: ' + balance.amount
  puts 'asset: ' + balance.sum_by.asset_alias
  puts ''
end

Which will output:

amount: 100
asset: usd

Amount of USD in each type of account

If we want to know the amount of USD in the ledger, broken out by type of account (merchants, consumers, and company), we perform a balance query, filtering to the USD asset alias and summing the results by asset alias and the type account tag.

Balance.ItemIterable balances = new Balance.QueryBuilder()
  .setFilter("asset_alias=$1")
  .addFilterParameter("usd")
  .addSumByField("asset_alias")
  .addSumByField("account_tags.type")
  .getIterable(ledger);
for (Balance balance : balances) {
  System.out.println("amount: " + balance.amount);
  System.out.println("account type: " + balance.sumBy.get("account_tags.type"));
  System.out.println("");
}
ledger.balances.queryAll({
  filter: 'asset_alias=$1',
  filterParams: ['usd'],
  sumBy: ['asset_alias','account_tags.type']
}).then(balances => {
  for (let i in balances) {
    const balance = balances[i];
    console.log('amount: ' + balance.amount )
    console.log('account type: ' + balance.sumBy['accountTags.type'])
    console.log('')
  }
})
ledger.balances.query(
  filter: 'asset_alias=$1',
  filter_params: ['usd'],
  sum_by: ['asset_alias','account_tags.type']
).each do |balance|
  puts 'amount: ' + balance.amount
  puts 'account type: ' + balance.sum_by["account_tags.type"]
  puts ''
end

Which will output:

amount: x
account type: consumer

amount: y
account type: merchant

amount: z
account type: company

Recent transactions in an account

If we want to display the latest transactions for a specific account, we perform a transaction query, filtering to actions that contain the account as the source_account or destination_account.

Let's query for all the transactions that involve Alice's account and display the type of transaction by accessing the type field in the action reference data.

Transaction.ItemIterable txs = new Transaction.QueryBuilder()
  .setFilter("actions(source_account_alias=$1 OR destination_account_alias=$2)")
  .addFilterParameter("alice")
  .addFilterParameter("alice")
  .getIterable(ledger);
for (Transaction tx : txs) {
  for (Transaction.Action action : tx.actions) {
    if ("alice".equals(action.sourceAccountAlias) || "alice".equals(action.destinationAccountAlias)) {
      System.out.println("type: " + action.referenceData.get("type"));
      System.out.println("asset: " + action.assetAlias);
      System.out.println("amount: " + action.amount);
      System.out.println("to: " + action.destinationAccountAlias);
      System.out.println("");
    }
  }
}
ledger.transactions.queryAll({
  filter: 'actions(source_account_alias=$1 OR destination_account_alias=$1)',
  filterParams: ['alice']
}).then(txs => {
  for (let i in txs) {
    const tx = txs[i];
    for (let j in tx.actions) {
      const action = tx.actions[j]
      if (action.sourceAccountAlias == 'alice' || action.destinationAccountAlias == 'alice') {
        console.log('type: ' + action.referenceData.type)
        console.log('asset: ' + action.assetAlias)
        console.log('amount: ' + action.amount)
        console.log('to: ' + action.destinationAccountAlias)
        console.log('')
      }
    }
  }
})
ledger.transactions.query(
  filter: 'actions(source_account_alias=$1 OR destination_account_alias=$1)',
  filter_params: ['alice']
).each do |tx|
  tx['actions'].each do |action|
    if action['source_account_alias'] == 'alice' || action['destination_account_alias'] == 'alice'
      puts 'type: ' + action['type']
      puts 'asset: ' + action['asset_alias']
      puts 'amount: ' + action['amount'].to_s
      puts 'to: ' + action['destination_account_alias']
      puts ''
    end
  end
end

Which will output:

type: merchant_payment
asset: usd
amount: 500
to: merchant1

type: p2p_transfer
asset: usd
amount: 1000
from: alice

type: p2p_transfer
asset: usd
amount: 2000
to: alice