mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-04-29 15:37:48 +00:00
211 lines
7.0 KiB
Go
211 lines
7.0 KiB
Go
// 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
|
|
}
|
|
}
|
|
}
|