Minka Ledger Docs
TutorialsAlias Directory

Alias directory onboarding tutorial

DateResponsibleChanges
December 23, 2022@Tomislav HermanInitial version
January 19, 2023@Tomislav HermanUpdated JSON payloads and wallet addresses according to new addressing schema.
January 20, 2023@Tomislav HermanAdded steps to create a bridge record for alias directory service. Updated code examples to match lates ledger-sdk.
January 27, 2023@Tomislav HermanChanged title from ‘Onboard alias directory users’ to ‘Alias directory onboarding tutorial’
February 10, 2023@Omar MonterreySDK Options refactoring (url to server and key to signer)
February 27, 2023@Omar MonterreyRefactored signer.schema-> signer.format
March 13, 2023@Tomislav HermanRenamed ledgersdk in code examples. Added real example of credit received on phone alias service when onboarding prone number.
April 17, 2023@Omar MonterreyRemoved config.signer property from bridge
May 22, 2023@Omar MonterreyRemoved bridge.schema property from bridge

Example of mobile phone wallet data configured to forward balance sent to phone number to different bank accounts for different currencies:

{
  "handle": "tel:15261234578",
  "routes": [{
    "filter": {
      "symbol": "usd"
    },
    "action": "forward",
    "target": "account:343454345@pbz"
  }, {
    "filter": {
      "symbol": "eur"
    },
    "action": "forward",
    "target": "account:41111339@zaba"
  }],
	"access": []
}

For example, this wallet will forward all balance movements of usd which initially targets the phone number 15261234578 to the wallet representing the account 343454345 in the bank pbz.

We will now show the process how this wallet is configured by different actors in the process. Examples are written in NodeJS by using @minka/ledger-sdk.

Creating phone alias bridge wallet

First we will create a bridge record which will represent the phone alias directory service. This code is executed only once during setup of a clearinghouse. It can be executed through CLI by admin, or from a server side code by using SDK. Example below shows execution from the phone alias directory service hosted on clearinghouse side.

import { LedgerSdk } from '@minka/ledger-sdk'
 
// Connect to ledger instance and provide a ledger public key
// in order to validate responses from it.
const sdk = new LedgerSdk({
  server: '<ledger url>',
  signer: {
		format: 'ed25519-raw',
		public: '<ledger public key>'
	}
})
 
// Populate this object with directory keys you have created previously
const serviceKeyPair = {
  format: 'ed25519-raw',
  public: '<service public key>',
  secret: '<service private key>'
}
 
const { bridge } = await sdk.bridge
	.init()
	.data({
		handle: 'tel',
		config: {
			// URL where the directory service bridge will be hosted and receive
			// calls from the ledger. By convention it should end with /v2 suffix.
			server: '<service bridge url>'
		},
		secure: [],
		access: []
	})
	.hash()
	.sign([{ keyPair: serviceKeyPair }])
	.send()

Created bridge record data:

{
  "handle": "tel",
  "config": {
    "server": "<service bridge url>",
    "signer": {
        "public": "<service public key>",
        "format": "ed25519-raw"
      }
  },
  "secure": [],
  "access": []
}

Then we will create a bridge type wallet described here: About Wallets, which will serve as a catch-all for all balance movements which target any phone number and pass them for processing to the bridge created above.

const { wallet } = await sdk.wallet
	.init()
	.data({
		handle: 'tel',
		bridge: 'tel',
		access: []
	})
	.hash()
	.sign([{ keyPair: serviceKeyPair }])
	.send()

Created wallet record data:

{
  "handle": "tel",
  "bridge": "tel",
  "access": []
}

Created bridge type wallet has handle tel equal to schema so it will automatically catch all balance movements sent to tel:*. If there is already an alias wallet tel:* created for some specific phone number then this wallet with catch the balance movement, but all others will go to the bridge wallet.

Bridge of the wallet is set to tel which represents the phone alias directory service created above. It handles phone number resolution and onboarding of new phone numbers into system. This bridge will process any intent which targets the phone alias wallet (if wallet still is not created).

Creating a wallet representing a phone number

When user starts to use the service for the first time there is still no wallet representing his phone number in the ledger so we need to create it. In our clearinghouse model, the phone alias directory service will be called to prepare the credit entry for intent which targets a phone number which is still not onboarded in ledger. Service needs to create specific wallet for this phone number.

// Credit entry which targets the phone number of the user.
// This is just an example, in real implementation it will be received
// through the bridge request body in POST /v2/credits.
const entry = {
	handle: 'cre_baC0LUTVW9lzYA284',
	schema: 'credit',
	target: {
		handle: 'tel:15261234578',
	},
	symbol: {
		handle: 'usd'
	},
	amount: 100
  // intent in intentionally omitted here
}
 
const { wallet } = await sdk.wallet.read(entry.target.handle)
 
// We will check if phone number is onboarded and create
// a wallet only if it still doesn't exist
if (!wallet) {
	const { wallet } = await sdk.wallet
		.init()
		.data({
			handle: entry.target.handle,
			access: []
		})
		.hash()
		.sign([{ keyPair: serviceKeyPair }])
		.send()
}

We created a wallet with handle tel:15261234578 which represents hierarchical wallet name described here: About Wallets. Prefix tel represents the schema and we already have authoritative bridge type wallet with the same name which will handle all balance movements to addresses in format tel:*. Number 15261234578 represents the phone number which is a handle under tel schema. Created phone wallet now looks like:

{
  "handle": "tel:15261234578",
  "access": []
}

In this moment the balance will still not be forwarded to the target user bank account since the wallet doesn’t have any routes. So the service will send an SMS to user notifying him that he has a pending transfer on his phone number and that he needs to select the bank account to receive the balance.

// Sends SMS to phone number from target wallet handle by using
// some transport mechanism such as Infobip. This function can be implemented
// in any way.
// SMS text will notify him that he has a pending balance and give
// instructions how to select the bank account to recieve it. It will
// point him to the one of banking mobile apps where he will see a 
// pending transfer and option to select receiving bank account for
// this transfer.
await sendOnboardingSms(entry.target.handle)

Selecting bank account to receive the balance

When user gets onboarding SMS he will be instructed that he has pending balance and needs to open the bank application (or one of the bank applications) where he will be able to select the bank account where he wants to receive the balance. He will see a pending transfer and when he opens it he will be able to accept the payment. This will add new route to his phone number wallet which will point to his bank account he just have accessed through mobile app. The code which adds a route will be executed on the bank backend service which serves the banking mobile app for pbz bank.

import { LedgerSdk } from '@minka/ledger-sdk'
 
// Connect to ledger instance and provide a ledger public key
// in order to validate responses from it.
const sdk = new LedgerSdk({
	server: '<ledger url>',
	signer: {
		format: 'ed25519-raw',
		public: '<ledger public key>'
	}
})
 
// Populate this object with bank keys you have created previously
const bankKeyPair = {
  format: 'ed25519-raw',
  public: '<bank public key>',
  secret: '<bank private key>'
}
 
// Handle of user phone wallet, handle of the user bank account
// and the symbol. We want to route balance in given symbol
// from `phoneWalletHandle` to the `accountHandle` which represents the 
// virtual wallet under the `pbz` bank wallet.
// This is just an example, in real implementation this data will
// be sent by the mobile app to the bank backend service.
const phoneWalletHandle = 'tel:15261234578'
const accountHandle = 'account:343454345@pbz'
const symbol = 'usd'
 
const { wallet, response } = await sdk.wallet.read(phoneWalletHandle)
 
// Find or add new route for symbol
const routes = wallet.routes ?? []
let route = routes.find(route => route.filter.symbol === symbol)
if (!route) {
	route = {
		filter: {
			symbol
		},
		action: 'forward'
	}
	routes.push(route)
}
 
// Override default target for the given symbol
route.target = accountHandle
 
// Update the user phone number wallet with new routes
await sdk.wallet
	.from(response.data)
  .data({ routes })
  .hash()
  .sign([{ keyPair: bankKeyPair }])
  .send()

Phone number wallet tel:15261234578 now has a route for usd:

{
	"handle": "tel:15261234578",
	"routes": [{
		"filter": {
	    "symbol": "usd"
		},
	  "action": "forward",
	  "target": "account:343454345@pbz"
	}],
	"access": []
}

Which means that all usd balance received to this phone number will be forwarded to the user bank account 343454345 in bank pbz. More info about routing can be found here: About Wallets.

Our initial intent which was pending until this moment can now continue the execution since it can now be continued by the phone alias directory service adding a signature to intent with prepared status. More details about the processing of intent sent to alias and also continuation of pending intent by phone alias directory service can be found here: Making cross-ledger payments.

Setting the default bank account to receive currency

In any moment user can decide to change the bank account he uses as a default to receive the balance for some currency through the mobile app of any bank. Mobile app will also call bank backend service which will add additional route for new currency or modify existing one. Backend code is basically the same, we will show the example for zaba bank setting default account for eur currency:

// Handle of user phone wallet, handle of the user bank account
// and the symbol. We want to route balance in given symbol
// from `phoneWalletHandle` to the `accountHandle` which represents the 
// virtual wallet under the `zaba` bank wallet.
// This is just an example, in real implementation it will
// be sent by the mobile app to the bank backend service.
const phoneWalletHandle = 'tel:15261234578'
const accountHandle = 'account:41111339@zaba'
const symbol = 'eur'
 
const { wallet, response } = await sdk.wallet.read(phoneWalletHandle)
 
// Find or add new route for symbol
const routes = wallet.routes ?? []
let route = routes.find(route => route.filter.symbol === symbol)
if (!route) {
	route = {
		filter: {
			symbol
		},
		action: 'forward'
	}
	routes.push(route)
}
 
// Override default target for the given symbol
route.target = accountHandle
 
// Update the user phone number wallet with new routes
await sdk.wallet
	.from(response.data)
  .data({ routes })
  .hash()
  .sign([{ keyPair: bankKeyPair }])
  .send()

Phone number wallet tel:15261234578 now has additional route route for eur:

{
	"handle": "tel:15261234578",
	"routes": [{
		"filter": {
	    "symbol": "usd"
		},
	  "action": "forward",
	  "target": "account:343454345@pbz"
	}, {
		"filter": {
	    "symbol": "eur"
		},
	  "action": "forward",
	  "target": "account:41111339@zaba"
	}],
	"access": []
}

Now the user is ready to receive the balance by phone number in usd and eur into 2 different banks.

On this page