mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-02-26 16:52:25 +00:00
Compare commits
8 Commits
remove-bat
...
go-code-sa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f52c9f626 | ||
|
|
5f60256f8e | ||
|
|
50bff79a4e | ||
|
|
c9b3e88219 | ||
|
|
58fe72fccf | ||
|
|
8c68e6bf7a | ||
|
|
37443e2dd6 | ||
|
|
376095d9b6 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ yarn-error.log
|
||||
*.iml
|
||||
.venv/
|
||||
_code-samples/*/js/package-lock.json
|
||||
_code-samples/*/go/go.sum
|
||||
_code-samples/*/*/*[Ss]etup.json
|
||||
|
||||
# PHP
|
||||
|
||||
293
_code-samples/lending-protocol/go/README.md
Normal file
293
_code-samples/lending-protocol/go/README.md
Normal file
@@ -0,0 +1,293 @@
|
||||
# Lending Protocol Examples (Go)
|
||||
|
||||
This directory contains Go examples demonstrating how to create a loan broker, claw back first-loss capital, deposit and withdraw first-loss capital, create a loan, manage a loan, and repay a loan.
|
||||
|
||||
## Setup
|
||||
|
||||
All commands should be run from this `go/` directory.
|
||||
|
||||
Install dependencies before running any examples:
|
||||
|
||||
```sh
|
||||
go mod tidy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Loan Broker
|
||||
|
||||
```sh
|
||||
go run ./create-loan-broker
|
||||
```
|
||||
|
||||
The script should output the LoanBrokerSet transaction, loan broker ID, and loan broker pseudo-account.
|
||||
|
||||
```sh
|
||||
Loan broker/vault owner address: rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV
|
||||
Vault ID: A300D6F7D43E1B143683F1917EE6456B0C3E84F0F763D9A1366FCD22138A11E9
|
||||
|
||||
=== Preparing LoanBrokerSet transaction ===
|
||||
|
||||
{
|
||||
"Account": "rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV",
|
||||
"ManagementFeeRate": 1000,
|
||||
"TransactionType": "LoanBrokerSet",
|
||||
"VaultID": "A300D6F7D43E1B143683F1917EE6456B0C3E84F0F763D9A1366FCD22138A11E9"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerSet transaction ===
|
||||
|
||||
Loan broker created successfully!
|
||||
|
||||
=== Loan Broker Information ===
|
||||
|
||||
LoanBroker ID: E4D9C485E101FAE449C8ACEC7FD039920CC02D2443687F2593DB397CC8EA670B
|
||||
LoanBroker Pseudo-Account Address: rMDsnf9CVRLRJzrL12Ex7nhstbni78y8af
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Claw Back First-loss Capital
|
||||
|
||||
```sh
|
||||
go run ./cover-clawback
|
||||
```
|
||||
|
||||
The script should output the cover available, the LoanBrokerCoverDeposit transaction, cover available after the deposit, the LoanBrokerCoverClawback transaction, and the final cover available after the clawback.
|
||||
|
||||
```sh
|
||||
Loan broker address: rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV
|
||||
MPT issuer address: rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW
|
||||
LoanBrokerID: 373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D
|
||||
MPT ID: 003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51
|
||||
|
||||
=== Cover Available ===
|
||||
|
||||
0 TSTUSD
|
||||
|
||||
=== Preparing LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
{
|
||||
"Account": "rLsTX2RjNTqwiwNpMn7mny3MyrXtmbhFQV",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51",
|
||||
"value": "1000"
|
||||
},
|
||||
"LoanBrokerID": "373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D",
|
||||
"TransactionType": "LoanBrokerCoverDeposit"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
Cover deposit successful!
|
||||
|
||||
=== Cover Available After Deposit ===
|
||||
|
||||
1000 TSTUSD
|
||||
|
||||
=== Verifying Asset Issuer ===
|
||||
|
||||
MPT issuer account verified: rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW. Proceeding to clawback.
|
||||
|
||||
=== Preparing LoanBrokerCoverClawback transaction ===
|
||||
|
||||
{
|
||||
"Account": "rfYxCEWxA9ACyvpciPZYbKujjLEVX5CCwW",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003A9D5247DC1C9997DB5500A84C3EC748F3F61D2BC56D51",
|
||||
"value": "1000"
|
||||
},
|
||||
"LoanBrokerID": "373BEBB8A1EF735FCD330C2B0DDF2C37FD3B1589B084C94F2CA52A904FBED08D",
|
||||
"TransactionType": "LoanBrokerCoverClawback"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverClawback transaction ===
|
||||
|
||||
Successfully clawed back 1000 TSTUSD!
|
||||
|
||||
=== Final Cover Available After Clawback ===
|
||||
|
||||
0 TSTUSD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deposit and Withdraw First-loss Capital
|
||||
|
||||
```sh
|
||||
go run ./cover-deposit-and-withdraw
|
||||
```
|
||||
|
||||
The script should output the LoanBrokerCoverDeposit, cover balance after the deposit, the LoanBrokerCoverWithdraw transaction, and the cover balance after the withdrawal.
|
||||
|
||||
```sh
|
||||
Loan broker address: rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV
|
||||
LoanBrokerID: 633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3
|
||||
MPT ID: 003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315
|
||||
|
||||
=== Preparing LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
{
|
||||
"Account": "rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315",
|
||||
"value": "2000"
|
||||
},
|
||||
"LoanBrokerID": "633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3",
|
||||
"TransactionType": "LoanBrokerCoverDeposit"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverDeposit transaction ===
|
||||
|
||||
Cover deposit successful!
|
||||
|
||||
=== Cover Balance ===
|
||||
|
||||
LoanBroker Pseudo-Account: rJoTTaGKQr8o475xKNZkEPRsmTbUkr6sbi
|
||||
Cover balance after deposit: 2000 TSTUSD
|
||||
|
||||
=== Preparing LoanBrokerCoverWithdraw transaction ===
|
||||
|
||||
{
|
||||
"Account": "rU9ANkvdSCs7p59Guf2XzGrrCPSMM2tDQV",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003B6A9EE92357082A44FA2EAA26385E2F85071634BC3315",
|
||||
"value": "1000"
|
||||
},
|
||||
"LoanBrokerID": "633375BCB82DB1440189D3E0721AF92B0888304717DA1B002C8824D631E97DC3",
|
||||
"TransactionType": "LoanBrokerCoverWithdraw"
|
||||
}
|
||||
|
||||
=== Submitting LoanBrokerCoverWithdraw transaction ===
|
||||
|
||||
Cover withdraw successful!
|
||||
|
||||
=== Updated Cover Balance ===
|
||||
|
||||
LoanBroker Pseudo-Account: rJoTTaGKQr8o475xKNZkEPRsmTbUkr6sbi
|
||||
Cover balance after withdraw: 1000 TSTUSD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create a Loan
|
||||
|
||||
```sh
|
||||
go run ./create-loan
|
||||
```
|
||||
|
||||
The script should output the LoanSet transaction, the updated LoanSet transaction with the loan broker signature, the final LoanSet transaction with the borrower signature added, and then the loan information.
|
||||
|
||||
---
|
||||
|
||||
## Manage a Loan
|
||||
|
||||
```sh
|
||||
go run ./loan-manage
|
||||
```
|
||||
|
||||
The script should output the initial status of the loan, the LoanManage transaction, and the updated loan status and grace period after impairment. The script will countdown the grace period before outputting another LoanManage transaction, and then the final flags on the loan.
|
||||
|
||||
```sh
|
||||
Loan broker address: rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX
|
||||
LoanID: 2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C
|
||||
|
||||
=== Loan Status ===
|
||||
|
||||
Total Amount Owed: 1001 TSTUSD.
|
||||
Payment Due Date: 2026-03-23 01:23:40
|
||||
|
||||
=== Preparing LoanManage transaction to impair loan ===
|
||||
|
||||
{
|
||||
"Account": "rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX",
|
||||
"Flags": 131072,
|
||||
"LoanID": "2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C",
|
||||
"TransactionType": "LoanManage"
|
||||
}
|
||||
|
||||
=== Submitting LoanManage impairment transaction ===
|
||||
|
||||
Loan impaired successfully!
|
||||
New Payment Due Date: 2026-02-21 00:24:10
|
||||
Grace Period: 60 seconds
|
||||
|
||||
=== Countdown until loan can be defaulted ===
|
||||
|
||||
Grace period expired. Loan can now be defaulted.
|
||||
|
||||
=== Preparing LoanManage transaction to default loan ===
|
||||
|
||||
{
|
||||
"Account": "rN7eCZhKHcq5LEC2W2RrrGcUPBYwZagEPX",
|
||||
"Flags": 65536,
|
||||
"LoanID": "2BD3F3F587D1BD4FB247B0935FB098E2DC6E3B571F493472CED914216990EC6C",
|
||||
"TransactionType": "LoanManage"
|
||||
}
|
||||
|
||||
=== Submitting LoanManage default transaction ===
|
||||
|
||||
Loan defaulted successfully!
|
||||
|
||||
=== Checking final loan status ===
|
||||
|
||||
Final loan flags: [tfLoanDefault tfLoanImpair]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pay a Loan
|
||||
|
||||
```sh
|
||||
go run ./loan-pay
|
||||
```
|
||||
|
||||
The script should output the amount required to totally pay off a loan, the LoanPay transaction, the amount due after the payment, the LoanDelete transaction, and then the status of the loan ledger entry.
|
||||
|
||||
```sh
|
||||
Borrower address: rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2
|
||||
LoanID: D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A
|
||||
MPT ID: 003B8FC2F51C1BC4E0211E6370EC4FC78BB20D5C4069F07B
|
||||
|
||||
=== Loan Status ===
|
||||
|
||||
Amount Owed: 1001 TSTUSD
|
||||
Loan Service Fee: 10 TSTUSD
|
||||
Total Payment Due (including fees): 1011 TSTUSD
|
||||
|
||||
=== Preparing LoanPay transaction ===
|
||||
|
||||
{
|
||||
"Account": "rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2",
|
||||
"Amount": {
|
||||
"mpt_issuance_id": "003B8FC2F51C1BC4E0211E6370EC4FC78BB20D5C4069F07B",
|
||||
"value": "1011"
|
||||
},
|
||||
"LoanID": "D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A",
|
||||
"TransactionType": "LoanPay"
|
||||
}
|
||||
|
||||
=== Submitting LoanPay transaction ===
|
||||
|
||||
Loan paid successfully!
|
||||
|
||||
=== Loan Status After Payment ===
|
||||
|
||||
Outstanding Loan Balance: Loan fully paid off!
|
||||
|
||||
=== Preparing LoanDelete transaction ===
|
||||
|
||||
{
|
||||
"Account": "rFx8s3P5J66MAvWkp5rMj5bBF76gQUCt2",
|
||||
"LoanID": "D0455CD5F9C2FEC62FC67F31BD97134FBA877D7FE1AE7130EE0006D10661325A",
|
||||
"TransactionType": "LoanDelete"
|
||||
}
|
||||
|
||||
=== Submitting LoanDelete transaction ===
|
||||
|
||||
Loan deleted successfully!
|
||||
|
||||
=== Verifying Loan Deletion ===
|
||||
|
||||
Loan has been successfully removed from the XRP Ledger!
|
||||
```
|
||||
210
_code-samples/lending-protocol/go/cover-clawback/main.go
Normal file
210
_code-samples/lending-protocol/go/cover-clawback/main.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// IMPORTANT: This example deposits and claws back first-loss capital from a
|
||||
// preconfigured LoanBroker entry. The first-loss capital is an MPT
|
||||
// with clawback enabled.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
// mptIssuanceEntryRequest looks up an MPTIssuance ledger entry by its MPT ID.
|
||||
// The library's GetLedgerEntry() method only supports lookup by ledger entry ID,
|
||||
// so this custom type is used with the generic Request() method.
|
||||
type mptIssuanceEntryRequest struct {
|
||||
common.BaseRequest
|
||||
MPTIssuance string `json:"mpt_issuance"`
|
||||
LedgerIndex common.LedgerSpecifier `json:"ledger_index,omitempty"`
|
||||
}
|
||||
|
||||
func (*mptIssuanceEntryRequest) Method() string { return "ledger_entry" }
|
||||
func (*mptIssuanceEntryRequest) Validate() error { return nil }
|
||||
func (*mptIssuanceEntryRequest) APIVersion() int { return 2 }
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mptIssuerWallet, err := wallet.FromSecret(setup["depositor"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanBrokerID := setup["loanBrokerID"].(string)
|
||||
mptID := setup["mptID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("MPT issuer address: %s\n", mptIssuerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
|
||||
fmt.Printf("MPT ID: %s\n", mptID)
|
||||
|
||||
// Check cover available ----------------------
|
||||
fmt.Printf("\n=== Cover Available ===\n\n")
|
||||
coverInfo, err := client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanBrokerID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
currentCoverAvailable := "0"
|
||||
if ca, ok := coverInfo.Node["CoverAvailable"].(string); ok {
|
||||
currentCoverAvailable = ca
|
||||
}
|
||||
fmt.Printf("%s TSTUSD\n", currentCoverAvailable)
|
||||
|
||||
// Prepare LoanBrokerCoverDeposit transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
coverDepositTx := transaction.LoanBrokerCoverDeposit{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "1000",
|
||||
},
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatCoverDepositTx := coverDepositTx.Flatten()
|
||||
coverDepositTxJSON, _ := json.MarshalIndent(flatCoverDepositTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverDepositTxJSON))
|
||||
|
||||
// Sign, submit, and wait for deposit validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
depositResponse, err := client.SubmitTxAndWait(flatCoverDepositTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if depositResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to deposit cover: %s\n", depositResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Cover deposit successful!\n")
|
||||
|
||||
// Extract updated cover available after deposit ----------------------
|
||||
fmt.Printf("\n=== Cover Available After Deposit ===\n\n")
|
||||
for _, node := range depositResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
currentCoverAvailable = node.ModifiedNode.FinalFields["CoverAvailable"].(string)
|
||||
break
|
||||
}
|
||||
}
|
||||
fmt.Printf("%s TSTUSD\n", currentCoverAvailable)
|
||||
|
||||
// Verify issuer of cover asset matches ----------------------
|
||||
// Only the issuer of the asset can submit clawback transactions.
|
||||
// The asset must also have clawback enabled.
|
||||
fmt.Printf("\n=== Verifying Asset Issuer ===\n\n")
|
||||
assetIssuerInfo, err := client.Request(&mptIssuanceEntryRequest{
|
||||
MPTIssuance: mptID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
issuer := assetIssuerInfo.Result["node"].(map[string]any)["Issuer"].(string)
|
||||
if issuer != string(mptIssuerWallet.ClassicAddress) {
|
||||
fmt.Printf("Error: %s does not match account (%s) attempting clawback!\n", issuer, mptIssuerWallet.ClassicAddress)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("MPT issuer account verified: %s. Proceeding to clawback.\n", mptIssuerWallet.ClassicAddress)
|
||||
|
||||
// Prepare LoanBrokerCoverClawback transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverClawback transaction ===\n\n")
|
||||
lbID := types.LoanBrokerID(loanBrokerID)
|
||||
coverClawbackTx := transaction.LoanBrokerCoverClawback{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: mptIssuerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: &lbID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: currentCoverAvailable,
|
||||
},
|
||||
}
|
||||
|
||||
flatCoverClawbackTx := coverClawbackTx.Flatten()
|
||||
coverClawbackTxJSON, _ := json.MarshalIndent(flatCoverClawbackTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverClawbackTxJSON))
|
||||
|
||||
// Sign, submit, and wait for clawback validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverClawback transaction ===\n\n")
|
||||
clawbackResponse, err := client.SubmitTxAndWait(flatCoverClawbackTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &mptIssuerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if clawbackResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to clawback cover: %s\n", clawbackResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Successfully clawed back %s TSTUSD!\n", currentCoverAvailable)
|
||||
|
||||
// Extract final cover available ----------------------
|
||||
fmt.Printf("\n=== Final Cover Available After Clawback ===\n\n")
|
||||
for _, node := range clawbackResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
finalCover := "0"
|
||||
if ca, ok := node.ModifiedNode.FinalFields["CoverAvailable"].(string); ok {
|
||||
finalCover = ca
|
||||
}
|
||||
fmt.Printf("%s TSTUSD\n", finalCover)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// IMPORTANT: This example deposits and withdraws first-loss capital from a
|
||||
// preconfigured LoanBroker entry.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanBrokerID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanBrokerID := setup["loanBrokerID"].(string)
|
||||
mptID := setup["mptID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanBrokerID: %s\n", loanBrokerID)
|
||||
fmt.Printf("MPT ID: %s\n", mptID)
|
||||
|
||||
// Prepare LoanBrokerCoverDeposit transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
coverDepositTx := transaction.LoanBrokerCoverDeposit{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "2000",
|
||||
},
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatCoverDepositTx := coverDepositTx.Flatten()
|
||||
coverDepositTxJSON, _ := json.MarshalIndent(flatCoverDepositTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverDepositTxJSON))
|
||||
|
||||
// Sign, submit, and wait for deposit validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n\n")
|
||||
depositResponse, err := client.SubmitTxAndWait(flatCoverDepositTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if depositResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to deposit cover: %s\n", depositResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Cover deposit successful!\n")
|
||||
|
||||
// Extract cover balance from the transaction result
|
||||
fmt.Printf("\n=== Cover Balance ===\n\n")
|
||||
for _, node := range depositResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
// First-loss capital is stored in the LoanBroker's pseudo-account.
|
||||
fmt.Printf("LoanBroker Pseudo-Account: %s\n", node.ModifiedNode.FinalFields["Account"])
|
||||
fmt.Printf("Cover balance after deposit: %s TSTUSD\n", node.ModifiedNode.FinalFields["CoverAvailable"])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare LoanBrokerCoverWithdraw transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerCoverWithdraw transaction ===\n\n")
|
||||
coverWithdrawTx := transaction.LoanBrokerCoverWithdraw{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanBrokerID: loanBrokerID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: "1000",
|
||||
},
|
||||
}
|
||||
|
||||
flatCoverWithdrawTx := coverWithdrawTx.Flatten()
|
||||
coverWithdrawTxJSON, _ := json.MarshalIndent(flatCoverWithdrawTx, "", " ")
|
||||
fmt.Printf("%s\n", string(coverWithdrawTxJSON))
|
||||
|
||||
// Sign, submit, and wait for withdraw validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerCoverWithdraw transaction ===\n\n")
|
||||
withdrawResponse, err := client.SubmitTxAndWait(flatCoverWithdrawTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if withdrawResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to withdraw cover: %s\n", withdrawResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Cover withdraw successful!\n")
|
||||
|
||||
// Extract updated cover balance from the transaction result
|
||||
fmt.Printf("\n=== Updated Cover Balance ===\n\n")
|
||||
for _, node := range withdrawResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "LoanBroker" {
|
||||
fmt.Printf("LoanBroker Pseudo-Account: %s\n", node.ModifiedNode.FinalFields["Account"])
|
||||
fmt.Printf("Cover balance after withdraw: %s TSTUSD\n", node.ModifiedNode.FinalFields["CoverAvailable"])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
105
_code-samples/lending-protocol/go/create-loan-broker/main.go
Normal file
105
_code-samples/lending-protocol/go/create-loan-broker/main.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// IMPORTANT: This example creates a loan broker using an existing account
|
||||
// that has already created a PRIVATE vault.
|
||||
// If you want to create a loan broker for a PUBLIC vault, you can replace the vaultID
|
||||
// and loanBroker values with your own.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and VaultID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
vaultID := setup["vaultID"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker/vault owner address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("Vault ID: %s\n", vaultID)
|
||||
|
||||
// Prepare LoanBrokerSet transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanBrokerSet transaction ===\n\n")
|
||||
mgmtFeeRate := types.InterestRate(1000)
|
||||
loanBrokerSetTx := transaction.LoanBrokerSet{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
VaultID: vaultID,
|
||||
ManagementFeeRate: &mgmtFeeRate,
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatLoanBrokerSetTx := loanBrokerSetTx.Flatten()
|
||||
loanBrokerSetTxJSON, _ := json.MarshalIndent(flatLoanBrokerSetTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanBrokerSetTxJSON))
|
||||
|
||||
// Submit, sign, and wait for validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanBrokerSet transaction ===\n\n")
|
||||
loanBrokerSetResponse, err := client.SubmitTxAndWait(flatLoanBrokerSetTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if loanBrokerSetResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to create loan broker: %s\n", loanBrokerSetResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan broker created successfully!\n")
|
||||
|
||||
// Extract loan broker information from the transaction result
|
||||
fmt.Printf("\n=== Loan Broker Information ===\n\n")
|
||||
for _, node := range loanBrokerSetResponse.Meta.AffectedNodes {
|
||||
if node.CreatedNode != nil && node.CreatedNode.LedgerEntryType == "LoanBroker" {
|
||||
fmt.Printf("LoanBroker ID: %s\n", node.CreatedNode.LedgerIndex)
|
||||
fmt.Printf("LoanBroker Pseudo-Account Address: %s\n", node.CreatedNode.NewFields["Account"])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
20
_code-samples/lending-protocol/go/go.mod
Normal file
20
_code-samples/lending-protocol/go/go.mod
Normal file
@@ -0,0 +1,20 @@
|
||||
module github.com/XRPLF
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require github.com/Peersyst/xrpl-go v0.1.15
|
||||
|
||||
require (
|
||||
github.com/bsv-blockchain/go-sdk v1.2.9 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
|
||||
github.com/decred/dcrd/crypto/ripemd160 v1.0.2 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/crypto v0.44.0 // indirect
|
||||
)
|
||||
196
_code-samples/lending-protocol/go/loan-manage/main.go
Normal file
196
_code-samples/lending-protocol/go/loan-manage/main.go
Normal file
@@ -0,0 +1,196 @@
|
||||
// IMPORTANT: This example impairs an existing loan, which has a 60 second grace period.
|
||||
// After the 60 seconds pass, this example defaults the loan.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
|
||||
xrpltime "github.com/Peersyst/xrpl-go/xrpl/time"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
loanBrokerWallet, err := wallet.FromSecret(setup["loanBroker"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanID := setup["loanID1"].(string)
|
||||
|
||||
fmt.Printf("\nLoan broker address: %s\n", loanBrokerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanID: %s\n", loanID)
|
||||
|
||||
// Check loan status before impairment ----------------------
|
||||
fmt.Printf("\n=== Loan Status ===\n\n")
|
||||
loanStatus, err := client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Total Amount Owed: %s TSTUSD.\n", loanStatus.Node["TotalValueOutstanding"])
|
||||
|
||||
// Convert Ripple Epoch timestamp to local date and time
|
||||
nextPaymentDueDate := int64(loanStatus.Node["NextPaymentDueDate"].(float64))
|
||||
paymentDue := time.UnixMilli(xrpltime.RippleTimeToUnixTime(nextPaymentDueDate))
|
||||
fmt.Printf("Payment Due Date: %s\n", paymentDue.Local().Format(time.DateTime))
|
||||
|
||||
// Prepare LoanManage transaction to impair the loan ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanManage transaction to impair loan ===\n\n")
|
||||
loanManageImpair := transaction.LoanManage{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
}
|
||||
loanManageImpair.SetLoanImpairFlag()
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatLoanManageImpair := loanManageImpair.Flatten()
|
||||
loanManageImpairJSON, _ := json.MarshalIndent(flatLoanManageImpair, "", " ")
|
||||
fmt.Printf("%s\n", string(loanManageImpairJSON))
|
||||
|
||||
// Sign, submit, and wait for impairment validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanManage impairment transaction ===\n\n")
|
||||
impairResponse, err := client.SubmitTxAndWait(flatLoanManageImpair, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if impairResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to impair loan: %s\n", impairResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan impaired successfully!\n")
|
||||
|
||||
// Extract loan impairment info from transaction results ----------------------
|
||||
var loanNode map[string]any
|
||||
for _, node := range impairResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
|
||||
loanNode = node.ModifiedNode.FinalFields
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Check grace period and next payment due date
|
||||
gracePeriod := int64(loanNode["GracePeriod"].(float64))
|
||||
nextPaymentDueDate = int64(loanNode["NextPaymentDueDate"].(float64))
|
||||
defaultTime := nextPaymentDueDate + gracePeriod
|
||||
paymentDue = time.UnixMilli(xrpltime.RippleTimeToUnixTime(nextPaymentDueDate))
|
||||
|
||||
fmt.Printf("New Payment Due Date: %s\n", paymentDue.Local().Format(time.DateTime))
|
||||
fmt.Printf("Grace Period: %d seconds\n", gracePeriod)
|
||||
|
||||
// Convert current time to Ripple Epoch timestamp
|
||||
currentTime := xrpltime.UnixTimeToRippleTime(time.Now().Unix())
|
||||
// Add a small buffer (5 seconds) to account for ledger close time
|
||||
secondsUntilDefault := defaultTime - currentTime + 5
|
||||
|
||||
// Countdown until loan can be defaulted ----------------------
|
||||
fmt.Printf("\n=== Countdown until loan can be defaulted ===\n\n")
|
||||
for secondsUntilDefault >= 0 {
|
||||
fmt.Printf("\r%d seconds...", secondsUntilDefault)
|
||||
time.Sleep(time.Second)
|
||||
secondsUntilDefault--
|
||||
}
|
||||
fmt.Print("\rGrace period expired. Loan can now be defaulted.\n")
|
||||
|
||||
// Prepare LoanManage transaction to default the loan ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanManage transaction to default loan ===\n\n")
|
||||
loanManageDefault := transaction.LoanManage{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: loanBrokerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
}
|
||||
loanManageDefault.SetLoanDefaultFlag()
|
||||
|
||||
flatLoanManageDefault := loanManageDefault.Flatten()
|
||||
loanManageDefaultJSON, _ := json.MarshalIndent(flatLoanManageDefault, "", " ")
|
||||
fmt.Printf("%s\n", string(loanManageDefaultJSON))
|
||||
|
||||
// Sign, submit, and wait for default validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanManage default transaction ===\n\n")
|
||||
defaultResponse, err := client.SubmitTxAndWait(flatLoanManageDefault, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &loanBrokerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if defaultResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to default loan: %s\n", defaultResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan defaulted successfully!\n")
|
||||
|
||||
// Verify loan default status from transaction results ----------------------
|
||||
fmt.Printf("\n=== Checking final loan status ===\n\n")
|
||||
for _, node := range defaultResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
|
||||
loanNode = node.ModifiedNode.FinalFields
|
||||
break
|
||||
}
|
||||
}
|
||||
loanFlags := uint32(loanNode["Flags"].(float64))
|
||||
|
||||
// Check which loan flags are set:
|
||||
// 0x00010000 = tfLoanDefault, 0x00020000 = tfLoanImpair
|
||||
activeFlags := []string{}
|
||||
if types.IsFlagEnabled(loanFlags, 0x00010000) {
|
||||
activeFlags = append(activeFlags, "tfLoanDefault")
|
||||
}
|
||||
if types.IsFlagEnabled(loanFlags, 0x00020000) {
|
||||
activeFlags = append(activeFlags, "tfLoanImpair")
|
||||
}
|
||||
fmt.Printf("Final loan flags: %v\n", activeFlags)
|
||||
}
|
||||
179
_code-samples/lending-protocol/go/loan-pay/main.go
Normal file
179
_code-samples/lending-protocol/go/loan-pay/main.go
Normal file
@@ -0,0 +1,179 @@
|
||||
// IMPORTANT: This example pays off an existing loan and then deletes it.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/common"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/queries/ledger"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/wallet"
|
||||
"github.com/Peersyst/xrpl-go/xrpl/websocket"
|
||||
wstypes "github.com/Peersyst/xrpl-go/xrpl/websocket/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Connect to the network ----------------------
|
||||
client := websocket.NewClient(
|
||||
websocket.NewClientConfig().
|
||||
WithHost("wss://s.devnet.rippletest.net:51233"),
|
||||
)
|
||||
defer client.Disconnect()
|
||||
|
||||
if err := client.Connect(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check for setup data; run lending-setup if missing
|
||||
if _, err := os.Stat("lending-setup.json"); os.IsNotExist(err) {
|
||||
fmt.Printf("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n\n")
|
||||
cmd := exec.Command("go", "run", "./lending-setup")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load preconfigured accounts and LoanID
|
||||
data, err := os.ReadFile("lending-setup.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
var setup map[string]any
|
||||
if err := json.Unmarshal(data, &setup); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// You can replace these values with your own
|
||||
borrowerWallet, err := wallet.FromSecret(setup["borrower"].(map[string]any)["seed"].(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
loanID := setup["loanID2"].(string)
|
||||
mptID := setup["mptID"].(string)
|
||||
|
||||
fmt.Printf("\nBorrower address: %s\n", borrowerWallet.ClassicAddress)
|
||||
fmt.Printf("LoanID: %s\n", loanID)
|
||||
fmt.Printf("MPT ID: %s\n", mptID)
|
||||
|
||||
// Check initial loan status ----------------------
|
||||
fmt.Printf("\n=== Loan Status ===\n\n")
|
||||
loanStatus, err := client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
totalValueOutstanding := loanStatus.Node["TotalValueOutstanding"].(string)
|
||||
loanServiceFee := loanStatus.Node["LoanServiceFee"].(string)
|
||||
|
||||
outstanding, _ := new(big.Int).SetString(totalValueOutstanding, 10)
|
||||
serviceFee, _ := new(big.Int).SetString(loanServiceFee, 10)
|
||||
totalPayment := new(big.Int).Add(outstanding, serviceFee).String()
|
||||
|
||||
fmt.Printf("Amount Owed: %s TSTUSD\n", totalValueOutstanding)
|
||||
fmt.Printf("Loan Service Fee: %s TSTUSD\n", loanServiceFee)
|
||||
fmt.Printf("Total Payment Due (including fees): %s TSTUSD\n", totalPayment)
|
||||
|
||||
// Prepare LoanPay transaction ----------------------
|
||||
fmt.Printf("\n=== Preparing LoanPay transaction ===\n\n")
|
||||
loanPayTx := transaction.LoanPay{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
Amount: types.MPTCurrencyAmount{
|
||||
MPTIssuanceID: mptID,
|
||||
Value: totalPayment,
|
||||
},
|
||||
}
|
||||
|
||||
// Flatten() converts the struct to a map and adds the TransactionType field
|
||||
flatLoanPayTx := loanPayTx.Flatten()
|
||||
loanPayTxJSON, _ := json.MarshalIndent(flatLoanPayTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanPayTxJSON))
|
||||
|
||||
// Sign, submit, and wait for payment validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanPay transaction ===\n\n")
|
||||
payResponse, err := client.SubmitTxAndWait(flatLoanPayTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &borrowerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if payResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to pay loan: %s\n", payResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan paid successfully!\n")
|
||||
|
||||
// Extract updated loan info from transaction results ----------------------
|
||||
fmt.Printf("\n=== Loan Status After Payment ===\n\n")
|
||||
for _, node := range payResponse.Meta.AffectedNodes {
|
||||
if node.ModifiedNode != nil && node.ModifiedNode.LedgerEntryType == "Loan" {
|
||||
if balance, ok := node.ModifiedNode.FinalFields["TotalValueOutstanding"].(string); ok {
|
||||
fmt.Printf("Outstanding Loan Balance: %s TSTUSD\n", balance)
|
||||
} else {
|
||||
fmt.Printf("Outstanding Loan Balance: Loan fully paid off!\n")
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare LoanDelete transaction ----------------------
|
||||
// Either the loan broker or borrower can submit this transaction.
|
||||
fmt.Printf("\n=== Preparing LoanDelete transaction ===\n\n")
|
||||
loanDeleteTx := transaction.LoanDelete{
|
||||
BaseTx: transaction.BaseTx{
|
||||
Account: borrowerWallet.ClassicAddress,
|
||||
},
|
||||
LoanID: loanID,
|
||||
}
|
||||
|
||||
flatLoanDeleteTx := loanDeleteTx.Flatten()
|
||||
loanDeleteTxJSON, _ := json.MarshalIndent(flatLoanDeleteTx, "", " ")
|
||||
fmt.Printf("%s\n", string(loanDeleteTxJSON))
|
||||
|
||||
// Sign, submit, and wait for deletion validation ----------------------
|
||||
fmt.Printf("\n=== Submitting LoanDelete transaction ===\n\n")
|
||||
deleteResponse, err := client.SubmitTxAndWait(flatLoanDeleteTx, &wstypes.SubmitOptions{
|
||||
Autofill: true,
|
||||
Wallet: &borrowerWallet,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if deleteResponse.Meta.TransactionResult != "tesSUCCESS" {
|
||||
fmt.Printf("Error: Unable to delete loan: %s\n", deleteResponse.Meta.TransactionResult)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Loan deleted successfully!\n")
|
||||
|
||||
// Verify loan deletion ----------------------
|
||||
fmt.Printf("\n=== Verifying Loan Deletion ===\n\n")
|
||||
_, err = client.GetLedgerEntry(&ledger.EntryRequest{
|
||||
Index: loanID,
|
||||
LedgerIndex: common.Validated,
|
||||
})
|
||||
if err != nil {
|
||||
if err.Error() == "entryNotFound" {
|
||||
fmt.Printf("Loan has been successfully removed from the XRP Ledger!\n")
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Warning: Loan still exists in the ledger.\n")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user