Building a Loan Participation Platform
An originator that sells participations in its loans has to keep two records straight at once: what each borrower still owes, and which share of each repayment belongs to which investor. The two move together — a single repayment reduces an obligation and is distributed across several owners — and if they are kept in separate systems they will eventually disagree.
On this page
In this guide, we model an originator that makes installment loans, sells participations in them to institutional investors, and services the repayments. One ledger holds the obligations, the ownership of them, and the cash.
OverviewLink to this section
The platform has two types of participant, borrowers and investors, each of which will have an account in the ledger. There will also be two accounts of its own: a processing account for accepting and splitting payments, and a lending account holding the loans it has not yet sold.
Each loan will be represented by its own asset, which will be tagged with details about the borrower. Token tags will be used to record due dates of individual installments. There will also be an asset for each currency that the platform supports.
This example assumes an originator whose loans have a fixed schedule: the amount and date of every installment are set when the loan is made. Participations in those loans are sold to investors, who then receive a proportional share of whatever is collected, less a 1% servicing fee. We will assume USD is the only currency supported.
SetupLink to this section
To set up our ledger, we will create several keys, assets, and accounts.
KeysLink to this section
The platform will have a single key, called operations.
new Key.Builder()
.setId("operations")
.create(ledger);ledger.keys.create({id: 'operations'})ledger.keys.create(id: 'operations')AccountsLink to this section
Each borrower and each investor needs an account in the ledger. Although these accounts would be created by the platform as participants are onboarded, for this example we'll assume we have one borrower (Alice) and two investors (Investor1 and Investor2) and create their accounts as part of the setup. We also need the platform's own processing and lending accounts.
We use account tags to differentiate between the types of accounts.
new Account.Builder()
.setId("alice")
.addKeyId("operations")
.addTag("type", "borrower")
.create(ledger);
new Account.Builder()
.setId("investor1")
.addKeyId("operations")
.addTag("type", "investor")
.create(ledger);
new Account.Builder()
.setId("investor2")
.addKeyId("operations")
.addTag("type", "investor")
.create(ledger);
new Account.Builder()
.setId("processing")
.addKeyId("operations")
.addTag("type", "company")
.create(ledger);
new Account.Builder()
.setId("lending")
.addKeyId("operations")
.addTag("type", "company")
.create(ledger);ledger.accounts.create({
id: 'alice',
keyIds: ['operations'],
tags: {type: 'borrower'}
})
ledger.accounts.create({
id: 'investor1',
keyIds: ['operations'],
tags: {type: 'investor'}
})
ledger.accounts.create({
id: 'investor2',
keyIds: ['operations'],
tags: {type: 'investor'}
})
ledger.accounts.create({
id: 'processing',
keyIds: ['operations'],
tags: {type: 'company'}
})
ledger.accounts.create({
id: 'lending',
keyIds: ['operations'],
tags: {type: 'company'}
})ledger.accounts.create(
id: 'alice',
key_ids: ['operations'],
tags: {type: 'borrower'}
)
ledger.accounts.create(
id: 'investor1',
key_ids: ['operations'],
tags: {type: 'investor'}
)
ledger.accounts.create(
id: 'investor2',
key_ids: ['operations'],
tags: {type: 'investor'}
)
ledger.accounts.create(
id: 'processing',
key_ids: ['operations'],
tags: {type: 'company'}
)
ledger.accounts.create(
id: 'lending',
key_ids: ['operations'],
tags: {type: 'company'}
)AssetsLink to this section
We will need a USD asset to represent cash. It is denominated in cents, meaning 100 tokens represent $1.00.
new Asset.Builder()
.setId("usd")
.addKeyId("operations")
.addTag("type", "currency")
.create(ledger);ledger.assets.create({
id: 'usd',
keyIds: ['operations'],
tags: {
type: 'currency'
}
})ledger.assets.create(
id: 'usd',
key_ids: ['operations'],
tags: {
type: 'currency'
}
)We will also need an asset for each loan originated, so that ownership of one loan can be told apart from ownership of another. Although these assets would be created as loans are written, for this example we'll assume we have one and create it as part of the setup. We use tags to record the static details of the loan — its id, its borrower, its type. In practice you would record more of the economic terms here; we leave them out for brevity.
For this example, assume the following terms of the loan:
- the originator lends Alice $300
- she will repay the loan in three installments of $110
- the installments will be payable on 1/1/2018, 2/1/2018, and 3/1/2018
new Asset.Builder()
.setId("loan1")
.addKeyId("operations")
.addTag("type", "loan")
.addTag("loan_id", "1")
.addTag("borrower_id", "alice")
.addTag("issue_date", "2017-12-01T00:00:00Z")
.create(ledger);ledger.assets.create({
id: 'loan1',
keyIds: ['operations'],
tags: {
type: 'loan',
loan_id: '1',
borrower_id: 'alice',
issue_date: '2017-12-01T00:00:00Z',
}
})ledger.assets.create(
id: 'loan1',
key_ids: ['operations'],
tags: {
type: 'loan',
loan_id: '1',
borrower_id: 'alice',
issue_date: '2017-12-01T00:00:00Z',
}
)Transaction TypesLink to this section
Now that we have created our assets and accounts, we can model the different types of transactions.
Issue LoanLink to this section
When the originator makes a loan, we need to track two aspects of the event: (1) distributing the loan amount to the borrower and (2) recording each of the installments for the loan (i.e., the repayment obligations). We will record #1 by issuing and then immediately retiring the loan amount to/from the borrower's account. Doing this will give us an easy way to look up loan amounts in the future (by querying for these actions). We will record #2 by issuing tokens of the loan's asset into the lending account. We will issue each installment separately, and include each installment's due date in token tags. We do all this in a single, atomic transaction, with five actions:
- Issue - the loan amount in
usdto the borrower's account - Retire - the loan amount in
usdfrom the borrower's account - Issue - the amount of the first installment, in
loan1, to the lending account, including the due date in token tags - Issue - the amount of the second installment, in
loan1, to the lending account, including the due date in token tags - Issue - the amount of the third installment, in
loan1, to the lending account, including the due date in token tags
new Transaction.Builder()
.addAction(new Transaction.Builder.Action.Issue()
.setAssetId("usd")
.setAmount(30000)
.setDestinationAccountId("alice")
.addActionTagsField("type", "distribute_loan_amount")
).addAction(new Transaction.Builder.Action.Retire()
.setAssetId("usd")
.setAmount(30000)
.setSourceAccountId("alice")
.addActionTagsField("type", "receive_loan_amount")
.addActionTagsField("system", "wire")
.addActionTagsField("wire_transaction_id", "11111")
).addAction(new Transaction.Builder.Action.Issue()
.setAssetId("loan1")
.setAmount(11000)
.setDestinationAccountId("lending")
.addActionTagsField("type", "create_installment")
.addTokenTagsField("installment", 1)
.addTokenTagsField("due_date", "2018-01-01T00:00:00Z")
).addAction(new Transaction.Builder.Action.Issue()
.setAssetId("loan1")
.setAmount(11000)
.setDestinationAccountId("lending")
.addActionTagsField("type", "create_installment")
.addTokenTagsField("installment", 2)
.addTokenTagsField("due_date", "2018-02-01T00:00:00Z")
).addAction(new Transaction.Builder.Action.Issue()
.setAssetId("loan1")
.setAmount(11000)
.setDestinationAccountId("lending")
.addActionTagsField("type", "create_installment")
.addTokenTagsField("installment", 3)
.addTokenTagsField("due_date", "2018-03-01T00:00:00Z")
).transact(ledger);ledger.transactions.transact(builder => {
builder.issue({
assetId: 'usd',
amount: 30000,
destinationAccountId: 'alice',
actionTags: {
type: 'distribute_loan_amount'
}
})
builder.retire({
assetId: 'usd',
amount: 30000,
sourceAccountId: 'alice',
actionTags: {
type: 'receive_loan_amount',
system: 'wire',
wire_transaction_id: '11111'
}
})
builder.issue({
assetId: 'loan1',
amount: 11000,
destinationAccountId: 'lending',
actionTags: {
type: 'create_installment'
},
tokenTags: {
installment: 1,
due_date: '2018-01-01T00:00:00Z'
}
})
builder.issue({
assetId: 'loan1',
amount: 11000,
destinationAccountId: 'lending',
actionTags: {
type: 'create_installment'
},
tokenTags: {
installment: 2,
due_date: '2018-02-01T00:00:00Z'
}
})
builder.issue({
assetId: 'loan1',
amount: 11000,
destinationAccountId: 'lending',
actionTags: {
type: 'create_installment'
},
tokenTags: {
installment: 3,
due_date: '2018-03-01T00:00:00Z'
}
})
})ledger.transactions.transact do |builder|
builder.issue(
asset_id: 'usd',
amount: 30000,
destination_account_id: 'alice',
action_tags: {
type: 'distribute_loan_amount'
}
)
builder.retire(
asset_id: 'usd',
amount: 30000,
source_account_id: 'alice',
action_tags: {
type: 'receive_loan_amount',
system: 'wire',
wire_transaction_id: '11111'
}
)
builder.issue(
asset_id: 'loan1',
amount: 11000,
destination_account_id: 'lending',
action_tags: {
type: 'create_installment'
},
token_tags: {
installment: 1,
due_date: '2018-01-01T00:00:00Z'
}
)
builder.issue(
asset_id: 'loan1',
amount: 11000,
destination_account_id: 'lending',
action_tags: {
type: 'create_installment'
},
token_tags: {
installment: 2,
due_date: '2018-02-01T00:00:00Z'
}
)
builder.issue(
asset_id: 'loan1',
amount: 11000,
destination_account_id: 'lending',
action_tags: {
type: 'create_installment'
},
token_tags: {
installment: 3,
due_date: '2018-03-01T00:00:00Z'
}
)
endInvestor DepositLink to this section
Investors deposit cash with the platform in order to buy participations. We model this as an issue action into the investor's account.
- Issue - USD equal to the deposit
Suppose Investor1 and Investor2 each deposit $1,000 onto the platform.
new Transaction.Builder()
.addAction(new Transaction.Builder.Action.Issue()
.setAssetId("usd")
.setAmount(100000)
.setDestinationAccountId("investor1")
.addActionTagsField("type", "deposit")
.addActionTagsField("system", "wire")
.addActionTagsField("wire_transaction_id", "22222")
).transact(ledger);
new Transaction.Builder()
.addAction(new Transaction.Builder.Action.Issue()
.setAssetId("usd")
.setAmount(100000)
.setDestinationAccountId("investor2")
.addActionTagsField("type", "deposit")
.addActionTagsField("system", "wire")
.addActionTagsField("wire_transaction_id", "33333")
).transact(ledger);await ledger.transactions.transact(builder => {
builder.issue({
assetId: 'usd',
amount: 100000,
destinationAccountId: 'investor1',
actionTags: {
type: 'deposit',
system: 'wire',
wire_transaction_id: '22222'
}
})
})
await ledger.transactions.transact(builder => {
builder.issue({
assetId: 'usd',
amount: 100000,
destinationAccountId: 'investor2',
actionTags: {
type: 'deposit',
system: 'wire',
wire_transaction_id: '33333'
}
})
})ledger.transactions.transact do |builder|
builder.issue(
asset_id: 'usd',
amount: 100000,
destination_account_id: 'investor1',
action_tags: {
type: 'deposit',
system: 'wire',
wire_transaction_id: '22222'
}
)
end
ledger.transactions.transact do |builder|
builder.issue(
asset_id: 'usd',
amount: 100000,
destination_account_id: 'investor2',
action_tags: {
type: 'deposit',
system: 'wire',
wire_transaction_id: '33333'
}
)
endSell ParticipationsLink to this section
When the originator sells a participation to an investor, the investor pays cash and the originator transfers all or part of one or more installments to them. Both legs are in the same transaction, so ownership never moves without the payment for it.
- Retire - USD equal to the purchase price from investor's account
- Transfer - for each loan installment being purchased, the portion of the loan tokens representing that installment from the lending account to the investor's account
For this example, we will assume Investor1 purchases one third of each installment of loan1 for $101, and Investor2 purchases two thirds of each installment for $202. We show these as two transactions rather than one. The structure should follow the deal: if the two sales are conditional on each other, they belong in one ledger transaction, because then neither can happen without the other.
new Transaction.Builder()
.addAction(new Transaction.Builder.Action.Retire()
.setAssetId("usd")
.setAmount(10100)
.setSourceAccountId("investor1")
.addActionTagsField("type", "loan_purchase_proceeds")
).addAction(new Transaction.Builder.Action.Transfer()
.setAssetId("loan1")
.setAmount(3666)
.setFilter("tags.due_date=$1")
.addFilterParameter("2018-01-01T00:00:00Z")
.setSourceAccountId("lending")
.setDestinationAccountId("investor1")
.addActionTagsField("type", "loan_purchase")
.addTokenTagsField("due_date", "2018-01-01T00:00:00Z")
.addTokenTagsField("installment", 1)
).addAction(new Transaction.Builder.Action.Transfer()
.setAssetId("loan1")
.setAmount(3666)
.setFilter("tags.due_date=$1")
.addFilterParameter("2018-02-01T00:00:00Z")
.setSourceAccountId("lending")
.setDestinationAccountId("investor1")
.addActionTagsField("type", "loan_purchase")
.addTokenTagsField("due_date", "2018-02-01T00:00:00Z")
.addTokenTagsField("installment", 2)
).addAction(new Transaction.Builder.Action.Transfer()
.setAssetId("loan1")
.setAmount(3666)
.setFilter("tags.due_date=$1")
.addFilterParameter("2018-03-01T00:00:00Z")
.setSourceAccountId("lending")
.setDestinationAccountId("investor1")
.addActionTagsField("type", "loan_purchase")
.addTokenTagsField("due_date", "2018-03-01T00:00:00Z")
.addTokenTagsField("installment", 3)
).transact(ledger);
new Transaction.Builder()
.addAction(new Transaction.Builder.Action.Retire()
.setAssetId("usd")
.setAmount(20200)
.setSourceAccountId("investor2")
.addActionTagsField("type", "loan_purchase_proceeds")
).addAction(new Transaction.Builder.Action.Transfer()
.setAssetId("loan1")
.setAmount(7334)
.setFilter("tags.due_date=$1")
.addFilterParameter("2018-01-01T00:00:00Z")
.setSourceAccountId("lending")
.setDestinationAccountId("investor2")
.addActionTagsField("type", "loan_purchase")
.addTokenTagsField("due_date", "2018-01-01T00:00:00Z")
.addTokenTagsField("installment", 1)
).addAction(new Transaction.Builder.Action.Transfer()
.setAssetId("loan1")
.setAmount(7334)
.setFilter("tags.due_date=$1")
.addFilterParameter("2018-02-01T00:00:00Z")
.setSourceAccountId("lending")
.setDestinationAccountId("investor2")
.addActionTagsField("type", "loan_purchase")
.addTokenTagsField("due_date", "2018-02-01T00:00:00Z")
.addTokenTagsField("installment", 2)
).addAction(new Transaction.Builder.Action.Transfer()
.setAssetId("loan1")
.setAmount(7334)
.setFilter("tags.due_date=$1")
.addFilterParameter("2018-03-01T00:00:00Z")
.setSourceAccountId("lending")
.setDestinationAccountId("investor2")
.addActionTagsField("type", "loan_purchase")
.addTokenTagsField("due_date", "2018-03-01T00:00:00Z")
.addTokenTagsField("installment", 3)
).transact(ledger);ledger.transactions.transact(builder => {
builder.retire({
assetId: 'usd',
amount: 10100,
sourceAccountId: 'investor1',
actionTags: {type: 'loan_purchase_proceeds'}
})
builder.transfer({
assetId: 'loan1',
amount: 3666,
filter: 'tags.due_date=$1',
filterParams: ['2018-01-01T00:00:00Z'],
sourceAccountId: 'lending',
destinationAccountId: 'investor1',
actionTags: {type: 'loan_purchase'},
tokenTags: {
due_date: '2018-01-01T00:00:00Z',
installment: 1
}
})
builder.transfer({
assetId: 'loan1',
amount: 3666,
filter: 'tags.due_date=$1',
filterParams: ['2018-02-01T00:00:00Z'],
sourceAccountId: 'lending',
destinationAccountId: 'investor1',
actionTags: {type: 'loan_purchase'},
tokenTags: {
due_date: '2018-02-01T00:00:00Z',
installment: 2
}
})
builder.transfer({
assetId: 'loan1',
amount: 3666,
filter: 'tags.due_date=$1',
filterParams: ['2018-03-01T00:00:00Z'],
sourceAccountId: 'lending',
destinationAccountId: 'investor1',
actionTags: {type: 'loan_purchase'},
tokenTags: {
due_date: '2018-03-01T00:00:00Z',
installment: 3
}
})
})
ledger.transactions.transact(builder => {
builder.retire({
assetId: 'usd',
amount: 20200,
sourceAccountId: 'investor2',
actionTags: {type: 'loan_purchase_proceeds'}
})
builder.transfer({
assetId: 'loan1',
amount: 7334,
filter: 'tags.due_date=$1',
filterParams: ['2018-01-01T00:00:00Z'],
sourceAccountId: 'lending',
destinationAccountId: 'investor2',
actionTags: {type: 'loan_purchase'},
tokenTags: {
due_date: '2018-01-01T00:00:00Z',
installment: 1
}
})
builder.transfer({
assetId: 'loan1',
amount: 7334,
filter: 'tags.due_date=$1',
filterParams: ['2018-02-01T00:00:00Z'],
sourceAccountId: 'lending',
destinationAccountId: 'investor2',
actionTags: {type: 'loan_purchase'},
tokenTags: {
due_date: '2018-02-01T00:00:00Z',
installment: 2
}
})
builder.transfer({
assetId: 'loan1',
amount: 7334,
filter: 'tags.due_date=$1',
filterParams: ['2018-03-01T00:00:00Z'],
sourceAccountId: 'lending',
destinationAccountId: 'investor2',
actionTags: {type: 'loan_purchase'},
tokenTags: {
due_date: '2018-03-01T00:00:00Z',
installment: 3
}
})
})ledger.transactions.transact do |builder|
builder.retire(
asset_id: 'usd',
amount: 10100,
source_account_id: 'investor1',
action_tags: {type: 'loan_purchase_proceeds'}
)
builder.transfer(
asset_id: 'loan1',
amount: 3666,
filter: 'tags.due_date=$1',
filter_params: ['2018-01-01T00:00:00Z'],
source_account_id: 'lending',
destination_account_id: 'investor1',
action_tags: {type: 'loan_purchase'},
token_tags: {
due_date: '2018-01-01T00:00:00Z',
installment: 1
}
)
builder.transfer(
asset_id: 'loan1',
amount: 3666,
filter: 'tags.due_date=$1',
filter_params: ['2018-02-01T00:00:00Z'],
source_account_id: 'lending',
destination_account_id: 'investor1',
action_tags: {type: 'loan_purchase'},
token_tags: {
due_date: '2018-02-01T00:00:00Z',
installment: 2
}
)
builder.transfer(
asset_id: 'loan1',
amount: 3666,
filter: 'tags.due_date=$1',
filter_params: ['2018-03-01T00:00:00Z'],
source_account_id: 'lending',
destination_account_id: 'investor1',
action_tags: {type: 'loan_purchase'},
token_tags: {
due_date: '2018-03-01T00:00:00Z',
installment: 3
}
)
end
ledger.transactions.transact do |builder|
builder.retire(
asset_id: 'usd',
amount: 20200,
source_account_id: 'investor2',
action_tags: {type: 'loan_purchase_proceeds'}
)
builder.transfer(
asset_id: 'loan1',
amount: 7334,
filter: 'tags.due_date=$1',
filter_params: ['2018-01-01T00:00:00Z'],
source_account_id: 'lending',
destination_account_id: 'investor2',
action_tags: {type: 'loan_purchase'},
token_tags: {
due_date: '2018-01-01T00:00:00Z',
installment: 1
}
)
builder.transfer(
asset_id: 'loan1',
amount: 7334,
filter: 'tags.due_date=$1',
filter_params: ['2018-02-01T00:00:00Z'],
source_account_id: 'lending',
destination_account_id: 'investor2',
action_tags: {type: 'loan_purchase'},
token_tags: {
due_date: '2018-02-01T00:00:00Z',
installment: 2
}
)
builder.transfer(
asset_id: 'loan1',
amount: 7334,
filter: 'tags.due_date=$1',
filter_params: ['2018-03-01T00:00:00Z'],
source_account_id: 'lending',
destination_account_id: 'investor2',
action_tags: {type: 'loan_purchase'},
token_tags: {
due_date: '2018-03-01T00:00:00Z',
installment: 3
}
)
endNote that when we transfer the loan1 tokens to the investor accounts, we have to specify the token tags to ensure that the due date information is tagged to the tokens that are transferred.
Borrower PaymentLink to this section
When a borrower repays, the platform takes its 1% servicing fee and distributes the rest to the investors in proportion to what each owns. The repayment lands in the processing account first, and is split from there.
To determine the amounts owed to each investor, we must first query the ledger to retrieve the total amount of the loan each investor owns. With that information, we can calculate each investor's pro rata share.
Once the pro rata shares are calculated, the eventual transaction is:
- Issue - USD equal to the payment to the borrower's account
- Transfer - USD equal to the payment from the borrower's account to the processing account
- Retire - USD equal to the company fee from the processing account
- Retire - For each owner of a portion of the loan, retire the loan tokens from their account in an amount equal to their pro rata share of the total payment (i.e., pre-company fee)
- Transfer - For each investor who owns a portion of the loan (if any), transfer USD to their account from the processing account in an amount equal to their pro rata share of the net payment (i.e., post-company fee)
If the platform still owns any portion of the loan, we would also include two more actions:
- Retire - loan tokens from the company lending account in an amount equal to the share still owned by the platform
- Retire - USD from the processing account in an amount equal to the payment attributable to the share still owned by the platform
The below shows Alice paying the first installment on loan1.
Token.SumBuilder builder = new Token.SumBuilder()
.setFilter("assetId=$1 AND tags.installment=$2")
.addFilterParameter("loan1")
.addFilterParameter(1)
.addGroupByField("accountId");
int total = 0;
for (TokenSum sum : builder.getIterable(ledger)) {
total += sum.amount;
}
Transaction.Builder tx = new Transaction.Builder()
.addAction(new Transaction.Builder.Action.Issue()
.setAssetId("usd")
.setAmount(11000)
.setDestinationAccountId("alice")
.addActionTagsField("type", "loan_payment_funds")
).addAction(new Transaction.Builder.Action.Transfer()
.setAssetId("usd")
.setAmount(11000)
.setSourceAccountId("alice")
.setDestinationAccountId("processing")
.addActionTagsField("type", "loan_payment")
);
int fee_amt = 11000; // Start at payment amount; deduct amounts paid to investors
for (TokenSum sum : builder.getIterable(ledger)) {
double percentage = (double) sum.amount / total;
int pre_fee_share = (int) Math.floor(percentage * 11000);
int post_fee_share = (int) Math.floor(percentage * 0.99 * 11000);
fee_amt -= post_fee_share;
tx.addAction(new Transaction.Builder.Action.Retire()
.setAssetId("loan1")
.setAmount(pre_fee_share)
.setFilter("tags.installment=$1")
.addFilterParameter(1)
.setSourceAccountId(sum.accountId)
.addActionTagsField("type", "loan_repayment")
).addAction(new Transaction.Builder.Action.Transfer()
.setAssetId("usd")
.setAmount(post_fee_share)
.setSourceAccountId("processing")
.setDestinationAccountId(sum.accountId)
.addActionTagsField("type", "loan_payment_owner_share")
);
}
tx.addAction(new Transaction.Builder.Action.Retire()
.setAssetId("usd")
.setAmount(fee_amt)
.setSourceAccountId("processing")
.addActionTagsField("type", "company_fee")
).transact(ledger);var page1 = ledger.tokens.sum({
filter: 'assetId=$1 AND tags.installment=$2',
filterParams: ['loan1', 1],
groupBy: ['accountId']
}).page()
total = 0
page1.items.forEach(sum => {
total += sum.amount
})
ledger.transactions.transact(builder => {
builder.issue({
assetId: 'usd',
amount: 11000,
destinationAccountId: 'alice',
actionTags: {
type: 'loan_payment_funds'
}
})
builder.transfer({
assetId: 'usd',
amount: 11000,
sourceAccountId: 'alice',
destinationAccountId: 'processing',
actionTags: {
type: 'loan_payment'
}
})
fee_amt = 11000 // Start at full amount; will deduct amounts paid to investors
page1.items.forEach(sum => {
percentage = sum.amount / total
pre_fee_share = Math.floor(percentage * 11000)
post_fee_share = Math.floor(percentage * 0.99 * 11000)
fee_amt -= post_fee_share
builder.retire({
assetId: 'loan1',
amount: pre_fee_share,
filter: 'tags.installment=$1',
filterParams: [1],
sourceAccountId: sum.accountId,
actionTags: {
type: 'loan_repayment'
}
})
builder.transfer({
assetId: 'usd',
amount: post_fee_share,
sourceAccountId: 'processing',
destinationAccountId: sum.accountId,
actionTags: {
type: 'loan_payment_owner_share'
}
})
})
builder.retire({
assetId: 'usd',
amount: fee_amt,
sourceAccountId: 'processing',
actionTags: {
type: 'company_fee'
}
})
})page1 = ledger.tokens.sum(
filter: 'asset_id=$1 AND tags.installment=$2',
filter_params: ['loan1', 1],
group_by: ['account_id']
).page()
owners = []
total = 0
page1.each do |sum|
owners << [sum.account_id, sum.amount]
total += sum.amount
end
ledger.transactions.transact do |builder|
builder.issue(
asset_id: 'usd',
amount: 11000,
destination_account_id: 'alice',
action_tags: {
type: 'loan_payment_funds'
}
)
builder.transfer(
asset_id: 'usd',
amount: 11000,
source_account_id: 'alice',
destination_account_id: 'processing',
action_tags: {
type: 'loan_payment'
}
)
fee_amt = 11000
owners.each do |owner|
percentage = owner[1] / total.to_f
pre_fee_share = (percentage * 11000).floor
post_fee_share = (percentage * 0.99 * 11000).floor
fee_amt -= post_fee_share
builder.retire(
asset_id: 'loan1',
amount: pre_fee_share,
filter: 'tags.installment=$1',
filter_params: [1],
source_account_id: owner[0],
action_tags: {
type: 'loan_repayment'
}
)
builder.transfer(
asset_id: 'usd',
amount: post_fee_share,
source_account_id: 'processing',
destination_account_id: owner[0],
action_tags: {
type: 'loan_payment_owner_share'
}
)
end
builder.retire(
asset_id: 'usd',
amount: fee_amt,
source_account_id: 'processing',
action_tags: {
type: 'company_fee'
}
)
endNote that the way the above code is written, each investor's share is rounded down to the nearest cent and the company keeps any remaining cents due to rounding (as a result, in the above, the company will take a fee of $1.11, even though 1% of the payment is $1.10).
Investor WithdrawalLink to this section
When an investor withdraws cash from their account on the platform, we retire an amount of USD equal to the withdrawal amount from their account.
Assume that Investor1 withdraws $50 from their account.
new Transaction.Builder()
.addAction(new Transaction.Builder.Action.Retire()
.setAssetId("usd")
.setAmount(5000)
.setSourceAccountId("investor1")
.addActionTagsField("type", "investor_withdrawal")
).transact(ledger);ledger.transactions.transact(builder => {
builder.retire({
assetId: 'usd',
amount: 5000,
sourceAccountId: 'investor1',
actionTags: {
type: 'investor_withdrawal'
}
})
})ledger.transactions.transact do |builder|
builder.retire(
asset_id: 'usd',
amount: 5000,
source_account_id: 'investor1',
action_tags: {
type: 'investor_withdrawal'
}
)
endQueriesLink to this section
Now that we have created several transactions, we can query the ledger in various ways.
Investor PortfolioLink to this section
If we want to know all of the loans an investor currenly owns, we perform a sum tokens query, filtering to the investor's account and the loan type in asset tags, and grouping by asset id.
TokenSum.ItemIterable sums = new Token.SumBuilder()
.setFilter("accountId=$1 AND assetTags.type=$2")
.addFilterParameter("investor1")
.addFilterParameter("loan")
.addGroupByField("assetId")
.getIterable(ledger);
for (TokenSum sum : sums) {
System.out.println("amount: " + sum.amount );
System.out.println("loan: " + sum.assetId);
System.out.println("");
}page1 = ledger.tokens.sum({
filter: 'accountId=$1 AND assetTags.type=$2',
filterParams: ['investor1', 'loan'],
groupBy: ['assetId']
}).page()
page1.items.forEach(sum => {
console.log('amount: ' + sum.amount)
console.log('loan: ' + sum.assetId)
console.log('')
})ledger.tokens.sum(
filter: 'account_id=$1 AND asset_tags.type=$2',
filter_params: ['investor1', 'loan'],
group_by: ['asset_id']
).each do |sum|
puts 'amount: ' + sum.amount.to_s
puts 'loan: ' + sum.asset_id.to_s
puts ''
endwhich will output:
amount: x
loan: y
amount: w
loan: uBorrower LoansLink to this section
If we want to know all the amounts that a particular borrower has outstanding, we perform a sum tokens query and group the results by due date.
sums = new Token.SumBuilder()
.setFilter("assetTags.borrower_id=$1")
.addFilterParameter("alice")
.addGroupByField("assetId")
.addGroupByField("tags.due_date")
.getIterable(ledger);
for (TokenSum sum : sums) {
System.out.println("amount: " + sum.amount );
System.out.println("loan: " + sum.assetId);
System.out.println("due date: " + sum.tags.get("due_date"));
System.out.println("");
}page1 = ledger.tokens.sum({
filter: 'assetTags.borrower_id=$1',
filterParams: ['alice'],
groupBy: ['assetId', 'tags.due_date']
}).page()
page1.items.forEach(sum => {
console.log('amount: ' + sum.amount)
console.log('loan: ' + sum.assetId)
console.log('due date: ' + sum.tags['due_date'])
console.log('')
})ledger.tokens.sum(
filter: 'asset_tags.borrower_id=$1',
filter_params: ['alice'],
group_by: ['asset_id', 'tags.due_date']
).each do |sum|
puts 'amount: ' + sum.amount.to_s
puts 'loan: ' + sum.asset_id
puts 'due date: ' + sum.tags['due_date']
puts ''
endwhich will output:
amount: ...
loan: ...
due date: ...
(etc.)Investor Cash BalanceLink to this section
If we want to know the amount of cash in each investor account (i.e., the amounts they could withdraw), we perform a sum tokens query, filtering to accounts with type 'investor' and assets with type 'currency'.
sums = new Token.SumBuilder()
.setFilter("accountTags.type=$1 AND assetTags.type=$2")
.addFilterParameter("investor")
.addFilterParameter("currency")
.addGroupByField("accountId")
.addGroupByField("assetId")
.getIterable(ledger);
for (TokenSum sum : sums) {
System.out.println("account: " + sum.accountId );
System.out.println("amount: " + sum.amount );
System.out.println("currency: " + sum.assetId );
System.out.println("");
}page1 = ledger.tokens.sum({
filter: 'accountTags.type=$1 AND assetTags.type=$2',
filterParams: ['investor', 'currency'],
groupBy: ['accountId', 'assetId']
}).page()
page1.items.forEach(sum => {
console.log('account: ' + sum.accountId)
console.log('amount: ' + sum.amount)
console.log('currency: ' + sum.assetId)
console.log('')
})ledger.tokens.sum(
filter: 'account_tags.type=$1 AND asset_tags.type=$2',
filter_params: ['investor', 'currency'],
group_by: ['account_id', 'asset_id']
).each do |sum|
puts 'account: ' + sum.account_id
puts 'amount: ' + sum.amount.to_s
puts 'currency: ' + sum.asset_id
puts ''
endwhich will output:
amount: x
amount: yTotal FeesLink to this section
If we want to know the total amount of fees collected, we perform a sum actions query, filtering to actions with a type of company_fee. We can also supply a time window, if we want.
For example, the below calculates total fees collected in the February 2018.
ActionSum.ItemIterable actionSums = new Action.SumBuilder()
.setFilter("tags.type=$1 AND timestamp >= $2 AND timestamp < $3")
.addFilterParameter("company_fee")
.addFilterParameter("2018-02-01T00:00:00Z")
.addFilterParameter("2018-03-01T00:00:00Z")
.getIterable(ledger);
for (ActionSum actionSum : actionSums) {
System.out.println("total fees in February: " + actionSum.amount);
}page1 = ledger.actions.sum({
filter: 'tags.type=$1 AND timestamp >= $2 AND timestamp < $3',
filterParams: ['company_fee', '2018-02-01T00:00:00Z', '2018-03-01T00:00:00Z']
}).page()
page1.items.forEach(sum => {
console.log('total fees in February: ' + sum.amount)
})ledger.actions.sum(
filter: 'tags.type=$1 AND timestamp >= $2 AND timestamp < $3',
filter_params: ['company_fee', '2018-02-01T00:00:00Z', '2018-03-01T00:00:00Z']
).each do |sum|
puts 'total fees in February: ' + sum.amount.to_s
endNote that to define the time window, instead of using the Onyx Mesh-generated timestamp (the timestamp field on Action objects), we could have included custom fields when building the actions in tags. When using these fields in the filter, we would simply add :time to indicate it is a timestamp field. See Filters for more information on custom time fields.