mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-04-29 15:37:48 +00:00
196 lines
6.6 KiB
Go
196 lines
6.6 KiB
Go
// 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/flag"
|
|
"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/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
|
|
activeFlags := []string{}
|
|
if flag.Contains(loanFlags, transaction.TfLoanDefault) {
|
|
activeFlags = append(activeFlags, "tfLoanDefault")
|
|
}
|
|
if flag.Contains(loanFlags, transaction.TfLoanImpair) {
|
|
activeFlags = append(activeFlags, "tfLoanImpair")
|
|
}
|
|
fmt.Printf("Final loan flags: %v\n", activeFlags)
|
|
}
|