Skip to main content

Lender

In this guide, you’ll learn how to build a simple defi dApp in under an hour using the Código Platform. We will be building a dApp that will allow informal lenders to lend money.

Código is an AI-powered code Generation Platform for blockchain developers and web3 teams that saves development time and increases the code's security.

To get started, Create an account on Código Platform following the steps mentioned here

info

The first time you create an account, provisioning your environment can take 3 to 5 minutes.

Create & implement the CIDL for a simple DeFi

After creating your account and successfully accessing the environment we can start writing the CIDL. Create a new file named main.yaml from your file explorer.

The CIDL (Código Interface Description Language) is the input to Código’s Generator; in it, we define our Solana program types and methods.

Código Studio SideBar

Implementing the CIDL metadata

The first part we need to implement of the CIDL is the CIDL’s spec version and program information. With this metadata information, Código’s Generator and other people will know which CIDL specification version is used, the contract name, its license, and more. Copy and paste the following code to your main.yaml file:

cidl: "0.9"
info:
name: informal_lender
title: Informal Lender
summary: |-
With this contract an informal lender can lend money through the blockchain.
For simplicity, the money will be SOL currency.
version: 0.1.0
license:
name: MIT
identifier: MIT

After copying and pasting successfully, your main.yaml file should look like this:

Código Studio CIDL-Lender1

Implementing the types

The CIDL structure is really simple, you basically have types and methods (will see this in the next point). In the types, we define the models of the data we want to store in the blockchain. For our simple defi, we defined 2 types, the first named Broker and the second Loan.

The Broker will store the capital amount that is available to lend, the amount it has lent, the revenue, and the fee percentage a client needs to pay as interest upon making a payment. With all this data and a small intervention by the informal lender, the program will be able to determine if it can lend a loan.

The Loan type will store the data for a given loan. Like the amount lent and paid, whether the loan is approved or not, and the KYC (Know Your Customer) information. These 2 last points are really important, based on the country's law before lending money the lender needs to screen the person to whom it will lend money. This is the small step we mentioned previously, where the informal lender needs to intervene in the approval of a loan.

Finally, copy and paste the following code into your main.yaml file

solana:
seeds:
Broker:
items:
- name: broker
Loan:
items:
- name: loan
- name: client
type: sol:pubkey
- name: index
type: u32
types:
Broker:
summary: |-
Broker is an account that can only exist once per contract.
It stores all the require information to lend money.
fields:
- name: delegate
type: sol:pubkey
description: Only this delegate can modify the Broker account
- name: capital
type: u128
description: The amount of money staked
- name: lended
type: u128
description: The amount of money lended
- name: revenue
type: u128
description: The amount of money earn
- name: fee
type: u8
description: The percentage a client needs to pay when paying a loan
Loan:
summary: |-
Loan is an account that will exist as many times is required
per client it stores a request to a Loan and if the loan is approved
it stores the amount payed.
fields:
- name: client
type: sol:pubkey
description: The client that owns this loan
- name: amount
type: u64
description: The amount of money the client borrow
- name: payed
type: u64
description: The amount of money this client has payed
- name: kyc_url
type: string
description: URL to the client's KYC document require by law
attributes: [ cap=96 ]
- name: fee
type: u8
description: The fee this client needs to pay for this loan when making payments
- name: approved
type: bool
description: Flag state if the loan was approved by the broker or not

After copying and pasting successfully, your main.yaml file should look like this:

Código Studio CIDL-Lender2

Implementing the methods

The final step in implementing the CIDL is to implement the methods. Through the methods, we defined the behavior that will determine how the models will change data over time or how it will be created. For this workshop, we defined 5 methods.

  1. Create broker method will be creating the broker model and saving it to the blockchain. This method can only be called once per program.
  2. Add capital to the broker will allow the informal lender to add additional capital at any point in time.
  3. Request loan allows the informal lender’s client to request a loan
  4. Approve loan allows the informal lender to approve a loan, when the loan is approved the funds will be transferred to the client’s wallet
  5. Pay loan will be used by the client to pay the lent money, every time a client makes a payment it will pay interest, and this interest will be the informal lender revenue.

Copy and paste the following methods into your main.yaml file

methods:
- name: create_broker
summary: |-
After deploying the contract this must be the first instruction
to call. It will configure the broker account.
signers:
- name: fee_payer
type: sol:account
attributes: [ sol:writable ]
- input: delegate
inputs:
- name: delegate
type: sol:account
description: This will be the account that has permission to update the broker and approved request.
attributes: [ sol:writable ]
- name: broker
type: sol:account<Broker, seeds.Broker>
attributes: [ sol:init ]
- name: amount
type: u64
description: The amount to be added to the capital. It will be debited from the delegate account
- name: fee
type: u8
- name: add_capital_to_broker
summary: Through this instruction any one can add capital to the broker
signers:
- name: fee_payer
type: sol:account
attributes: [ sol:writable ]
- input: delegate
inputs:
- name: delegate
type: sol:account
attributes: [ sol:writable ]
- name: broker
type: sol:account<Broker, seeds.Broker>
attributes: [ sol:writable ]
- name: amount
type: u64
description: The amount to be added to the capital. It will be debited from the delegate account
- name: request_loan
summary: This instruction is used by a client to request a loan
signers:
- name: fee_payer
type: sol:account
attributes: [ sol:writable ]
- input: client
inputs:
- name: client
type: sol:account
- name: loan
type: sol:account<Loan, seeds.Loan(client=client)>
attributes: [ sol:init ]
- name: broker
type: sol:account<Broker, seeds.Broker>
- name: amount
type: u64
description: The request amount to borrow
- name: kyc_url
type: string
- name: approve_loan
summary: |-
Through this instruction the delegate can approve a loan.
Upon approval, the funds will be transfer from the broker
account to the client's account.
signers:
- name: fee_payer
type: sol:account
attributes: [ sol:writable ]
- input: delegate
inputs:
- name: delegate
type: sol:account
- name: loan
type: sol:account<Loan, seeds.Loan(client=client)>
attributes: [ sol:writable ]
- name: broker
type: sol:account<Broker, seeds.Broker>
attributes: [ sol:writable ]
- name: client
type: sol:account
attributes: [ sol:writable ]
- name: pay_loan
summary: |-
A client can pay a loan through this instruction. When paying
the contract will calculate the interest based on the loan approved
fee. Additioanlly, it will transfer money from the client's account
to the broker account
signers:
- name: fee_payer
type: sol:account
attributes: [ sol:writable ]
- input: client
inputs:
- name: client
type: sol:account
attributes: [ sol:writable ]
- name: loan
type: sol:account<Loan, seeds.Loan(client=client)>
attributes: [ sol:writable ]
- name: broker
type: sol:account<Broker, seeds.Broker>
attributes: [ sol:writable ]
- name: amount
type: u64
description: The amount to pay to the loan

After copying and pasting successfully, your main.yaml file should look like this in the methods section (with them collapsed):

Código Studio CIDL-Lender3

The final CIDL must look like this:

cidl: "0.9"
info:
name: informal_lender
title: Informal Lender
summary: |-
With this contract an informal lender can lend money through the blockchain.
For simplicity, the money will be SOL currency.
version: 0.1.0
license:
name: MIT
identifier: MIT
solana:
seeds:
Broker:
items:
- name: broker
Loan:
items:
- name: loan
- name: client
type: sol:pubkey
- name: index
type: u32
types:
Broker:
summary: |-
Broker is an account that can only exist once per contract.
It stores all the require information to lend money.
fields:
- name: delegate
type: sol:pubkey
description: Only this delegate can modify the Broker account
- name: capital
type: u128
description: The amount of money staked
- name: lended
type: u128
description: The amount of money lended
- name: revenue
type: u128
description: The amount of money earn
- name: fee
type: u8
description: The percentage a client needs to pay when paying a loan
Loan:
summary: |-
Loan is an account that will exist as many times is required
per client it stores a request to a Loan and if the loan is approved
it stores the amount payed.
fields:
- name: client
type: sol:pubkey
description: The client that owns this loan
- name: amount
type: u64
description: The amount of money the client borrow
- name: payed
type: u64
description: The amount of money this client has payed
- name: kyc_url
type: string
description: URL to the client's KYC document require by law
attributes: [ cap=96 ]
- name: fee
type: u8
description: The fee this client needs to pay for this loan when making payments
- name: approved
type: bool
description: Flag state if the loan was approved by the broker or not
methods:
- name: create_broker
summary: |-
After deploying the contract this must be the first instruction
to call. It will configure the broker account.
signers:
- name: fee_payer
type: sol:account
attributes: [ sol:writable ]
- input: delegate
inputs:
- name: delegate
type: sol:account
description: This will be the account that has permission to update the broker and approved request.
attributes: [ sol:writable ]
- name: broker
type: sol:account<Broker, seeds.Broker>
attributes: [ sol:init ]
- name: amount
type: u64
description: The amount to be added to the capital. It will be debited from the delegate account
- name: fee
type: u8
- name: add_capital_to_broker
summary: Through this instruction any one can add capital to the broker
signers:
- name: fee_payer
type: sol:account
attributes: [ sol:writable ]
- input: delegate
inputs:
- name: delegate
type: sol:account
attributes: [ sol:writable ]
- name: broker
type: sol:account<Broker, seeds.Broker>
attributes: [ sol:writable ]
- name: amount
type: u64
description: The amount to be added to the capital. It will be debited from the delegate account
- name: request_loan
summary: This instruction is used by a client to request a loan
signers:
- name: fee_payer
type: sol:account
attributes: [ sol:writable ]
- input: client
inputs:
- name: client
type: sol:account
- name: loan
type: sol:account<Loan, seeds.Loan(client=client)>
attributes: [ sol:init ]
- name: broker
type: sol:account<Broker, seeds.Broker>
- name: amount
type: u64
description: The request amount to borrow
- name: kyc_url
type: string
- name: approve_loan
summary: |-
Through this instruction the delegate can approve a loan.
Upon approval, the funds will be transfer from the broker
account to the client's account.
signers:
- name: fee_payer
type: sol:account
attributes: [ sol:writable ]
- input: delegate
inputs:
- name: delegate
type: sol:account
- name: loan
type: sol:account<Loan, seeds.Loan(client=client)>
attributes: [ sol:writable ]
- name: broker
type: sol:account<Broker, seeds.Broker>
attributes: [ sol:writable ]
- name: client
type: sol:account
attributes: [ sol:writable ]
- name: pay_loan
summary: |-
A client can pay a loan through this instruction. When paying
the contract will calculate the interest based on the loan approved
fee. Additioanlly, it will transfer money from the client's account
to the broker account
signers:
- name: fee_payer
type: sol:account
attributes: [ sol:writable ]
- input: client
inputs:
- name: client
type: sol:account
attributes: [ sol:writable ]
- name: loan
type: sol:account<Loan, seeds.Loan(client=client)>
attributes: [ sol:writable ]
- name: broker
type: sol:account<Broker, seeds.Broker>
attributes: [ sol:writable ]
- name: amount
type: u64
description: The amount to pay to the loan

Generate code

After completing the implementation of your CIDL, the next step is to generate code. Open a new terminal by going to Terminal > New Terminal

Código Studio Terminal &gt; New Terminal

Before being able to generate code, you need to authenticate with your GitHub account into the Código CLI, you can achieve this by typing the command codigo login. You can follow a step-by-step guide to learn how to log in to the Código CLI here. generated_files_sidebar After login to Código CLI, from the terminal type the command codigo solana generate main.yaml with this command, Código CLI will generate the Solana program and client library

Código Solana Generate

After the generation has finished, in the file explorer you will see 2 new directories, one name program, and the other program_client. The program_client will be added as a dependency to our web app or Node.js API to integrate it with our product, for the workshop we will do it a little bit simpler. In the program directory, we will be implementing the business logic.

Sidebar with generate files

Implement the business logic

After generating the code, if you expand the program directory you will see a directory named src, in the src directory you will find a file that corresponds to each method defined in the CIDL, these files contain the stub function for each method where you will be implementing the business logic. In the generated directory you can find all the files and code Código generated, is recommended to not modify these files.

Generated files Business Logic

Implement create_broker

Replace the comment // Implement your business logic here… with the following code:

// Set initial broker state
broker.data.delegate = *delegate.key;
broker.data.capital = amount as u128;
broker.data.lended = 0;
broker.data.revenue = 0;
broker.data.fee = fee;

// Transfer from delegate account to broker's account
**delegate.try_borrow_mut_lamports()? -= amount;
**broker.info.try_borrow_mut_lamports()? += amount;

The business logic for create broker is straightforward, we are setting the default values to our Broker model and we are funding the broker with some capital so the program will be able to lend the given capital. We also stored the wallet of the user who’s creating the broker, this will be used later for security checks.

Implement add_capital_to_broker

Replace the comment // Implement your business logic here… with the following code:

// Update broker's capital amount
broker.data.capital += amount as u128;

// Transfer from delegate account to broker's account
**delegate.try_borrow_mut_lamports()? -= amount;
**broker.info.try_borrow_mut_lamports()? += amount;

The business logic for add capital to broker is similar to the previous method, here we are adding more funds to the broker’s capital.

Implement request_loan

Replace the comment // Implement your business logic here… with the following code:

// Check if the broker has enough capital to lend
if broker.data.capital.saturating_sub(amount as u128) <= 0 {
return Err(InformalLenderError::InvalidInstruction.into());
}

loan.data.client = *client.key;
loan.data.amount = amount;
loan.data.payed = 0;
loan.data.kyc_url = kyc_url;
loan.data.fee = broker.data.fee;
loan.data.approved = false;

We start to see a pattern here, where implementing business logic is straightforward. The business logic for request_loan is also simple, first, we check if the broker has capital available to lend, because the capital is stored in an unsigned number we need to subtract using a special rust function named saturatin_sub, then we compare the result of this function and check if it is less or equal to 0, if true, it means that the broker doesn’t have enough capital to lend the requested amount, thus, the program returns an error. If the broker has the capital to lend, then we proceed with creating the loan model.

For this implementation you will need to add in line 10 the following import:

use crate::generated::errors::InformalLenderError;

Implement approve_loan

Replace the comment // Implement your business logic here… with the following code:

// Check if the broker has enough capital to lend
if broker.data.capital.saturating_sub(loan.data.amount as u128) <= 0 {
return Err(InformalLenderError::InvalidInstruction.into());
}

// Check if the delegate is the authorized user by the broker
if broker.data.delegate != *delegate.key {
return Err(InformalLenderError::InvalidInstruction.into());
}

// Check if the client to which we will transfer the funds is the client that requested the loan
if *client.key != loan.data.client {
return Err(InformalLenderError::InvalidInstruction.into());
}

// Update the broker state
broker.data.capital -= loan.data.amount as u128;
broker.data.lended += loan.data.amount as u128;
loan.data.approved = true;

// Transfer from broker account to client's account
**broker.info.try_borrow_mut_lamports()? -= loan.data.amount;
**client.try_borrow_mut_lamports()? += loan.data.amount;

Similar to request loan we check if the broker has enough capital to lend. Afterward, we check if the user authorizing the loan is the same user that created the broker, this check is really important because without it, any user will be able to approve loans, and we don’t want that. We also check the wallet to which we will be transferring the funds, we need to verify that we are transferring the funds to the correct client. Finally, we transfer the funds.

For this implementation you will need to add in line 10 the following import:

use crate::generated::errors::InformalLenderError;

Implement pay_loan

Replace the comment // Implement your business logic here… with the following code:

// Check if the client is not over-paying
if loan.data.payed + amount > loan.data.amount {
return Err(InformalLenderError::InvalidInstruction.into());
}

// Calculate how much interest the client need to pay
let interest = loan.data.amount / loan.data.fee as u64;

// Update the broker state
broker.data.revenue += interest as u128;
broker.data.capital += amount as u128;
broker.data.lended -= amount as u128;

// Update loan's payed amount
loan.data.payed += amount;

// Transfer from client's account to broker account
let total = amount + interest;
**client.try_borrow_mut_lamports()? -= total;
**broker.info.try_borrow_mut_lamports()? += total;

The final business logic we need to implement is for pay_loan, here we check if the client is not overpaying a loan then we proceed to calculate the interest the client needs to pay and finally we debit the client’s wallet and credit the brokers' wallet. For the sake of simplicity, the interest calculation is straightforward, but for a production environment, we need to improve the calculation so the informal lender doesn’t lose cents.

For this implementation, you will need to add in line 10 the following import:

use crate::generated::errors::InformalLenderError;

Build & deploy the program

Congratulations!! You have successfully created and implemented a Solana program the next step is to build and deploy the contract. But before doing that, open your Cargo.toml file and add the following dependency:

bumpalo = "=3.14.0"

After adding the previous dependency, your Cargo.toml must look like this:

Cargo.toml

The reason for this is that the latest bumpalo version is not compatible with the installed Solana toolchain. This fix has already been applied to our latest Código CLI version. Now, to where we were:

Open a new terminal by going to Terminal -> New Terminal. Navigate to the program directory by typing the command cd program, and inside the program directory, type the following command cargo build-sbf

Cargo Build SBF

This command will take a few minutes to complete. When the previous command completes, open another terminal by going to Terminal -> New Terminal. In the new terminal, type the command:

solana-test-validator

you will get logs similar to the image. Don’t close the terminal, otherwise, the solana validator will be killed.

Solana test validator

Finally, return to the terminal where you built the contract and type the command:

solana program deploy target/deploy/informal_lender.so

This command will deploy the built contract to the local solana validator we ran in the previous step. When the command completes, it will return the Program Id of the contract, save it for later.

Deploy target

info

Your Program Id will be different from the one presented in this documentation.

Integrate the client library

Wow! We have built and deployed a Solana DeFi program in just a few minutes. The last step is to use this program from our application. For this workshop, we will keep it simple and integrate the program client into a Node.js CLI application.

Go to your file explorer and create a new file named app.ts in the program_client directory:

app.ts file creation

In the file, app.ts copy and paste the following code:

import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
sendAndConfirmTransaction,
SystemProgram,
Transaction
} from "@solana/web3.js";
import * as fs from "fs/promises";
import * as path from "path";
import * as os from "os";
import {
approveLoanSendAndConfirm,
createBrokerSendAndConfirm,
deriveBrokerPDA,
deriveLoanPDA,
getBroker,
getLoan,
initializeClient,
payLoanSendAndConfirm,
requestLoanSendAndConfirm,
} from "./index";

async function main(feePayer: Keypair) {
const connection = new Connection("http://127.0.0.1:8899", {
commitment: "confirmed"
});

// TODO: Specify the smart contract Program Id we saved from when we deploy the smart contract
const progId = new PublicKey("PASTE_YOUR_PROGRAM_ID_HERE");

initializeClient(progId, connection);

/**
* Create wallets to represent the broker creator and 2 clients
*/
const delegate = Keypair.generate();
const client1 = Keypair.generate();
const client2 = Keypair.generate();

const rent = await connection.getMinimumBalanceForRentExemption(0);
const capital = 5 * LAMPORTS_PER_SOL;
const interest = 5 * LAMPORTS_PER_SOL;

await sendAndConfirmTransaction(
connection,
new Transaction()
.add(
SystemProgram.createAccount({
fromPubkey: feePayer.publicKey,
newAccountPubkey: delegate.publicKey,
space: 0,
lamports: rent + capital,
programId: progId,
})
)
.add(
SystemProgram.createAccount({
fromPubkey: feePayer.publicKey,
newAccountPubkey: client1.publicKey,
space: 0,
lamports: rent + interest,
programId: progId,
})
)
.add(
SystemProgram.createAccount({
fromPubkey: feePayer.publicKey,
newAccountPubkey: client2.publicKey,
space: 0,
lamports: rent + interest,
programId: progId,
})
),
[feePayer, delegate, client1, client2]
);

const [brokerPK] = deriveBrokerPDA(progId);
let broker = await getBroker(brokerPK)

// Broken can only exists once per contract
// so let's check if we haven't created it
if (!broker) {
await createBrokerSendAndConfirm({
amount: BigInt(capital),
fee: 10,
signers: {
feePayer,
delegate,
}
});
broker = await getBroker(brokerPK)
}

// Log current state of the broker
console.log("+=====+BROKER STATE+=====+")
console.info(broker);

// Request loan 1 by client 1
const [client1Loan1] = deriveLoanPDA({
client: client1.publicKey,
index: 1,
}, progId)
await requestLoanSendAndConfirm({
amount: BigInt(2.5 * LAMPORTS_PER_SOL),
kycUrl: "https://example.com",
loanSeedIndex: 1,
signers: {
feePayer,
client: client1,
}
});
console.log("+=====+CLIENT ONE LOAN ONE STATE+=====+")
let client1Loan1State = await getLoan(client1Loan1);
console.info(client1Loan1State);

// Aprove loan 1 by client 1
await approveLoanSendAndConfirm({
client: client1.publicKey,
loanSeedIndex: 1,
signers: {
feePayer,
delegate,
}
});
console.log("+=====+CLIENT ONE LOAN ONE STATE+=====+")
client1Loan1State = await getLoan(client1Loan1);
console.info(client1Loan1State);

console.log("+=====+BROKER STATE+=====+")
broker = await getBroker(brokerPK)
console.info(broker);

// Request loan 2 by client 1
const [client1Loan2] = deriveLoanPDA({
client: client1.publicKey,
index: 2,
}, progId)
await requestLoanSendAndConfirm({
amount: BigInt(2 * LAMPORTS_PER_SOL),
kycUrl: "https://example.com",
loanSeedIndex: 2,
signers: {
feePayer,
client: client1,
}
});
console.log("+=====+CLIENT ONE LOAN TWO STATE+=====+")
let client1Loan2State = await getLoan(client1Loan2);
console.info(client1Loan2State);

// Aprove loan 2 by client 1
await approveLoanSendAndConfirm({
client: client1.publicKey,
loanSeedIndex: 2,
signers: {
feePayer,
delegate,
}
});
console.log("+=====+CLIENT ONE LOAN TWO STATE+=====+")
client1Loan2State = await getLoan(client1Loan2);
console.info(client1Loan2State);

console.log("+=====+BROKER STATE+=====+")
broker = await getBroker(brokerPK)
console.info(broker);

// Request loan 1 by client 2
// This request must fail because broker doesn't have capital
try {
const [client2Loan1] = deriveLoanPDA({
client: client2.publicKey,
index: 1,
}, progId)
await requestLoanSendAndConfirm({
amount: BigInt(2 * LAMPORTS_PER_SOL),
kycUrl: "https://example.com",
loanSeedIndex: 1,
signers: {
feePayer,
client: client2,
}
});
let client2Loan1State = await getLoan(client2Loan1);
console.info(client2Loan1State);
} catch (e) {
console.info(`Broker doesn't have capital to lend: ${e}`)
}

// Pay the full amount of loan 1 by client 1
await payLoanSendAndConfirm({
amount: BigInt(2.5 * LAMPORTS_PER_SOL),
loanSeedIndex: 1,
signers: {
feePayer,
client: client1,
}
});
console.log("+=====+CLIENT ONE LOAN ONE STATE+=====+")
client1Loan1State = await getLoan(client1Loan1);
console.info(client1Loan1State);

console.log("+=====+BROKER STATE+=====+")
broker = await getBroker(brokerPK)
console.info(broker);

// Pay the full amount of loan 2 by client 1
await payLoanSendAndConfirm({
amount: BigInt(2 * LAMPORTS_PER_SOL),
loanSeedIndex: 2,
signers: {
feePayer,
client: client1,
}
});
console.log("+=====+CLIENT ONE LOAN TWO STATE+=====+")
client1Loan2State = await getLoan(client1Loan2);
console.info(client1Loan2State);

console.log("+=====+BROKER STATE+=====+")
broker = await getBroker(brokerPK)
console.info(broker);

// Request loan 1 by client 2
const [client2Loan1] = deriveLoanPDA({
client: client2.publicKey,
index: 1,
}, progId)
await requestLoanSendAndConfirm({
amount: BigInt(2 * LAMPORTS_PER_SOL),
kycUrl: "https://example.com",
loanSeedIndex: 1,
signers: {
feePayer,
client: client2,
}
});
console.log("+=====+CLIENT TWO LOAN ONE STATE+=====+")
let client2Loan1State = await getLoan(client2Loan1);
console.info(client2Loan1State);

console.log("+=====+BROKER STATE+=====+")
broker = await getBroker(brokerPK)
console.info(broker);
}

fs.readFile(path.join(os.homedir(), ".config/solana/id.json"))
.then(file => main(Keypair.fromSecretKey(new Uint8Array(JSON.parse(file.toString())))));
tip

Remember to replace your program id you got after deploying your program in the string where it says "PASTE_YOUR_PROGRAM_ID_HERE"

Replace program id

The final step, open a new terminal by going to Terminal -> New Terminal. Navigate to the program client directory by typing the command cd program_client, and inside the program_client directory, type the following command npm install @types/node ts-node after it completes, execute the command npx ts-node app.ts if you did these steps correctly you will get the following output:

App.ts output

Congratulations! 🎉👏 you just created your first Solana DeFi program using the Código Platform. The app.ts code is straightforward, first, we create some wallets, and after that, we create the broker by calling the create_broker function, and we add a check to verify if the broker is not already created, otherwise, the Solana program returns an error. Secondly, we start to request, approve, and pay some loans, if we implemented the business logic correctly the loan life cycle should work as expected.

Join the Código community 💚

Código is a growing community of developers. Join us on Discord and GitHub