Repository: Peersyst/xrpl-go
Pull request #113: Add the Credential transaction types
Author: login: florent-uzio
Labels: name: feature
description: Feature
name: xrpl
description: xrpl package
Created: 2025-03-11T09:02:54Z | Merged: 2025-03-19T08:51:11Z

# Title

## Description
This PR aims to add the Credential transaction types:
- CredentialCreate
- CredentialAccept
- CredentialDelete

I also added a new IsValidCredentialType function in the `common` directory and constants for the `CredentialType` length.

## Type of change
- [ ] Bug fix
- [X] New feature
- [ ] Breaking change
- [ ] Documentation update
- [ ] Refactoring

## Checklist:
- [X] My code follows the style guidelines of this project
- [X] I have performed a self-review of my own code
- [X] I have commented my code where needed
- [X] I have made corresponding changes to the documentation
- [X] My changes generate no new warnings
- [X] I have added tests that prove my fix is effective
- [X] New and existing unit tests pass locally with my changes

## Changes

- CredentialCreate
- CredentialAccept
- CredentialDelete

## Notes (optional)

Describe any additional notes here.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
	- Introduced full support for credential transactions, enabling creation, acceptance, and deletion of credentials on the ledger.
	- Added a dedicated ledger entry type for managing credential details, along with refined validations and error handling.
	- Implemented a transaction result indicator for successful operations.

- **Tests**
	- Expanded unit and integration tests to ensure robust behavior and validation for credential operations.

- **Documentation**
	- Updated the changelog to reflect the new credential transaction capabilities.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2fcd853..c05dbde2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,6 +19,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 - Updated `AffectedNode` type fields to be a pointer to allow nil values.
 
+### Added
+
+#### xrpl
+
+- Added support for all the Credential transaction types:
+  - CredentialCreate
+  - CredentialAccept
+  - CredentialDelete
+
 ### Fixed
 
 #### xrpl
@@ -53,6 +62,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
   - `WalletLocator`
   - `WalletSize`
   - `TransferRate`
+
 - Adds more tests for setting some `asf` flags in `AccountSet`.
 - Fixed `Transaction` field in `account_tx` response.
 - Fixed `Ledger` field in `ledger` response. LedgerIndex is now an uint32 instead of a string.
diff --git a/binary-codec/definitions/definitions.json b/binary-codec/definitions/definitions.json
index 797be9ce..f1d75817 100644
--- a/binary-codec/definitions/definitions.json
+++ b/binary-codec/definitions/definitions.json
@@ -30,34 +30,35 @@
     "Metadata": 10004
   },
   "LEDGER_ENTRY_TYPES": {
-    "Invalid": -1,
     "AccountRoot": 97,
-    "DirectoryNode": 100,
-    "RippleState": 114,
-    "Ticket": 84,
-    "SignerList": 83,
-    "Offer": 111,
-    "Bridge": 105,
-    "LedgerHashes": 104,
     "Amendments": 102,
-    "XChainOwnedClaimID": 113,
-    "XChainOwnedCreateAccountClaimID": 116,
-    "FeeSettings": 115,
-    "Escrow": 117,
-    "PayChannel": 120,
+    "AMM": 121,
+    "Any": -3,
+    "Bridge": 105,
     "Check": 67,
+    "Child": -2,
+    "Contract": 99,
+    "Credential": 129,
     "DepositPreauth": 112,
+    "DID": 73,
+    "DirectoryNode": 100,
+    "Escrow": 117,
+    "FeeSettings": 115,
+    "GeneratorMap": 103,
+    "Invalid": -1,
+    "LedgerHashes": 104,
     "NegativeUNL": 78,
-    "NFTokenPage": 80,
     "NFTokenOffer": 55,
-    "AMM": 121,
-    "DID": 73,
-    "Oracle": 128,
-    "Any": -3,
-    "Child": -2,
+    "NFTokenPage": 80,
     "Nickname": 110,
-    "Contract": 99,
-    "GeneratorMap": 103
+    "Offer": 111,
+    "Oracle": 128,
+    "PayChannel": 120,
+    "RippleState": 114,
+    "SignerList": 83,
+    "Ticket": 84,
+    "XChainOwnedClaimID": 113,
+    "XChainOwnedCreateAccountClaimID": 116
   },
   "FIELDS": [
     [
@@ -1070,6 +1071,16 @@
         "type": "UInt64"
       }
     ],
+    [
+      "SubjectNode",
+      {
+        "nth": 28,
+        "isVLEncoded": false,
+        "isSerialized": true,
+        "isSigningField": true,
+        "type": "UInt64"
+      }
+    ],
     [
       "EmailHash",
       {
@@ -1980,6 +1991,16 @@
         "type": "Blob"
       }
     ],
+    [
+      "CredentialType",
+      {
+        "nth": 31,
+        "isVLEncoded": true,
+        "isSerialized": true,
+        "isSigningField": true,
+        "type": "Blob"
+      }
+    ],
     [
       "Account",
       {
@@ -2180,6 +2201,16 @@
         "type": "Vector256"
       }
     ],
+    [
+      "CredentialIDs",
+      {
+        "nth": 5,
+        "isVLEncoded": true,
+        "isSerialized": true,
+        "isSigningField": true,
+        "type": "Vector256"
+      }
+    ],
     [
       "Paths",
       {
@@ -2260,6 +2291,16 @@
         "type": "XChainBridge"
       }
     ],
+    [
+      "Subject",
+      {
+        "nth": 24,
+        "isVLEncoded": true,
+        "isSerialized": true,
+        "isSigningField": true,
+        "type": "AccountID"
+      }
+    ],
     [
       "TransactionMetaData",
       {
@@ -2550,6 +2591,16 @@
         "type": "STObject"
       }
     ],
+    [
+      "Credential",
+      {
+        "nth": 33,
+        "isVLEncoded": false,
+        "isSerialized": true,
+        "isSigningField": true,
+        "type": "STObject"
+      }
+    ],
     [
       "Signers",
       {
@@ -2739,6 +2790,36 @@
         "isSigningField": true,
         "type": "STArray"
       }
+    ],
+    [
+      "AuthorizeCredentials",
+      {
+        "nth": 26,
+        "isVLEncoded": false,
+        "isSerialized": true,
+        "isSigningField": true,
+        "type": "STArray"
+      }
+    ],
+    [
+      "UnauthorizeCredentials",
+      {
+        "nth": 27,
+        "isVLEncoded": false,
+        "isSerialized": true,
+        "isSigningField": true,
+        "type": "STArray"
+      }
+    ],
+    [
+      "AcceptedCredentials",
+      {
+        "nth": 28,
+        "isVLEncoded": false,
+        "isSerialized": true,
+        "isSigningField": true,
+        "type": "STArray"
+      }
     ]
   ],
   "TRANSACTION_RESULTS": {
@@ -2847,6 +2928,7 @@
 
     "tesSUCCESS": 0,
 
+    "tecBAD_CREDENTIALS": 193,
     "tecCLAIM": 100,
     "tecPATH_PARTIAL": 101,
     "tecUNFUNDED_ADD": 102,
@@ -2926,56 +3008,59 @@
     "tecARRAY_TOO_LARGE": 191
   },
   "TRANSACTION_TYPES": {
-    "Invalid": -1,
-    "Payment": 0,
-    "EscrowCreate": 1,
-    "EscrowFinish": 2,
+    "AccountDelete": 21,
     "AccountSet": 3,
+    "AMMBid": 39,
+    "AMMCreate": 35,
+    "AMMDelete": 40,
+    "AMMDeposit": 36,
+    "AMMVote": 38,
+    "AMMWithdraw": 37,
+    "CheckCancel": 18,
+    "CheckCash": 17,
+    "CheckCreate": 16,
+    "Clawback": 30,
+    "Contract": 9,
+    "CredentialAccept": 59,
+    "CredentialCreate": 58,
+    "CredentialDelete": 60,
+    "DepositPreauth": 19,
+    "DIDDelete": 50,
+    "DIDSet": 49,
+    "EnableAmendment": 100,
     "EscrowCancel": 4,
-    "SetRegularKey": 5,
+    "EscrowCreate": 1,
+    "EscrowFinish": 2,
+    "Invalid": -1,
+    "NFTokenAcceptOffer": 29,
+    "NFTokenBurn": 26,
+    "NFTokenCancelOffer": 28,
+    "NFTokenCreateOffer": 27,
+    "NFTokenMint": 25,
     "NickNameSet": 6,
-    "OfferCreate": 7,
     "OfferCancel": 8,
-    "Contract": 9,
-    "TicketCreate": 10,
-    "TicketCancel": 11,
-    "SignerListSet": 12,
+    "OfferCreate": 7,
+    "OracleDelete": 52,
+    "OracleSet": 51,
+    "Payment": 0,
+    "PaymentChannelClaim": 15,
     "PaymentChannelCreate": 13,
     "PaymentChannelFund": 14,
-    "PaymentChannelClaim": 15,
-    "CheckCreate": 16,
-    "CheckCash": 17,
-    "CheckCancel": 18,
-    "DepositPreauth": 19,
-    "TrustSet": 20,
-    "AccountDelete": 21,
+    "SetFee": 101,
     "SetHook": 22,
-    "NFTokenMint": 25,
-    "NFTokenBurn": 26,
-    "NFTokenCreateOffer": 27,
-    "NFTokenCancelOffer": 28,
-    "NFTokenAcceptOffer": 29,
-    "Clawback": 30,
-    "AMMCreate": 35,
-    "AMMDeposit": 36,
-    "AMMWithdraw": 37,
-    "AMMVote": 38,
-    "AMMBid": 39,
-    "AMMDelete": 40,
-    "XChainCreateClaimID": 41,
-    "XChainCommit": 42,
-    "XChainClaim": 43,
+    "SetRegularKey": 5,
+    "SignerListSet": 12,
+    "TicketCancel": 11,
+    "TicketCreate": 10,
+    "TrustSet": 20,
+    "UNLModify": 102,
     "XChainAccountCreateCommit": 44,
-    "XChainAddClaimAttestation": 45,
     "XChainAddAccountCreateAttestation": 46,
-    "XChainModifyBridge": 47,
+    "XChainAddClaimAttestation": 45,
+    "XChainClaim": 43,
+    "XChainCommit": 42,
     "XChainCreateBridge": 48,
-    "DIDSet": 49,
-    "DIDDelete": 50,
-    "OracleSet": 51,
-    "OracleDelete": 52,
-    "EnableAmendment": 100,
-    "SetFee": 101,
-    "UNLModify": 102
+    "XChainCreateClaimID": 41,
+    "XChainModifyBridge": 47
   }
 }
diff --git a/examples/clients/clients.go b/examples/clients/clients.go
new file mode 100644
index 00000000..9520bdac
--- /dev/null
+++ b/examples/clients/clients.go
@@ -0,0 +1,55 @@
+package clients
+
+import (
+	"github.com/Peersyst/xrpl-go/xrpl/faucet"
+	"github.com/Peersyst/xrpl-go/xrpl/rpc"
+	"github.com/Peersyst/xrpl-go/xrpl/websocket"
+)
+
+// GetDevnetRpcClient returns a new rpc client for the devnet
+func GetDevnetRpcClient() *rpc.Client {
+	cfg, err := rpc.NewClientConfig(
+		"https://s.devnet.rippletest.net:51234",
+		rpc.WithFaucetProvider(faucet.NewDevnetFaucetProvider()),
+	)
+	if err != nil {
+		panic(err)
+	}
+
+	return rpc.NewClient(cfg)
+}
+
+// GetTestnetRpcClient returns a new rpc client for the testnet
+func GetTestnetRpcClient() *rpc.Client {
+	cfg, err := rpc.NewClientConfig(
+		"https://s.altnet.rippletest.net:51234",
+		rpc.WithFaucetProvider(faucet.NewTestnetFaucetProvider()),
+	)
+	if err != nil {
+		panic(err)
+	}
+
+	return rpc.NewClient(cfg)
+}
+
+// GetDevnetWebsocketClient returns a new websocket client for the devnet
+func GetDevnetWebsocketClient() *websocket.Client {
+	client := websocket.NewClient(
+		websocket.NewClientConfig().
+			WithHost("wss://s.devnet.rippletest.net:51233").
+			WithFaucetProvider(faucet.NewDevnetFaucetProvider()),
+	)
+
+	return client
+}
+
+// GetTestnetWebsocketClient returns a new websocket client for the testnet
+func GetTestnetWebsocketClient() *websocket.Client {
+	client := websocket.NewClient(
+		websocket.NewClientConfig().
+			WithHost("wss://s.altnet.rippletest.net:51233").
+			WithFaucetProvider(faucet.NewTestnetFaucetProvider()),
+	)
+
+	return client
+}
diff --git a/examples/clients/submission.go b/examples/clients/submission.go
new file mode 100644
index 00000000..5adc1095
--- /dev/null
+++ b/examples/clients/submission.go
@@ -0,0 +1,53 @@
+package clients
+
+import (
+	"fmt"
+
+	requests "github.com/Peersyst/xrpl-go/xrpl/queries/transactions"
+	transactions "github.com/Peersyst/xrpl-go/xrpl/transaction"
+	"github.com/Peersyst/xrpl-go/xrpl/wallet"
+)
+
+type SubmittableTransaction interface {
+	TxType() transactions.TxType
+	Flatten() transactions.FlatTransaction
+}
+
+// Client interface that both RPC and WebSocket clients must implement
+type TransactionClient interface {
+	Autofill(tx *transactions.FlatTransaction) error
+	SubmitAndWait(txBlob string, failHard bool) (*requests.TxResponse, error)
+}
+
+// SubmitAndWait submits a transaction and waits for it to be included in a validated ledger
+func SubmitAndWait(client TransactionClient, txn SubmittableTransaction, wallet wallet.Wallet) {
+	fmt.Println()
+	fmt.Printf("⏳ Submitting %s transaction...\n", txn.TxType())
+
+	flattenedTx := txn.Flatten()
+
+	err := client.Autofill(&flattenedTx)
+	if err != nil {
+		fmt.Printf("❌ Error autofilling %s transaction: %s\n", txn.TxType(), err)
+		fmt.Println()
+		return
+	}
+
+	txBlob, _, err := wallet.Sign(flattenedTx)
+	if err != nil {
+		fmt.Printf("❌ Error signing %s transaction: %s\n", txn.TxType(), err)
+		fmt.Println()
+		return
+	}
+
+	response, err := client.SubmitAndWait(txBlob, false)
+	if err != nil {
+		fmt.Printf("❌ Error submitting %s transaction: %s\n", txn.TxType(), err)
+		fmt.Println()
+		return
+	}
+
+	fmt.Printf("✅ %s transaction submitted\n", txn.TxType())
+	fmt.Printf("🌐 Hash: %s\n", response.Hash.String())
+	fmt.Println()
+}
diff --git a/examples/credential/rpc/main.go b/examples/credential/rpc/main.go
new file mode 100644
index 00000000..4b62f19b
--- /dev/null
+++ b/examples/credential/rpc/main.go
@@ -0,0 +1,109 @@
+package main
+
+import (
+	"encoding/hex"
+	"fmt"
+	"time"
+
+	"github.com/Peersyst/xrpl-go/examples/clients"
+	"github.com/Peersyst/xrpl-go/pkg/crypto"
+	rippleTime "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"
+)
+
+func main() {
+	// As of February 2025, Credential is only available on Devnet.
+	client := clients.GetDevnetRpcClient()
+
+	// Configure wallets
+
+	// Issuer
+	fmt.Println("⏳ Setting up credential issuer wallet...")
+	issuer, err := wallet.New(crypto.ED25519())
+	if err != nil {
+		fmt.Printf("❌ Error creating issuer wallet: %s\n", err)
+		return
+	}
+
+	err = client.FundWallet(&issuer)
+	if err != nil {
+		fmt.Printf("❌ Error funding issuer wallet: %s\n", err)
+		return
+	}
+	fmt.Printf("✅ Issuer wallet funded: %s\n", issuer.ClassicAddress)
+
+	// -----------------------------------------------------
+
+	// Subject (destination)
+	fmt.Println("⏳ Setting up Subject wallet...")
+	subjectWallet, err := wallet.New(crypto.ED25519())
+	if err != nil {
+		fmt.Printf("❌ Error creating subject wallet: %s\n", err)
+		return
+	}
+
+	err = client.FundWallet(&subjectWallet)
+	if err != nil {
+		fmt.Printf("❌ Error funding subject wallet: %s\n", err)
+		return
+	}
+	fmt.Printf("✅ Subject wallet funded: %s\n", subjectWallet.ClassicAddress)
+
+	// -----------------------------------------------------
+
+	// Creating the CredentialCreate transaction
+	fmt.Println("⏳ Creating CredentialCreate transaction...")
+
+	expiration, err := rippleTime.IsoTimeToRippleTime(time.Now().Add(time.Hour * 24).Format(time.RFC3339))
+	credentialType := types.CredentialType("6D795F63726564656E7469616C")
+
+	if err != nil {
+		fmt.Printf("❌ Error converting expiration to ripple time: %s\n", err)
+		return
+	}
+
+	txn := &transaction.CredentialCreate{
+		BaseTx: transaction.BaseTx{
+			Account: types.Address(issuer.ClassicAddress),
+		},
+		CredentialType: credentialType,
+		Subject:        types.Address(subjectWallet.ClassicAddress),
+		Expiration:     uint32(expiration),
+		URI:            hex.EncodeToString([]byte("https://example.com")),
+	}
+
+	clients.SubmitAndWait(client, txn, issuer)
+
+	// -----------------------------------------------------
+
+	// Creating the CredentialAccept transaction
+	fmt.Println("⏳ Creating CredentialAccept transaction...")
+
+	acceptTxn := &transaction.CredentialAccept{
+		BaseTx: transaction.BaseTx{
+			Account: types.Address(subjectWallet.ClassicAddress),
+		},
+		CredentialType: credentialType,
+		Issuer:         types.Address(issuer.ClassicAddress),
+	}
+
+	clients.SubmitAndWait(client, acceptTxn, subjectWallet)
+
+	// -----------------------------------------------------
+
+	// Creating the CredentialDelete transaction
+	fmt.Println("⏳ Creating CredentialDelete transaction...")
+
+	deleteTxn := &transaction.CredentialDelete{
+		BaseTx: transaction.BaseTx{
+			Account: types.Address(issuer.ClassicAddress),
+		},
+		CredentialType: credentialType,
+		Issuer:         types.Address(issuer.ClassicAddress),
+		Subject:        types.Address(subjectWallet.ClassicAddress),
+	}
+
+	clients.SubmitAndWait(client, deleteTxn, issuer)
+}
diff --git a/examples/credential/ws/main.go b/examples/credential/ws/main.go
new file mode 100644
index 00000000..c376f185
--- /dev/null
+++ b/examples/credential/ws/main.go
@@ -0,0 +1,122 @@
+package main
+
+import (
+	"encoding/hex"
+	"fmt"
+	"time"
+
+	"github.com/Peersyst/xrpl-go/examples/clients"
+	"github.com/Peersyst/xrpl-go/pkg/crypto"
+	rippleTime "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"
+)
+
+func main() {
+
+	fmt.Println("⏳ Setting up client...")
+
+	client := clients.GetDevnetWebsocketClient()
+	fmt.Println("Connecting to server...")
+	if err := client.Connect(); err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	fmt.Println("✅ Client configured!")
+	fmt.Println()
+
+	fmt.Printf("Connection: %t", client.IsConnected())
+	fmt.Println()
+
+	// Configure wallets
+
+	// Issuer
+	fmt.Println("⏳ Setting up credential issuer wallet...")
+	issuer, err := wallet.New(crypto.ED25519())
+	if err != nil {
+		fmt.Printf("❌ Error creating issuer wallet: %s\n", err)
+		return
+	}
+
+	err = client.FundWallet(&issuer)
+	if err != nil {
+		fmt.Printf("❌ Error funding issuer wallet: %s\n", err)
+		return
+	}
+	fmt.Printf("✅ Issuer wallet funded: %s\n", issuer.ClassicAddress)
+
+	// -----------------------------------------------------
+
+	// Subject (destination)
+	fmt.Println("⏳ Setting up Subject wallet...")
+	subjectWallet, err := wallet.New(crypto.ED25519())
+	if err != nil {
+		fmt.Printf("❌ Error creating subject wallet: %s\n", err)
+		return
+	}
+
+	err = client.FundWallet(&subjectWallet)
+	if err != nil {
+		fmt.Printf("❌ Error funding subject wallet: %s\n", err)
+		return
+	}
+	fmt.Printf("✅ Subject wallet funded: %s\n", subjectWallet.ClassicAddress)
+
+	// -----------------------------------------------------
+
+	// Creating the CredentialCreate transaction
+	fmt.Println("⏳ Creating CredentialCreate transaction...")
+
+	expiration, err := rippleTime.IsoTimeToRippleTime(time.Now().Add(time.Hour * 24).Format(time.RFC3339))
+	credentialType := types.CredentialType("6D795F63726564656E7469616C")
+
+	if err != nil {
+		fmt.Printf("❌ Error converting expiration to ripple time: %s\n", err)
+		return
+	}
+
+	txn := &transaction.CredentialCreate{
+		BaseTx: transaction.BaseTx{
+			Account: types.Address(issuer.ClassicAddress),
+		},
+		CredentialType: credentialType,
+		Subject:        types.Address(subjectWallet.ClassicAddress),
+		Expiration:     uint32(expiration),
+		URI:            hex.EncodeToString([]byte("https://example.com")),
+	}
+
+	clients.SubmitAndWait(client, txn, issuer)
+
+	// -----------------------------------------------------
+
+	// Creating the CredentialAccept transaction
+	fmt.Println("⏳ Creating CredentialAccept transaction...")
+
+	acceptTxn := &transaction.CredentialAccept{
+		BaseTx: transaction.BaseTx{
+			Account: types.Address(subjectWallet.ClassicAddress),
+		},
+		CredentialType: credentialType,
+		Issuer:         types.Address(issuer.ClassicAddress),
+	}
+
+	clients.SubmitAndWait(client, acceptTxn, subjectWallet)
+
+	// -----------------------------------------------------
+
+	// Creating the CredentialDelete transaction
+	fmt.Println("⏳ Creating CredentialDelete transaction...")
+
+	deleteTxn := &transaction.CredentialDelete{
+		BaseTx: transaction.BaseTx{
+			Account: types.Address(issuer.ClassicAddress),
+		},
+		CredentialType: credentialType,
+		Issuer:         types.Address(issuer.ClassicAddress),
+		Subject:        types.Address(subjectWallet.ClassicAddress),
+	}
+
+	clients.SubmitAndWait(client, deleteTxn, issuer)
+}
diff --git a/xrpl/ledger-entry-types/credential.go b/xrpl/ledger-entry-types/credential.go
new file mode 100644
index 00000000..fe6f6d13
--- /dev/null
+++ b/xrpl/ledger-entry-types/credential.go
@@ -0,0 +1,50 @@
+package ledger
+
+import "github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+
+const (
+	// If enabled, the subject of the credential has accepted the credential.
+	// Otherwise, the issuer created the credential but the subject has not yet accepted it, meaning it is not yet valid.
+	lsfAccepted uint32 = 0x00010000
+)
+
+// A Credential entry represents a credential, which contains an attestation about a subject account from a credential issuer account.
+// The meaning of the attestation is defined by the issuer.
+// Requires the Credentials amendment to be enabled: https://xrpl.org/resources/known-amendments#credentials
+type Credential struct {
+	// The unique ID for this ledger entry. In JSON, this field is represented with different names depending on the
+	// context and API method. (Note, even though this is specified as "optional" in the code, every ledger entry
+	// should have one unless it's legacy data from very early in the XRP Ledger's history.)
+	Index types.Hash256 `json:"index,omitempty"`
+	// The value 0x0044, mapped to the string Credential, indicates that this is a Credential ledger entry.
+	LedgerEntryType EntryType
+	// Set of bit-flags for this ledger entry.
+	Flags uint32
+	// Arbitrary data defining the type of credential this entry represents. The minimum length is 1 byte and the maximum length is 64 bytes.
+	CredentialType types.CredentialType
+	// Time after which the credential is expired, in seconds since the Ripple Epoch.
+	Expiration uint32 `json:",omitempty"`
+	// The account that issued the credential.
+	Issuer types.Address
+	// A hint indicating which page of the issuer's directory links to this entry, in case the directory consists of multiple pages.
+	IssuerNode string
+	// The identifying hash of the transaction that most recently modified this entry.
+	PreviousTxnID types.Hash256
+	// The index of the ledger that contains the transaction that most recently modified this entry.
+	PreviousTxnLgrSeq uint32
+	// The account that this credential is for.
+	Subject types.Address
+	// A hint indicating which page of the subject's owner directory links to this entry, in case the directory consists of multiple pages.
+	SubjectNode string
+	// Arbitrary additional data about the credential, for example a URL where a W3C-formatted Verifiable Credential can be retrieved.
+	URI string `json:",omitempty"`
+}
+
+func (*Credential) EntryType() EntryType {
+	return CredentialEntry
+}
+
+// SetLsfAccepted sets the one owner count flag.
+func (c *Credential) SetLsfAccepted() {
+	c.Flags |= lsfAccepted
+}
diff --git a/xrpl/ledger-entry-types/credential_test.go b/xrpl/ledger-entry-types/credential_test.go
new file mode 100644
index 00000000..7a90a854
--- /dev/null
+++ b/xrpl/ledger-entry-types/credential_test.go
@@ -0,0 +1,66 @@
+package ledger
+
+import (
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/testutil"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCredential_EntryType(t *testing.T) {
+	credential := &Credential{}
+	require.Equal(t, credential.EntryType(), CredentialEntry)
+}
+
+func TestCredential_SetLsfAccepted(t *testing.T) {
+	credential := &Credential{}
+	credential.SetLsfAccepted()
+	require.Equal(t, credential.Flags, lsfAccepted)
+}
+
+func TestCredential_Flatten(t *testing.T) {
+	tests := []struct {
+		name       string
+		credential *Credential
+		expected   string
+	}{
+		{
+			name: "pass - valid Credential",
+			credential: &Credential{
+				Index:             types.Hash256("A738A1E6E8505E1FC77BBB9FEF84FF9A9C609F2739E0F9573CDD6367100A0AA9"),
+				LedgerEntryType:   CredentialEntry,
+				Flags:             lsfAccepted,
+				CredentialType:    types.CredentialType("6D795F63726564656E7469616C"),
+				Subject:           types.Address("rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8"),
+				Issuer:            types.Address("ra5nK24KXen9AHvsdFTKHSANinZseWnPcX"),
+				IssuerNode:        "0000000000000000",
+				PreviousTxnID:     types.Hash256("8089451B193AAD110ACED3D62BE79BB523658545E6EE8B7BB0BE573FED9BCBFB"),
+				PreviousTxnLgrSeq: 234644,
+				SubjectNode:       "0000000000000000",
+				URI:               "987654321",
+			},
+			expected: `{
+	"index": "A738A1E6E8505E1FC77BBB9FEF84FF9A9C609F2739E0F9573CDD6367100A0AA9",
+	"LedgerEntryType": "Credential",
+	"Flags": 65536,
+	"CredentialType": "6D795F63726564656E7469616C",
+	"Issuer": "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
+	"IssuerNode": "0000000000000000",
+	"PreviousTxnID": "8089451B193AAD110ACED3D62BE79BB523658545E6EE8B7BB0BE573FED9BCBFB",
+	"PreviousTxnLgrSeq": 234644,
+	"Subject": "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
+	"SubjectNode": "0000000000000000",
+	"URI": "987654321"
+}`,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if err := testutil.SerializeAndDeserialize(t, test.credential, test.expected); err != nil {
+				t.Error(err)
+			}
+		})
+	}
+}
diff --git a/xrpl/ledger-entry-types/ledger_object.go b/xrpl/ledger-entry-types/ledger_object.go
index 54db2c3d..f0ca7101 100644
--- a/xrpl/ledger-entry-types/ledger_object.go
+++ b/xrpl/ledger-entry-types/ledger_object.go
@@ -13,6 +13,7 @@ const (
 	AMMEntry                             EntryType = "AMM"
 	BridgeEntry                          EntryType = "Bridge"
 	CheckEntry                           EntryType = "Check"
+	CredentialEntry                      EntryType = "Credential"
 	DepositPreauthObjEntry               EntryType = "DepositPreauth"
 	DIDEntry                             EntryType = "DID"
 	DirectoryNodeEntry                   EntryType = "DirectoryNode"
@@ -54,6 +55,8 @@ func EmptyLedgerObject(t string) (Object, error) {
 		return &Bridge{}, nil
 	case CheckEntry:
 		return &Check{}, nil
+	case CredentialEntry:
+		return &Credential{}, nil
 	case DepositPreauthObjEntry:
 		return &DepositPreauthObj{}, nil
 	case DIDEntry:
@@ -113,6 +116,8 @@ func UnmarshalLedgerObject(data []byte) (Object, error) {
 		o = &Bridge{}
 	case CheckEntry:
 		o = &Check{}
+	case CredentialEntry:
+		o = &Credential{}
 	case DepositPreauthObjEntry:
 		o = &DepositPreauthObj{}
 	case DIDEntry:
@@ -146,6 +151,7 @@ func UnmarshalLedgerObject(data []byte) (Object, error) {
 	case XChainOwnedClaimIDEntry:
 		o = &XChainOwnedClaimID{}
 	case XChainOwnedCreateAccountClaimIDEntry:
+		o = &XChainOwnedCreateAccountClaimID{}
 	default:
 		return nil, fmt.Errorf("unsupported ledger object of type %s", h.LedgerEntryType)
 	}
diff --git a/xrpl/testutil/integration/runner.go b/xrpl/testutil/integration/runner.go
index 7cedc034..61e710ec 100644
--- a/xrpl/testutil/integration/runner.go
+++ b/xrpl/testutil/integration/runner.go
@@ -74,7 +74,7 @@ func (r *Runner) TestTransaction(flatTx *transaction.FlatTransaction, signer *wa
 	}
 
 	require.NoError(r.t, err)
-	require.Equal(r.t, tx.EngineResult, expectedEngineResult)
+	require.Equal(r.t, expectedEngineResult, tx.EngineResult)
 	require.Equal(r.t, hash, tx.Tx["hash"].(string))
 
 	return tx, nil
@@ -89,7 +89,7 @@ func (r *Runner) TestMultisigTransaction(blob string, expectedEngineResult strin
 	}
 
 	require.NoError(r.t, err)
-	require.Equal(r.t, tx.EngineResult, expectedEngineResult)
+	require.Equal(r.t, expectedEngineResult, tx.EngineResult)
 
 	return tx, nil
 }
diff --git a/xrpl/transaction/credential_accept.go b/xrpl/transaction/credential_accept.go
new file mode 100644
index 00000000..0fb2b693
--- /dev/null
+++ b/xrpl/transaction/credential_accept.go
@@ -0,0 +1,61 @@
+package transaction
+
+import (
+	addresscodec "github.com/Peersyst/xrpl-go/address-codec"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+)
+
+// A CredentialCreate transaction creates a credential in the ledger.
+// The issuer of the credential uses this transaction to provisionally issue a credential.
+// The credential is not valid until the subject of the credential accepts it with a CredentialAccept transaction.
+type CredentialAccept struct {
+	// Base transaction fields
+	BaseTx
+
+	// The address of the issuer that created the credential.
+	Issuer types.Address
+
+	// Arbitrary data defining the type of credential. The minimum size is 1 byte and the maximum is 64 bytes.
+	CredentialType types.CredentialType
+}
+
+// TxType implements the TxType method for the CredentialCreate struct.
+func (*CredentialAccept) TxType() TxType {
+	return CredentialAcceptTx
+}
+
+// Flatten implements the Flatten method for the CredentialCreate struct.
+func (c *CredentialAccept) Flatten() FlatTransaction {
+	flattened := c.BaseTx.Flatten()
+
+	flattened["TransactionType"] = c.TxType().String()
+
+	if c.Issuer != "" {
+		flattened["Issuer"] = c.Issuer.String()
+	}
+
+	if c.CredentialType != "" {
+		flattened["CredentialType"] = c.CredentialType.String()
+	}
+
+	return flattened
+}
+
+// Validate implements the Validate method for the CredentialCreate struct.
+func (c *CredentialAccept) Validate() (bool, error) {
+	// validate the base transaction
+	_, err := c.BaseTx.Validate()
+	if err != nil {
+		return false, err
+	}
+
+	if !addresscodec.IsValidAddress(c.Issuer.String()) {
+		return false, ErrInvalidIssuer
+	}
+
+	if !c.CredentialType.IsValid() {
+		return false, ErrInvalidCredentialType
+	}
+
+	return true, nil
+}
diff --git a/xrpl/transaction/credential_accept_test.go b/xrpl/transaction/credential_accept_test.go
new file mode 100644
index 00000000..b39e8be4
--- /dev/null
+++ b/xrpl/transaction/credential_accept_test.go
@@ -0,0 +1,177 @@
+package transaction
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCredentialAccept_TxType(t *testing.T) {
+	tx := &CredentialAccept{}
+	require.Equal(t, CredentialAcceptTx, tx.TxType())
+}
+
+func TestCredentialAccept_Flatten(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *CredentialAccept
+		expected FlatTransaction
+	}{
+		{
+			name: "pass - valid CredentialAccept",
+			input: &CredentialAccept{
+				BaseTx: BaseTx{
+					Account:         "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
+					TransactionType: CredentialAcceptTx,
+					Fee:             types.XRPCurrencyAmount(1),
+					Sequence:        1234,
+				},
+				Issuer:         "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
+				CredentialType: "6D795F63726564656E7469616C", // "my_credential" in hex
+			},
+			expected: FlatTransaction{
+				"Account":         "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
+				"TransactionType": "CredentialAccept",
+				"Fee":             "1",
+				"Sequence":        uint32(1234),
+				"Issuer":          "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
+				"CredentialType":  "6D795F63726564656E7469616C",
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			flattened := tt.input.Flatten()
+			require.Equal(t, tt.expected, flattened, "Flatten result differs from expected")
+		})
+	}
+}
+
+func TestCredentialAccept_Validate(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *CredentialAccept
+		expected bool
+	}{
+		{
+			name: "pass - valid CredentialAccept",
+			input: &CredentialAccept{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: "6D795F63726564656E7469616C",
+			},
+			expected: true,
+		},
+		{
+			name: "fail - CredentialAccept with an invalid Issuer",
+			input: &CredentialAccept{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Issuer:         "invalid_address",
+				CredentialType: types.CredentialType("6D795F63726564656E7469616C"),
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialAccept with an invalid CredentialType (empty)",
+			input: &CredentialAccept{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType(""),
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialAccept with an invalid CredentialType (not hex)",
+			input: &CredentialAccept{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType("not hexadecimal value"),
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialCreate with an invalid CredentialType (too long)",
+			input: &CredentialAccept{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType(strings.Repeat("0", types.MaxCredentialTypeLength+1)),
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialCreate with an invalid CredentialType (too short)",
+			input: &CredentialAccept{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType(strings.Repeat("0", types.MinCredentialTypeLength-1)),
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialAccept with an invalid BaseTx",
+			input: &CredentialAccept{
+				BaseTx: BaseTx{
+					Account:         "invalid",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType("6D795F63726564656E7469616C"),
+			},
+			expected: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			valid, err := tt.input.Validate()
+			require.Equal(t, tt.expected, valid, "Validation result mismatch")
+			if tt.expected {
+				require.NoError(t, err, "Expected no error for valid case")
+			} else {
+				require.Error(t, err, "Expected error for invalid case")
+			}
+		})
+	}
+}
diff --git a/xrpl/transaction/credential_create.go b/xrpl/transaction/credential_create.go
new file mode 100644
index 00000000..e0e4049a
--- /dev/null
+++ b/xrpl/transaction/credential_create.go
@@ -0,0 +1,96 @@
+package transaction
+
+import (
+	"errors"
+
+	addresscodec "github.com/Peersyst/xrpl-go/address-codec"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+)
+
+var (
+	// ErrInvalidCredentialURI is returned when the URI field does not meet the maximum allowed hex-encoded length of 512 characters (256 bytes).
+	ErrInvalidCredentialURI = errors.New("credential create: invalid URI, must have a maximum hex string length of 512 characters (256 bytes)")
+)
+
+const (
+	// Minimum length of a credential type is 1 byte (1 byte = 2 hex characters).
+	MinURILength = 2
+
+	// Maximum of 256 bytes for the URI field (1 byte = 2 hex characters)
+	MaxURILength = 512
+)
+
+// A CredentialCreate transaction creates a credential in the ledger.
+// The issuer of the credential uses this transaction to provisionally issue a credential.
+// The credential is not valid until the subject of the credential accepts it with a CredentialAccept transaction.
+type CredentialCreate struct {
+	// Base transaction fields
+	BaseTx
+
+	// The subject of the credential (the address that will receive the credential).
+	Subject types.Address
+
+	// Arbitrary data defining the type of credential this entry represents. The minimum length is 1 byte and the maximum length is 64 bytes.
+	CredentialType types.CredentialType
+
+	// Time after which this credential expires, in seconds since the Ripple Epoch.
+	// The Ripple Epoch is January 1, 2000 (00:00 UTC), represented as Unix time 946684800.
+	// https://xrpl.org/docs/references/protocol/data-types/basic-data-types#specifying-time
+	Expiration uint32 `json:",omitempty"`
+
+	// Arbitrary additional data about the credential, such as the URL where users can look up an associated Verifiable Credential document. If present, the minimum length is 1 byte and the maximum is 256 bytes.
+	URI string `json:",omitempty"`
+}
+
+// TxType implements the TxType method for the CredentialCreate struct.
+func (*CredentialCreate) TxType() TxType {
+	return CredentialCreateTx
+}
+
+// Flatten implements the Flatten method for the CredentialCreate struct.
+func (c *CredentialCreate) Flatten() FlatTransaction {
+	flattened := c.BaseTx.Flatten()
+
+	flattened["TransactionType"] = c.TxType().String()
+
+	if c.Subject != "" {
+		flattened["Subject"] = c.Subject.String()
+	}
+
+	if c.CredentialType != "" {
+		flattened["CredentialType"] = c.CredentialType.String()
+	}
+
+	if c.Expiration != 0 {
+		flattened["Expiration"] = c.Expiration
+	}
+
+	if c.URI != "" {
+		flattened["URI"] = c.URI
+	}
+
+	return flattened
+}
+
+// Validate implements the Validate method for the CredentialCreate struct.
+func (c *CredentialCreate) Validate() (bool, error) {
+	// validate the base transaction
+	_, err := c.BaseTx.Validate()
+	if err != nil {
+		return false, err
+	}
+
+	if !addresscodec.IsValidAddress(c.Subject.String()) {
+		return false, ErrInvalidSubject
+	}
+
+	if !c.CredentialType.IsValid() {
+		return false, ErrInvalidCredentialType
+	}
+
+	if c.URI != "" && (len(c.URI) < MinURILength || len(c.URI) > MaxURILength) {
+		return false, ErrInvalidCredentialURI
+	}
+
+	return true, nil
+}
diff --git a/xrpl/transaction/credential_create_test.go b/xrpl/transaction/credential_create_test.go
new file mode 100644
index 00000000..8e041375
--- /dev/null
+++ b/xrpl/transaction/credential_create_test.go
@@ -0,0 +1,281 @@
+package transaction
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCredentialCreate_TxType(t *testing.T) {
+	tx := &CredentialCreate{}
+	require.Equal(t, CredentialCreateTx, tx.TxType())
+}
+
+func TestCredentialCreate_Flatten(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *CredentialCreate
+		expected FlatTransaction
+	}{
+		{
+			name: "pass - valid CredentialCreate",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
+					TransactionType: CredentialCreateTx,
+					Fee:             types.XRPCurrencyAmount(1),
+					Sequence:        1234,
+				},
+				Subject:        "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
+				Expiration:     123456,
+				CredentialType: "6D795F63726564656E7469616C",                                   // "my_credential" in hex
+				URI:            "687474703A2F2F636F6D70616E792E636F6D2F63726564656E7469616C73", // "http://company.com/credentials" in hex
+			},
+			expected: FlatTransaction{
+				"Account":         "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX",
+				"TransactionType": "CredentialCreate",
+				"Fee":             "1",
+				"Sequence":        uint32(1234),
+				"Subject":         "rsUiUMpnrgxQp24dJYZDhmV4bE3aBtQyt8",
+				"Expiration":      uint32(123456),
+				"CredentialType":  "6D795F63726564656E7469616C",
+				"URI":             "687474703A2F2F636F6D70616E792E636F6D2F63726564656E7469616C73",
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			flattened := tt.input.Flatten()
+			require.Equal(t, tt.expected, flattened)
+		})
+	}
+}
+
+func TestCredentialCreate_Validate(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *CredentialCreate
+		expected bool
+	}{
+		{
+			name: "pass - valid CredentialCreate",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: "6D795F63726564656E7469616C",
+				Expiration:     123456,
+				URI:            "687474703A2F2F636F6D70616E792E636F6D2F63726564656E7469616C73",
+			},
+			expected: true,
+		},
+		{
+			name: "pass - valid CredentialCreate with required fields only",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: "6D795F63726564656E7469616C",
+			},
+			expected: true,
+		},
+		{
+			name: "pass - valid CredentialCreate without URI",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType("6D795F63726564656E7469616C"),
+				Expiration:     123456,
+			},
+			expected: true,
+		},
+		{
+			name: "pass - valid CredentialCreate without URI",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType("6D795F63726564656E7469616C"),
+				Expiration:     123456,
+			},
+			expected: true,
+		},
+		{
+			name: "fail - CredentialCreate with an invalid Subject",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "invalid_address",
+				CredentialType: types.CredentialType("6D795F63726564656E7469616C"),
+				Expiration:     123456,
+			},
+			expected: false,
+		},
+		{
+			name: "pass - CredentialCreate with an Expiration of 0 (won't be pass to the flatten transaction)",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType("6D795F63726564656E7469616C"),
+				Expiration:     0,
+			},
+			expected: true,
+		},
+		{
+			name: "fail - CredentialCreate with an invalid CredentialType (empty)",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType(""),
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialCreate with an invalid CredentialType (not hex)",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType("not hexadecimal value"),
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialCreate with an invalid CredentialType (too long)",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType(strings.Repeat("0", types.MaxCredentialTypeLength+1)),
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialCreate with an invalid CredentialType (too short)",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType(strings.Repeat("0", types.MinCredentialTypeLength-1)),
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialCreate with an invalid BaseTx",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "invalid",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: types.CredentialType("6D795F63726564656E7469616C"),
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialCreate with a URI that is too short",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: "6D795F63726564656E7469616C",
+				Expiration:     123456,
+				URI:            "0",
+			},
+			expected: false,
+		},
+		{
+			name: "fail - CredentialCreate with a URI that is too long",
+			input: &CredentialCreate{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: "AMMWithdraw",
+					Fee:             types.XRPCurrencyAmount(10),
+					Flags:           1048576,
+					Sequence:        10,
+				},
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				CredentialType: "6D795F63726564656E7469616C",
+				Expiration:     123456,
+				URI:            strings.Repeat("0", 513),
+			},
+			expected: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			valid, err := tt.input.Validate()
+			require.Equal(t, tt.expected, valid)
+			if tt.expected {
+				require.NoError(t, err)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
diff --git a/xrpl/transaction/credential_delete.go b/xrpl/transaction/credential_delete.go
new file mode 100644
index 00000000..0f9784ea
--- /dev/null
+++ b/xrpl/transaction/credential_delete.go
@@ -0,0 +1,70 @@
+package transaction
+
+import (
+	addresscodec "github.com/Peersyst/xrpl-go/address-codec"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+)
+
+// A CredentialDelete transaction removes a credential from the ledger, effectively revoking it.
+// Users may also want to delete an unwanted credential to reduce their reserve requirement.
+type CredentialDelete struct {
+	BaseTx
+
+	// Arbitrary data defining the type of credential this entry represents. The minimum length is 1 byte and the maximum length is 64 bytes.
+	CredentialType types.CredentialType
+
+	// The subject of the credential to delete. If omitted, use the Account (sender of the transaction) as the subject of the credential.
+	Subject types.Address `json:",omitempty"`
+
+	// The issuer of the credential to delete. If omitted, use the Account (sender of the transaction) as the issuer of the credential.
+	Issuer types.Address `json:",omitempty"`
+}
+
+// TxType returns the type of the CredentialDelete transaction.
+func (*CredentialDelete) TxType() TxType {
+	return CredentialDeleteTx
+}
+
+// Flatten returns a flattened version of the CredentialDelete transaction.
+func (c *CredentialDelete) Flatten() FlatTransaction {
+	flattened := c.BaseTx.Flatten()
+
+	flattened["TransactionType"] = c.TxType().String()
+
+	if c.CredentialType != "" {
+		flattened["CredentialType"] = c.CredentialType.String()
+	}
+
+	if c.Subject != "" {
+		flattened["Subject"] = c.Subject.String()
+	}
+
+	if c.Issuer != "" {
+		flattened["Issuer"] = c.Issuer.String()
+	}
+
+	return flattened
+}
+
+// Validate validates the CredentialDelete transaction.
+func (c *CredentialDelete) Validate() (bool, error) {
+	// validate the base transaction
+	_, err := c.BaseTx.Validate()
+	if err != nil {
+		return false, err
+	}
+
+	if !c.CredentialType.IsValid() {
+		return false, ErrInvalidCredentialType
+	}
+
+	if c.Subject != "" && !addresscodec.IsValidAddress(c.Subject.String()) {
+		return false, ErrInvalidSubject
+	}
+
+	if c.Issuer != "" && !addresscodec.IsValidAddress(c.Issuer.String()) {
+		return false, ErrInvalidIssuer
+	}
+
+	return true, nil
+}
diff --git a/xrpl/transaction/credential_delete_test.go b/xrpl/transaction/credential_delete_test.go
new file mode 100644
index 00000000..45782969
--- /dev/null
+++ b/xrpl/transaction/credential_delete_test.go
@@ -0,0 +1,146 @@
+package transaction
+
+import (
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCredentialDelete_TxType(t *testing.T) {
+	tx := &CredentialDelete{}
+	require.Equal(t, CredentialDeleteTx, tx.TxType())
+}
+
+func TestCredentialDelete_Flatten(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *CredentialDelete
+		expected FlatTransaction
+	}{
+		{
+			name: "pass - valid CredentialDelete",
+			input: &CredentialDelete{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: CredentialDeleteTx,
+					Fee:             types.XRPCurrencyAmount(10),
+					Sequence:        10,
+				},
+				CredentialType: "6D795F63726564656E7469616C",
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+			},
+			expected: FlatTransaction{
+				"Account":         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+				"TransactionType": "CredentialDelete",
+				"Fee":             "10",
+				"Sequence":        uint32(10),
+				"CredentialType":  "6D795F63726564656E7469616C",
+				"Subject":         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				"Issuer":          "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+			},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			flattened := test.input.Flatten()
+			require.Equal(t, test.expected, flattened)
+		})
+	}
+}
+
+func TestCredentialDelete_Validate(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    *CredentialDelete
+		expected bool
+	}{
+		{
+			name: "pass - valid CredentialDelete",
+			input: &CredentialDelete{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: CredentialDeleteTx,
+					Fee:             types.XRPCurrencyAmount(10),
+					Sequence:        10,
+				},
+				CredentialType: "6D795F63726564656E7469616C",
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+			},
+			expected: true,
+		},
+		{
+			name: "fail - invalid CredentialType",
+			input: &CredentialDelete{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: CredentialDeleteTx,
+					Fee:             types.XRPCurrencyAmount(10),
+					Sequence:        10,
+				},
+				CredentialType: "invalid",
+			},
+			expected: false,
+		},
+		{
+			name: "fail - invalid Subject",
+			input: &CredentialDelete{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: CredentialDeleteTx,
+					Fee:             types.XRPCurrencyAmount(10),
+					Sequence:        10,
+				},
+				CredentialType: "6D795F63726564656E7469616C",
+				Subject:        "invalid",
+				Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+			},
+			expected: false,
+		},
+		{
+			name: "fail - invalid Issuer",
+			input: &CredentialDelete{
+				BaseTx: BaseTx{
+					Account:         "rJVUeRqDFNs2xqA7ncVE6ZoAhPUoaJJSQm",
+					TransactionType: CredentialDeleteTx,
+					Fee:             types.XRPCurrencyAmount(10),
+					Sequence:        10,
+				},
+				CredentialType: "6D795F63726564656E7469616C",
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				Issuer:         "invalid",
+			},
+			expected: false,
+		},
+		{
+			name: "fail - invalid BaseTx",
+			input: &CredentialDelete{
+				BaseTx: BaseTx{
+					Account:         "invalid",
+					TransactionType: CredentialDeleteTx,
+					Fee:             types.XRPCurrencyAmount(10),
+					Sequence:        10,
+				},
+				CredentialType: "6D795F63726564656E7469616C",
+				Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+				Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+			},
+			expected: false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			valid, err := test.input.Validate()
+			require.Equal(t, test.expected, valid)
+			if test.expected {
+				require.NoError(t, err)
+			} else {
+				require.Error(t, err)
+			}
+		})
+	}
+}
diff --git a/xrpl/transaction/errors.go b/xrpl/transaction/errors.go
index 7519caf2..66678265 100644
--- a/xrpl/transaction/errors.go
+++ b/xrpl/transaction/errors.go
@@ -11,6 +11,8 @@ var (
 	ErrInvalidAccount = errors.New("invalid xrpl address for Account")
 	// ErrInvalidCheckID is returned when the CheckID is not a valid 64-character hexadecimal string.
 	ErrInvalidCheckID = errors.New("invalid CheckID, must be a valid 64-character hexadecimal string")
+	// ErrInvalidCredentialType is returned when the CredentialType is not a valid hexadecimal string between 1 and 64 bytes.
+	ErrInvalidCredentialType = errors.New("invalid credential type, must be a hexadecimal string between 1 and 64 bytes")
 	// ErrInvalidDestination is returned when the Destination field does not meet XRPL address standards.
 	ErrInvalidDestination = errors.New("invalid xrpl address for Destination")
 	// ErrInvalidIssuer is returned when the issuer address is an invalid xrpl address.
@@ -21,4 +23,6 @@ var (
 	ErrInvalidHexPublicKey = errors.New("invalid PublicKey, must be a valid hexadecimal string")
 	// ErrInvalidTransactionType is returned when the TransactionType field is invalid or missing.
 	ErrInvalidTransactionType = errors.New("invalid or missing TransactionType")
+	// ErrInvalidSubject is returned when the Subject field is an invalid xrpl address.
+	ErrInvalidSubject = errors.New("invalid xrpl address for Subject")
 )
diff --git a/xrpl/transaction/integration/credential/credential_create_test.go b/xrpl/transaction/integration/credential/credential_create_test.go
new file mode 100644
index 00000000..2c806d3d
--- /dev/null
+++ b/xrpl/transaction/integration/credential/credential_create_test.go
@@ -0,0 +1,133 @@
+package credential
+
+import (
+	"encoding/hex"
+	"testing"
+	"time"
+
+	"github.com/Peersyst/xrpl-go/xrpl/testutil/integration"
+	rippleTime "github.com/Peersyst/xrpl-go/xrpl/time"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction"
+	e2eIntegration "github.com/Peersyst/xrpl-go/xrpl/transaction/integration"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/Peersyst/xrpl-go/xrpl/websocket"
+	"github.com/stretchr/testify/require"
+)
+
+type CredentialCreateTest struct {
+	Name               string
+	CredentialCreate   *transaction.CredentialCreate
+	ExpectedResultCode transaction.TxResult
+	ExpectedError      string
+}
+
+func TestIntegrationCredentialCreateWebsocket(t *testing.T) {
+	// Get the test environment configuration for websocket connection
+	env := integration.GetWebsocketEnv(t)
+
+	// Create a new websocket client with the test environment host and faucet provider
+	client := websocket.NewClient(websocket.NewClientConfig().WithHost(env.Host).WithFaucetProvider(env.FaucetProvider))
+
+	// Initialize a test runner with 2 wallets (one for sender, one for receiver)
+	runner := integration.NewRunner(t, client, &integration.RunnerConfig{
+		WalletCount: 2,
+	})
+
+	// Set up the test environment and create the wallets
+	err := runner.Setup()
+	require.NoError(t, err)
+
+	// Get the wallet instances for sender and receiver
+	sender := runner.GetWallet(0)
+	receiver := runner.GetWallet(1)
+
+	// Convert current time to Ripple time (seconds since Ripple Epoch)
+	nowTime, err := rippleTime.IsoTimeToRippleTime(time.Now().Format(time.RFC3339))
+	require.NoError(t, err)
+
+	// Set up time values for testing:
+	// - futureTime: 10000 seconds in the future (for valid expiration)
+	// - pastTime: 1000 seconds (for testing expired credentials)
+	futureTime := nowTime + 10000
+	pastTime := 1000
+
+	// Define credential types in hex format:
+	// - "6D795F63726564656E7469616C" = "my_credential" in ASCII
+	// - "6D795F63726564656E7469616C32" = "my_credential2" in ASCII
+	// - "6D795F63726564656E7469616C33" = "my_credential3" in ASCII
+	credentialType := types.CredentialType("6D795F63726564656E7469616C")
+	credentialType2 := types.CredentialType("6D795F63726564656E7469616C32")
+	credentialType3 := types.CredentialType("6D795F63726564656E7469616C33")
+
+	tt := []CredentialCreateTest{
+		{
+			Name: "pass - valid CredentialCreate",
+			CredentialCreate: &transaction.CredentialCreate{
+				BaseTx: transaction.BaseTx{
+					Account:         sender.GetAddress(),
+					TransactionType: transaction.CredentialCreateTx,
+					Fee:             types.XRPCurrencyAmount(10),
+				},
+				CredentialType: credentialType,
+				Subject:        types.Address(receiver.GetAddress()),
+				Expiration:     uint32(futureTime),
+				URI:            hex.EncodeToString([]byte("https://example.com")),
+			},
+			ExpectedResultCode: transaction.TesSUCCESS,
+		},
+		{
+			Name: "fail - Expiration is in the past	",
+			CredentialCreate: &transaction.CredentialCreate{
+				BaseTx: transaction.BaseTx{
+					Account:         sender.GetAddress(),
+					TransactionType: transaction.CredentialCreateTx,
+				},
+				Subject:        types.Address(receiver.GetAddress()),
+				CredentialType: credentialType2,
+				Expiration:     uint32(pastTime),
+			},
+			ExpectedResultCode: transaction.TecEXPIRED,
+		},
+		{
+			Name: "fail - Subject is missing",
+			CredentialCreate: &transaction.CredentialCreate{
+				BaseTx: transaction.BaseTx{
+					Account:         sender.GetAddress(),
+					TransactionType: transaction.CredentialCreateTx,
+				},
+				CredentialType: credentialType3,
+			},
+			ExpectedResultCode: transaction.TesSUCCESS, // not relevant as rippled will throw "invalidTransaction" immediately upon the transaction submission
+			ExpectedError:      e2eIntegration.ErrInvalidTransaction,
+		},
+		{
+			Name: "fail - Duplicate CredentialCreate",
+			CredentialCreate: &transaction.CredentialCreate{
+				BaseTx: transaction.BaseTx{
+					Account:         sender.GetAddress(),
+					TransactionType: transaction.CredentialCreateTx,
+				},
+				Subject:        types.Address(receiver.GetAddress()),
+				CredentialType: credentialType,
+			},
+			ExpectedResultCode: transaction.TecDUPLICATE,
+		},
+	}
+
+	for _, tc := range tt {
+		t.Run(tc.Name, func(t *testing.T) {
+			flatTx := tc.CredentialCreate.Flatten()
+
+			_, err = runner.TestTransaction(&flatTx, sender, tc.ExpectedResultCode.String())
+			if tc.ExpectedError != "" {
+				require.Error(t, err)
+				require.Contains(t, err.Error(), tc.ExpectedError)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+
+	err = runner.Teardown()
+	require.NoError(t, err)
+}
diff --git a/xrpl/transaction/results.go b/xrpl/transaction/results.go
new file mode 100644
index 00000000..03d9407a
--- /dev/null
+++ b/xrpl/transaction/results.go
@@ -0,0 +1,50 @@
+package transaction
+
+// TxResult represents the result code of a transaction
+type TxResult string
+
+const (
+	// Success results - https://xrpl.org/docs/references/protocol/transactions/transaction-results/tes-success
+
+	// The transaction was applied and forwarded to other servers.
+	// If this appears in a validated ledger, then the transaction's success is final.
+	TesSUCCESS TxResult = "tesSUCCESS"
+
+	// ------------------------------------------------------------------------------------------------
+	// tec codes ⬇️ - https://xrpl.org/docs/references/protocol/transactions/transaction-results/tec-codes
+
+	// These codes indicate that the transaction failed, but it was applied to a ledger to apply the transaction cost. They have numerical values in the range 100 to 199. It is recommended to use the text code, not the numeric value.
+
+	// Transactions with tec codes destroy the XRP paid as a transaction cost, and consume a sequence number. For the most part, the transactions take no other action, but there are some exceptions. For example, a transaction that results in tecOVERSIZE still cleans up some unfunded offers. Always look at the transaction metadata to see precisely what a transaction did.
+	// ------------------------------------------------------------------------------------------------
+
+	// The transaction tried to create an object (such as an Offer or a Check) whose provided Expiration time has already passed.
+	TecEXPIRED   TxResult = "tecEXPIRED"
+	TecDUPLICATE TxResult = "tecDUPLICATE"
+
+	// ------------------------------------------------------------------------------------------------
+	// tem codes ⬇️ - https://xrpl.org/docs/references/protocol/transactions/transaction-results/tem-codes
+	//
+	// These codes indicate that the transaction was malformed, and cannot succeed according to the XRP Ledger protocol.
+	// They have numerical values in the range -299 to -200. The exact code for any given error is subject to change, so don't rely on it.
+	// ------------------------------------------------------------------------------------------------
+
+	// The transaction is otherwise invalid. For example, the transaction ID may not be the right format, the signature may not be formed properly, or something else went wrong in understanding the transaction.
+	TemINVALID TxResult = "temINVALID"
+
+	// ------------------------------------------------------------------------------------------------
+	// tef codes ⬇️ - https://xrpl.org/docs/references/protocol/transactions/transaction-results/tef-codes
+	//
+	// These codes indicate that the transaction failed and was not included in a ledger, but the transaction could have succeeded in some theoretical ledger.
+	// Typically this means that the transaction can no longer succeed in any future ledger. They have numerical values in the range -199 to -100. The exact code for any given error is subject to change, so don't rely on it.
+	// ------------------------------------------------------------------------------------------------
+
+	// The sequence number of the transaction is lower than the current sequence number of the account sending the transaction.
+	//revive:disable-next-line:var-naming
+	TefPAST_SEQ TxResult = "tefPAST_SEQ"
+)
+
+// String returns the string representation of the result
+func (t TxResult) String() string {
+	return string(t)
+}
diff --git a/xrpl/transaction/results_test.go b/xrpl/transaction/results_test.go
new file mode 100644
index 00000000..d9f173d4
--- /dev/null
+++ b/xrpl/transaction/results_test.go
@@ -0,0 +1,47 @@
+package transaction
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestTxResult_String(t *testing.T) {
+	tests := []struct {
+		name     string
+		txResult TxResult
+		expected string
+	}{
+		{
+			name:     "TesSUCCESS",
+			txResult: TesSUCCESS,
+			expected: "tesSUCCESS",
+		},
+		{
+			name:     "TecEXPIRED",
+			txResult: TecEXPIRED,
+			expected: "tecEXPIRED",
+		},
+		{
+			name:     "TecDUPLICATE",
+			txResult: TecDUPLICATE,
+			expected: "tecDUPLICATE",
+		},
+		{
+			name:     "TemINVALID",
+			txResult: TemINVALID,
+			expected: "temINVALID",
+		},
+		{
+			name:     "TefPAST_SEQ",
+			txResult: TefPAST_SEQ,
+			expected: "tefPAST_SEQ",
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			result := test.txResult.String()
+			require.Equal(t, test.expected, result)
+		})
+	}
+}
diff --git a/xrpl/transaction/tx_type.go b/xrpl/transaction/tx_type.go
index dd2874bf..2f0faa5d 100644
--- a/xrpl/transaction/tx_type.go
+++ b/xrpl/transaction/tx_type.go
@@ -2,6 +2,7 @@ package transaction
 
 type TxType string
 
+// nolint // otherwise issues with the Credential transaction types
 const (
 	AccountSetTx                        TxType = "AccountSet"
 	AccountDeleteTx                     TxType = "AccountDelete"
@@ -15,6 +16,9 @@ const (
 	CheckCashTx                         TxType = "CheckCash"
 	CheckCreateTx                       TxType = "CheckCreate"
 	ClawbackTx                          TxType = "Clawback"
+	CredentialAcceptTx                  TxType = "CredentialAccept"
+	CredentialCreateTx                  TxType = "CredentialCreate"
+	CredentialDeleteTx                  TxType = "CredentialDelete"
 	DepositPreauthTx                    TxType = "DepositPreauth"
 	DIDDeleteTx                         TxType = "DIDDelete"
 	DIDSetTx                            TxType = "DIDSet"
diff --git a/xrpl/transaction/types/credential_type.go b/xrpl/transaction/types/credential_type.go
new file mode 100644
index 00000000..db0b45d5
--- /dev/null
+++ b/xrpl/transaction/types/credential_type.go
@@ -0,0 +1,36 @@
+package types
+
+import "github.com/Peersyst/xrpl-go/pkg/typecheck"
+
+const (
+	// Minimum length of a credential type is 1 byte (1 byte = 2 hex characters).
+	MinCredentialTypeLength = 2
+
+	// Maximum length of a credential type is 64 bytes (1 byte = 2 hex characters).
+	MaxCredentialTypeLength = 128
+)
+
+type CredentialType string
+
+// String returns the string representation of a CredentialType.
+func (c *CredentialType) String() string {
+	return string(*c)
+}
+
+// IsValidCredentialType checks if a credential type meets all the requirements:
+// - Not empty
+// - Valid hex string
+// - Length between MinCredentialTypeLength and MaxCredentialTypeLength
+func (c *CredentialType) IsValid() bool {
+	if c.String() == "" {
+		return false
+	}
+
+	credTypeStr := c.String()
+	if !typecheck.IsHex(credTypeStr) {
+		return false
+	}
+
+	length := len(credTypeStr)
+	return length >= MinCredentialTypeLength && length <= MaxCredentialTypeLength
+}
diff --git a/xrpl/transaction/types/credential_type_test.go b/xrpl/transaction/types/credential_type_test.go
new file mode 100644
index 00000000..d439fb71
--- /dev/null
+++ b/xrpl/transaction/types/credential_type_test.go
@@ -0,0 +1,81 @@
+package types
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCredentialType_String(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    CredentialType
+		expected string
+	}{
+		{
+			name:     "pass - valid CredentialType",
+			input:    CredentialType("my_credential"),
+			expected: "my_credential",
+		},
+		{
+			name:     "pass - empty URI",
+			input:    CredentialType(""),
+			expected: "",
+		},
+		{
+			name:     "pass - CredentialType with special characters",
+			input:    CredentialType("https://example.com/nft/1?query=param&another=param"),
+			expected: "https://example.com/nft/1?query=param&another=param",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.input.String(); got != tt.expected {
+				t.Errorf("CredentialType.String(), got: %v but we want: %v", got, tt.expected)
+			}
+		})
+	}
+}
+
+func TestCredentialType_IsValid(t *testing.T) {
+	tests := []struct {
+		name     string
+		credType CredentialType
+		expected bool
+	}{
+		{
+			name:     "fail - empty string",
+			credType: "",
+			expected: false,
+		},
+		{
+			name:     "pass - valid hex string",
+			credType: "6D795F63726564656E7469616C",
+			expected: true,
+		},
+		{
+			name:     "fail - invalid hex string",
+			credType: "invalid",
+			expected: false,
+		},
+		{
+			name:     "fail - short hex string",
+			credType: CredentialType(strings.Repeat("0", MinCredentialTypeLength-1)),
+			expected: false,
+		},
+		{
+			name:     "fail - long hex string",
+			credType: CredentialType(strings.Repeat("0", MaxCredentialTypeLength+1)),
+			expected: false,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			result := test.credType.IsValid()
+			assert.Equal(t, test.expected, result)
+		})
+	}
+}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (17)</summary><blockquote>

<details>
<summary>xrpl/transaction/types/credential_type.go (1)</summary><blockquote>

`3-8`: **Good implementation of the CredentialType type**

The implementation of the `CredentialType` type and its `String()` method are straightforward and correct.

A minor stylistic suggestion: Since `CredentialType` is based on `string` (which is an immutable type in Go), you might consider using a value receiver instead of a pointer receiver for the `String()` method. This would be more idiomatic Go for types that don't need to be modified and aren't expensive to copy.


```diff
-func (c *CredentialType) String() string {
-	return string(*c)
+func (c CredentialType) String() string {
+	return string(c)
```

</blockquote></details>
<details>
<summary>xrpl/common/constants.go (1)</summary><blockquote>

`20-24`: **Discrepancy in comment and constants for credential type length**

There's an inconsistency between the comments and the actual constant values:

1. The comment says "Minimum length of a credential type is 1 byte" but the constant value is 2
2. The comment says "Maximum length of a credential type is 64 bytes" but the constant value is 128

If 1 byte = 2 hex characters as stated, then the values are correct, but the comments should be adjusted to avoid confusion.


```diff
-	// Minimum length of a credential type is 1 byte (1 byte = 2 hex characters).
+	// Minimum length of a credential type is 2 hex characters (equivalent to 1 byte).
 	MinCredentialTypeLength = 2
-	// Maximum length of a credential type is 64 bytes (1 byte = 2 hex characters).
+	// Maximum length of a credential type is 128 hex characters (equivalent to 64 bytes).
 	MaxCredentialTypeLength = 128
```

</blockquote></details>
<details>
<summary>xrpl/common/credential.go (1)</summary><blockquote>

`17-17`: **Consider direct string conversion**

Since `credType` is already of type `CredentialType` which is a string type, you could consider a direct string conversion instead of calling the `String()` method. This would remove the dependency on the `String()` method implementation.


```diff
-	credTypeStr := credType.String()
+	credTypeStr := string(credType)
```

</blockquote></details>
<details>
<summary>xrpl/transaction/tx_type.go (1)</summary><blockquote>

`5-5`: **Consider documenting the linting issues being suppressed**

The comment indicates that there are linting issues related to the Credential transaction types. It would be helpful to provide more specific information about what issues are being suppressed and why.

```diff
-// nolint // otherwise issues with the Credential transaction types
+// nolint // Suppressing linting warnings related to naming conventions for Credential transaction types
```

</blockquote></details>
<details>
<summary>xrpl/transaction/credential_accept.go (3)</summary><blockquote>

`9-11`: **Correct the transaction reference in the doc comments.**

Lines 9-11 incorrectly reference "CredentialCreate" instead of "CredentialAccept." Please update them to accurately describe the CredentialAccept transaction.


Apply this diff to fix the doc mismatch:

```diff
-// A CredentialCreate transaction creates a credential in the ledger.
// The issuer of the credential uses this transaction to provisionally issue a credential.
// The credential is not valid until the subject of the credential accepts it with a CredentialAccept transaction.
+// A CredentialAccept transaction finalizes an already created credential in the ledger.
// The subject of the credential uses this transaction to accept the provisional credential.
// Once accepted, the credential becomes valid on the ledger.
```

---

`23-24`: **Fix the TxType method documentation reference.**

The doc string references the `CredentialCreate` struct rather than `CredentialAccept`. Update it to avoid confusion.


```diff
-// TxType implements the TxType method for the CredentialCreate struct.
+// TxType implements the TxType method for the CredentialAccept struct.
```

---

`39-40`: **Align the Validate method documentation with its actual struct.**

Similar to above, the documentation here references `CredentialCreate` instead of `CredentialAccept`. Please correct it.


```diff
-// Validate implements the Validate method for the CredentialCreate struct.
+// Validate implements the Validate method for the CredentialAccept struct.
```

</blockquote></details>
<details>
<summary>xrpl/transaction/credential_delete.go (1)</summary><blockquote>

`12-12`: **Synchronize the maximum length in the doc comment with actual validation.**

The comment says 256 bytes, but `IsValidCredentialType` in `common` typically enforces a 64-byte max. Adjust the comment or the validation as needed to maintain consistency.


```diff
-// Arbitrary data defining the type of credential to delete. The minimum length is 1 byte and the maximum length is 256 bytes.
+// Arbitrary data defining the type of credential to delete. The minimum length is 1 byte and the maximum length is 64 bytes.
```

</blockquote></details>
<details>
<summary>xrpl/transaction/credential_accept_test.go (5)</summary><blockquote>

`41-43`: **Consider using assert.Equal instead of reflect.DeepEqual for consistency**

The test is using `reflect.DeepEqual` for comparison, but the rest of the test file (and other test files) uses the testify/assert package. For consistency and better error messages, consider using `assert.Equal(t, expected, flattened)` instead.


```diff
-	if !reflect.DeepEqual(flattened, expected) {
-		t.Errorf("Flatten result differs from expected: %v, %v", flattened, expected)
-	}
+	assert.Equal(t, expected, flattened)
```

---

`57-58`: **Use CredentialAcceptTx for TransactionType in test cases**

The test is using "AMMWithdraw" as the TransactionType, but for consistency and to better reflect real-world usage, it would be more appropriate to use CredentialAcceptTx.


```diff
-					TransactionType: "AMMWithdraw",
+					TransactionType: CredentialAcceptTx,
```

---

`113-114`: **Correct test case name references wrong transaction type**

This test case is named "fail - CredentialCreate with an invalid CredentialType (too long)" but it's testing CredentialAccept. Update the name to reference the correct transaction type.


```diff
-			name: "fail - CredentialCreate with an invalid CredentialType (too long)",
+			name: "fail - CredentialAccept with an invalid CredentialType (too long)",
```

---

`128-129`: **Correct test case name references wrong transaction type**

This test case is named "fail - CredentialCreate with an invalid CredentialType (too short)" but it's testing CredentialAccept. Update the name to reference the correct transaction type.


```diff
-			name: "fail - CredentialCreate with an invalid CredentialType (too short)",
+			name: "fail - CredentialAccept with an invalid CredentialType (too short)",
```

---

`159-167`: **Consider using testify/assert for more readable test assertions**

The test is using standard Go error checks for assertions. For consistency with other parts of the test and better error messages, consider using the testify/assert package.


```diff
-			if valid != tt.expected {
-				t.Errorf("Expected validation result to be %v, got %v", tt.expected, valid)
-			}
-			if (err != nil) != !tt.expected {
-				t.Errorf("Expected error presence to be %v, got %v", !tt.expected, err != nil)
-			}
+			if tt.expected {
+				assert.True(t, valid)
+				assert.NoError(t, err)
+			} else {
+				assert.False(t, valid)
+				assert.Error(t, err)
+			}
```

</blockquote></details>
<details>
<summary>xrpl/transaction/credential_create_test.go (3)</summary><blockquote>

`43-45`: **Consider using assert.Equal instead of reflect.DeepEqual for consistency**

The test is using `reflect.DeepEqual` for comparison, but elsewhere in the tests the testify/assert package is used. For consistency and better error messages, consider using `assert.Equal(t, expected, flattened)` instead.


```diff
-	if !reflect.DeepEqual(flattened, expected) {
-		t.Errorf("Flatten result differs from expected: %v, %v", flattened, expected)
-	}
+	assert.Equal(t, expected, flattened)
```

---

`59-60`: **Use CredentialCreateTx for TransactionType in test cases**

The test is using "AMMWithdraw" as the TransactionType, but for consistency and to better reflect real-world usage, it would be more appropriate to use CredentialCreateTx.


```diff
-					TransactionType: "AMMWithdraw",
+					TransactionType: CredentialCreateTx,
```

---

`73-82`: **Consider using testify/assert for more readable test assertions**

The test is using standard Go error checks for assertions. For consistency with other parts of the test and better error messages, consider using the testify/assert package like in credential_delete_test.go.


```diff
-			if valid != tt.expected {
-				t.Errorf("Expected validation result to be %v, got %v", tt.expected, valid)
-			}
-			if (err != nil) != !tt.expected {
-				t.Errorf("Expected error presence to be %v, got %v", !tt.expected, err != nil)
-			}
+			if tt.expected {
+				assert.True(t, valid)
+				assert.NoError(t, err)
+			} else {
+				assert.False(t, valid)
+				assert.Error(t, err)
+			}
```

</blockquote></details>
<details>
<summary>xrpl/transaction/credential_delete_test.go (1)</summary><blockquote>

`23-26`: **Consider testing case with different Issuer and Subject**

In this test, the Issuer and Subject are set to the same address. Consider adding a test case where they are different to ensure that both fields are properly validated independently.


```diff
@@ -23,7 +23,7 @@
 		},
 		CredentialType: "6D795F63726564656E7469616C",
 		Subject:        "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
-		Issuer:         "rJZdUoJnJb5q8tHb9cYfYh5vZg9G6z2v1d",
+		Issuer:         "rGiKBT99aScVFXoFMWTX5DNMbLTNAYmCAk",
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a11cbd4e81277cbe2c432760b87547b6a4f47585 and 6ebac7b10acd885d7d0e80d3c090b850735519c2.

</details>

<details>
<summary>📒 Files selected for processing (24)</summary>

* `xrpl/transaction/credential_create.go` (1 hunks)
* `xrpl/transaction/credential_create_test.go` (1 hunks)
* `xrpl/transaction/tx_type.go` (1 hunks)
* `xrpl/transaction/types/credential_type.go` (1 hunks)
* `xrpl/transaction/types/credential_type_test.go` (1 hunks)
* `xrpl/transaction/credential_create.go` (3 hunks)
* `xrpl/transaction/credential_create_test.go` (1 hunks)
* `xrpl/transaction/credential_create.go` (2 hunks)
* `xrpl/transaction/credential_create_test.go` (2 hunks)
* `xrpl/transaction/errors.go` (1 hunks)
* `xrpl/common/constants.go` (1 hunks)
* `xrpl/common/credential.go` (1 hunks)
* `xrpl/common/credential_test.go` (1 hunks)
* `xrpl/transaction/credential_accept.go` (1 hunks)
* `xrpl/transaction/credential_accept_test.go` (1 hunks)
* `xrpl/transaction/credential_create.go` (2 hunks)
* `xrpl/transaction/credential_create_test.go` (2 hunks)
* `xrpl/transaction/credential_create.go` (0 hunks)
* `xrpl/transaction/credential_delete.go` (1 hunks)
* `xrpl/transaction/errors.go` (1 hunks)
* `xrpl/transaction/credential_delete_test.go` (1 hunks)
* `CHANGELOG.md` (2 hunks)
* `xrpl/transaction/credential_delete.go` (3 hunks)
* `xrpl/transaction/tx_type.go` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>xrpl/common/credential.go (1)</summary>

`8-24`: **Well-structured validation function**

The implementation of `IsValidCredentialType` is clean and effective. It properly validates that the credential type is:
1. Not empty
2. A valid hex string
3. Within the length constraints defined by the constants

One minor observation: Since you're using the `String()` method on line 17, you're relying on the implementation of that method. If you change the receiver type of `String()` from a pointer to a value receiver as suggested in the previous comment, this code will still work correctly.

</details>
<details>
<summary>CHANGELOG.md (1)</summary>

`8-17`: **Proper documentation of the new credential transaction types**

The changelog entry follows the established format and clearly documents the addition of the three new credential transaction types, which aligns with the PR objectives.

</details>
<details>
<summary>xrpl/transaction/types/credential_type_test.go (1)</summary>

`7-37`: **Test cases appropriately validate the CredentialType String method**

Good test coverage with a variety of cases including a valid credential type, an empty string, and a more complex URL with query parameters. The table-driven test approach is well-structured and follows good testing practices.

</details>
<details>
<summary>xrpl/transaction/tx_type.go (2)</summary>

`18-20`: **New credential transaction types added correctly**

The new transaction types follow the existing naming convention and structure of the file.

---

`23-24`: <details>
<summary>❌ Incorrect review comment</summary>

**Existing duplicate declaration detected**

There appears to be a duplicate declaration of `DIDSetTx`. This is unrelated to your changes but might be worth addressing in a separate PR.

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for duplicate constant declarations in tx_type.go
grep -n "DIDSetTx" xrpl/transaction/tx_type.go
```

Length of output: 104

---



</details>

**Verified: No Duplicate Declaration of DIDSetTx Found**

The shell script output confirms that `DIDSetTx` appears only once (at line 24) in `xrpl/transaction/tx_type.go`. It appears that the duplicate declaration warning is outdated and can be disregarded.



> Likely an incorrect or invalid review comment.

</details>
<details>
<summary>xrpl/common/credential_test.go (2)</summary>

`11-49`: **Test cases appropriately validate credential type constraints**

The test cases cover important validation scenarios including empty string, valid/invalid hex strings, and strings that are too short or too long based on the defined constraints. Good use of the testify assertion library to make the test cases clear and readable.

---

`34-35`: <details>
<summary>✅ Verification successful</summary>

**Verify consistency between test code and constant definitions**

The test uses `MinCredentialTypeLength` and `MaxCredentialTypeLength` constants to validate length constraints. Ensure that these constants are correctly defined and consistent with the requirements for credential types.




Also applies to: 39-40

---

🏁 Script executed:

```shell
#!/bin/bash
# Find the definition of credential type length constants
grep -r "MinCredentialTypeLength\|MaxCredentialTypeLength" --include="*.go" .
```

Length of output: 1200

---



</details>

**Credential Constants Consistency Verified**

- Confirmed that `MinCredentialTypeLength` is set to 2 and `MaxCredentialTypeLength` to 128 in `xrpl/common/constants.go`.
- The tests in `xrpl/common/credential_test.go` (lines 34-35 and 39-40), as well as similar tests in the transaction test files, correctly use `MinCredentialTypeLength-1` and `MaxCredentialTypeLength+1` to validate credential type length boundaries.
- The constants and test validations are consistent with the intended requirements.

</details>
<details>
<summary>xrpl/transaction/credential_create.go (1)</summary>

`12-13`: **Confirm whether credential data is strictly hexadecimal or arbitrary bytes.**

The error message states "must be an hexadecimal between 1 and 64 bytes," but the doc comment for `CredentialType` describes it as arbitrary data. Please verify which is correct and adjust the error message or documentation accordingly.


Possible diff if the requirement is truly arbitrary data up to 64 bytes:

```diff
-ErrInvalidCredentialType = errors.New("credential create: invalid credential type, must be an hexadecimal between 1 and 64 bytes")
+ErrInvalidCredentialType = errors.New("credential create: invalid credential type, must be between 1 and 64 bytes")
```

</details>
<details>
<summary>xrpl/transaction/credential_create_test.go (1)</summary>

`48-84`: <details>
<summary>❌ Incorrect review comment</summary>

**Test cases appear incomplete**

The test cases section only includes one test case for a valid CredentialCreate, but the file seems to have more test cases after line 84 that aren't properly annotated. Make sure all test cases are properly included and tested.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the file exists and verify its content
cat xrpl/transaction/credential_create_test.go | wc -l
echo "Number of test cases:"
grep -c "name:" xrpl/transaction/credential_create_test.go
```

Length of output: 175

---



</details>

**Test cases are fully included and properly annotated.**  
After verification, the file `xrpl/transaction/credential_create_test.go` contains 274 lines with 13 test cases defined (confirmed via the grep command). The snippet in question only shows a portion of the file (lines 48–84), and all additional test cases are present and adequately described further down in the code. There is no issue with missing or improperly annotated test cases.



> Likely an incorrect or invalid review comment.

</details>
<details>
<summary>xrpl/transaction/credential_delete_test.go (1)</summary>

`1-141`: **Test implementation looks good**

The test cases in this file are well-structured and cover both valid and invalid scenarios for CredentialDelete. The use of assert package is consistent, and the transaction type is correctly set to CredentialDeleteTx throughout the tests.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T09:51:04Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T11:50:05Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T11:51:04Z
user: login: GuillemGarciaDev
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-11T14:58:07Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T08:43:58Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T08:54:14Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T08:56:31Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>xrpl/ledger-entry-types/credential.go (1)</summary><blockquote>

`47-50`: **Fix the comment for SetLsfAccepted method.**

The function comment incorrectly describes setting "the one owner count flag" when it actually sets the accepted flag.


```diff
-// SetLsfAccepted sets the one owner count flag.
+// SetLsfAccepted sets the accepted flag to indicate the credential has been accepted by the subject.
```

Additionally, consider adding an `IsLsfAccepted() bool` method to check if the credential has been accepted, which would complement the setter method.

</blockquote></details>
<details>
<summary>xrpl/transaction/integration/credential_create.go (1)</summary><blockquote>

`34-47`: **Consider adding more test cases for better coverage.**

The test suite currently has only one test case for a valid credential creation. Consider adding more cases to test validation, such as:
- Invalid credential type
- Invalid subject address
- Missing required fields
- Maximum length URI

This would ensure the validation logic is thoroughly tested.

</blockquote></details>
<details>
<summary>xrpl/transaction/types/credential_type.go (1)</summary><blockquote>

`20-36`: **Return error context or incorporate reason**  
`IsValid()` effectively enforces length constraints and checks for a valid hex string. However, returning false alone can make troubleshooting difficult for callers. Consider throwing or returning an error (or reason) where appropriate.

</blockquote></details>
<details>
<summary>xrpl/ledger-entry-types/credential_test.go (1)</summary><blockquote>

`22-53`: **Comprehensive flatten/unflatten test**  
The `SerializeAndDeserialize` check is a solid way to confirm round-trip serialization. You might also consider negative or edge-case tests (e.g., malformed JSON) to ensure robust error handling.

</blockquote></details>
<details>
<summary>xrpl/transaction/types/credential_type_test.go (1)</summary><blockquote>

`42-82`: **Good coverage of valid/invalid hex checks.**

You already cover short and long strings. Consider also testing exactly at the boundary limits (e.g., len == MinCredentialTypeLength, len == MaxCredentialTypeLength) for completeness.  


Example snippet to add within the test table:
```diff
+{
+  name:     "pass - exact min hex string",
+  credType: CredentialType(strings.Repeat("0", MinCredentialTypeLength)),
+  expected: true,
+},
+{
+  name:     "pass - exact max hex string",
+  credType: CredentialType(strings.Repeat("0", MaxCredentialTypeLength)),
+  expected: true,
+},
```

</blockquote></details>
<details>
<summary>xrpl/transaction/credential_delete_test.go (2)</summary><blockquote>

`30-32`: **Add comments explaining test value formats**

The test uses hex values like "6D795F63726564656E7469616C" for the CredentialType field without explaining what these represent. Consider adding comments explaining the format or using constants with descriptive names to improve readability.

---

`137-143`: **Verify specific error messages in validation tests**

Currently, the test only checks if an error is returned for invalid cases but doesn't verify the specific error message. For more robust testing, consider checking the exact error message to ensure validation fails for the expected reason.

```diff
if test.expected {
    require.NoError(t, err)
} else {
    require.Error(t, err)
+   // Add expected error message checks for each case
+   // Example: if test.name == "fail - invalid CredentialType" {
+   //     require.Contains(t, err.Error(), "expected error message")
+   // }
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6ebac7b10acd885d7d0e80d3c090b850735519c2 and d8a3d9f32fdba913faeda912d0196388c74d81fc.

</details>

<details>
<summary>📒 Files selected for processing (15)</summary>

* `CHANGELOG.md` (2 hunks)
* `xrpl/ledger-entry-types/credential.go` (1 hunks)
* `xrpl/ledger-entry-types/credential_test.go` (1 hunks)
* `xrpl/ledger-entry-types/ledger_object.go` (1 hunks)
* `xrpl/transaction/credential_accept.go` (1 hunks)
* `xrpl/transaction/credential_accept_test.go` (1 hunks)
* `xrpl/transaction/credential_create.go` (1 hunks)
* `xrpl/transaction/credential_create_test.go` (1 hunks)
* `xrpl/transaction/credential_delete.go` (1 hunks)
* `xrpl/transaction/credential_delete_test.go` (1 hunks)
* `xrpl/transaction/errors.go` (2 hunks)
* `xrpl/transaction/integration/credential_create.go` (1 hunks)
* `xrpl/transaction/results/results.go` (1 hunks)
* `xrpl/transaction/types/credential_type.go` (1 hunks)
* `xrpl/transaction/types/credential_type_test.go` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary>

* CHANGELOG.md
* xrpl/transaction/credential_accept_test.go
* xrpl/transaction/credential_create_test.go
* xrpl/transaction/credential_accept.go

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (5)</summary>

<details>
<summary>📓 Common learnings</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

</details>
<details>
<summary>xrpl/transaction/credential_create.go (3)</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: The CredentialCreate struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In the PR #113 for xrpl-go, the initial flagging of duplicated fields (`CredentialType` and `Expiration`) in the `xrpl/transaction/credential_create.go` file was determined to be irrelevant and not an actual issue.
```

</details>
<details>
<summary>xrpl/transaction/credential_delete.go (3)</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: The CredentialCreate struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In the PR #113 for xrpl-go, the initial flagging of duplicated fields (`CredentialType` and `Expiration`) in the `xrpl/transaction/credential_create.go` file was determined to be irrelevant and not an actual issue.
```

</details>
<details>
<summary>xrpl/transaction/integration/credential_create.go (2)</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: The CredentialCreate struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

</details>
<details>
<summary>xrpl/ledger-entry-types/credential.go (1)</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In the PR #113 for xrpl-go, the initial flagging of duplicated fields (`CredentialType` and `Expiration`) in the `xrpl/transaction/credential_create.go` file was determined to be irrelevant and not an actual issue.
```

</details>

</details><details>
<summary>🪛 GitHub Actions: XRPL-GO Lint and Test</summary>

<details>
<summary>xrpl/transaction/integration/credential_create.go</summary>

[error] 30-30: Error return value of `runner.Teardown` is not checked (errcheck)

---

[error] 20-20: var-naming: don't use underscores in Go names; func TestIntegrationCredentialCreate_Websocket should be TestIntegrationCredentialCreateWebsocket (revive)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (26)</summary><blockquote>

<details>
<summary>xrpl/transaction/errors.go (3)</summary>

`14-15`: **LGTM! Error message for credential type validation.**

The error message for `ErrInvalidCredentialType` is clear and correctly formatted with the appropriate grammar.

---

`16-17`: **LGTM! Error message for credential URI validation.**

The error message for `ErrInvalidCredentialURI` is clear and correctly specifies the maximum length constraint.

---

`28-29`: **LGTM! Error message for subject validation.**

The error message for `ErrInvalidSubject` follows the existing pattern and is consistent with other address validation errors.

</details>
<details>
<summary>xrpl/transaction/results/results.go (1)</summary>

`3-17`: **LGTM! Well-structured transaction result type and constants.**

The new `TxResult` type and `TesSUCCESS` constant are well-documented and follow Go conventions. The String() method is correctly implemented.

</details>
<details>
<summary>xrpl/ledger-entry-types/credential.go (3)</summary>

`5-9`: **LGTM! Well-defined flag constant.**

The `lsfAccepted` constant is properly defined with clear documentation explaining its purpose.

---

`11-41`: **LGTM! Well-structured Credential struct definition.**

The `Credential` struct is well-documented with clear comments for each field. The structure follows the patterns used in other ledger entry types.

---

`43-45`: **LGTM! Entry type method is correctly implemented.**

The `EntryType` method correctly returns the `CredentialEntry` type.

</details>
<details>
<summary>xrpl/transaction/integration/credential_create.go (1)</summary>

`49-68`: **LGTM! Well-structured test logic.**

The test execution logic is well-structured, validating the transaction, flattening it, and then testing it against the expected result.

</details>
<details>
<summary>xrpl/transaction/types/credential_type.go (3)</summary>

`5-11`: **Consider making min/max lengths more discoverable**  
Defining `MinCredentialTypeLength` and `MaxCredentialTypeLength` here is correct for scoping. However, consider documenting or referencing these constraints in the associated domain logic (e.g., Credential docs or usage) to help maintain clarity.

---

`13-14`: **Straightforward custom type declaration**  
Declaring `type CredentialType string` is a clear and concise approach. Good use of a dedicated type for credential logic.

---

`15-19`: **String representation is correct**  
Implementing the `String()` method on `CredentialType` is straightforward and behaves as expected.

</details>
<details>
<summary>xrpl/ledger-entry-types/credential_test.go (2)</summary>

`11-14`: **Sufficient test coverage for `EntryType`**  
This test ensures that the `Credential` struct’s `EntryType` method properly returns `CredentialEntry`. The usage of `require.Equal` is consistent with project guidelines.

---

`16-20`: **Good test for `SetLsfAccepted`**  
Test logic is straightforward. Verifying that `Flags` is updated to `lsfAccepted` is clear and aligned with expected behavior.

</details>
<details>
<summary>xrpl/transaction/credential_create.go (5)</summary>

`1-14`: **Well-defined constants for URI length.**

The min/max constraints (2/512) align with the documented 1–256 byte requirement in hex form for the URI. No issues with concurrency or performance here.

---

`16-34`: **Struct definition looks consistent and clear.**

The fields are clearly documented and neatly grouped. No apparent duplication or merge conflict issues, as previously discussed.

---

`36-39`: **Transaction type accessor is straightforward.**

No issues with the method returning the correct enum.

---

`41-59`: **Flatten method is well-structured.**

Assigning only non-zero optional fields ensures a concise serialized form. The approach is good-to-go.

---

`61-82`: **Validate method is robust but consider edge cases.**

The code properly checks:
- BaseTx validity  
- Subject’s address format  
- Credential type validity  
- Optional URI length

As an extra precaution, confirm whether an empty Subject is truly invalid or if it should fallback to another default.  

Would you like a script to scan for references in the codebase clarifying if Subject must always be supplied or if it defaults to the transaction sender?

</details>
<details>
<summary>xrpl/transaction/types/credential_type_test.go (1)</summary>

`1-40`: **Comprehensive tests for CredentialType.String().**

Covering empty, normal, and special-character strings nicely verifies the method’s behavior.

</details>
<details>
<summary>xrpl/transaction/credential_delete.go (5)</summary>

`1-7`: **Imports and package declaration are fine.**

No issues with concurrency, correctness, or performance here.

---

`8-21`: **CredentialDelete struct documentation mismatch.**

You mention a maximum length of 256 bytes here, whereas `CredentialCreate` references a maximum of 64 bytes for the same credential type. Please confirm or unify these limits.

---

`23-26`: **Transaction type enumeration is correct.**

Clearly identifies this transaction as `CredentialDeleteTx`.

---

`28-44`: **Flatten method is consistent with standard patterns.**

Conditionally setting optional fields keeps the flattened representation minimal.

---

`46-67`: **Validate method is coherent.**

• Properly checks base transaction and credential type validity.  
• Enforces address checks only if Subject or Issuer is non-empty.

</details>
<details>
<summary>xrpl/transaction/credential_delete_test.go (2)</summary>

`16-52`: **Consider adding more test cases to leverage table-driven testing**

The test currently uses a table-driven approach with only one test case. Adding more cases (like different account addresses, varying credential types, empty fields, etc.) would better leverage this pattern and improve test coverage.

---

`1-146`: **Tests are well-structured and comprehensive**

The test file covers all essential functionality of the CredentialDelete transaction type with proper test functions for TxType, Flatten, and Validate operations. The table-driven approach for validation tests covers multiple cases including valid usage and various failure scenarios.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T09:50:21Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 6**

<details>
<summary>🧹 Nitpick comments (5)</summary><blockquote>

<details>
<summary>xrpl/ledger-entry-types/credential_test.go (1)</summary><blockquote>

`51-53`: **Consider using `require.NoError` for error handling**

Using `t.Error(err)` will continue test execution after the error. A better approach would be to use `require.NoError(t, err)` to fail fast if an error occurs, which is consistent with the rest of the test assertions.


```diff
-	if err := testutil.SerializeAndDeserialize(t, credential, json); err != nil {
-		t.Error(err)
-	}
+	err := testutil.SerializeAndDeserialize(t, credential, json)
+	require.NoError(t, err)
```

</blockquote></details>
<details>
<summary>xrpl/ledger-entry-types/credential.go (1)</summary><blockquote>

`47-47`: **Fix comment to match the actual function**

The comment for `SetLsfAccepted` mentions "one owner count flag" but the function is setting the `lsfAccepted` flag. Please update the comment to describe the actual purpose of the function.


```diff
-// SetLsfAccepted sets the one owner count flag.
+// SetLsfAccepted sets the lsfAccepted flag to indicate the credential has been accepted.
```

</blockquote></details>
<details>
<summary>xrpl/transaction/integration/credential_create.go (1)</summary><blockquote>

`34-47`: **Consider adding negative test cases**

The test suite only includes a single positive test case. It would be beneficial to add negative test cases to validate error handling for scenarios like invalid credential types, missing required fields, etc.

</blockquote></details>
<details>
<summary>xrpl/transaction/results/results.go (1)</summary><blockquote>

`6-12`: **Consider adding more result types.**

While `TesSUCCESS` is a good start, transaction processing typically involves multiple potential outcome states. Consider adding other common result codes that may be relevant for credential transactions.

```shell
#!/bin/bash
# Look for existing transaction result codes in the codebase
rg "tes|tec|tef|tel|tem" --type go | grep 'const'
```

</blockquote></details>
<details>
<summary>xrpl/transaction/types/credential_type.go (1)</summary><blockquote>

`25-36`: **Comprehensive credential type validation.**  
Checking for non-empty hex strings and length boundaries is straightforward and robust. Consider also confirming an even number of hex characters to ensure perfect byte alignment, if required by your specification.  

```diff
 func (c *CredentialType) IsValid() bool {
     if c.String() == "" {
         return false
     }
 
     credTypeStr := c.String()
     if !typecheck.IsHex(credTypeStr) {
         return false
     }
+    // (Optional) Check for even length to ensure proper byte alignment
+    if len(credTypeStr) % 2 != 0 {
+        return false
+    }

     length := len(credTypeStr)
     return length >= MinCredentialTypeLength && length <= MaxCredentialTypeLength
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6ebac7b10acd885d7d0e80d3c090b850735519c2 and d8a3d9f32fdba913faeda912d0196388c74d81fc.

</details>

<details>
<summary>📒 Files selected for processing (15)</summary>

* `CHANGELOG.md` (2 hunks)
* `xrpl/ledger-entry-types/credential.go` (1 hunks)
* `xrpl/ledger-entry-types/credential_test.go` (1 hunks)
* `xrpl/ledger-entry-types/ledger_object.go` (1 hunks)
* `xrpl/transaction/credential_accept.go` (1 hunks)
* `xrpl/transaction/credential_accept_test.go` (1 hunks)
* `xrpl/transaction/credential_create.go` (1 hunks)
* `xrpl/transaction/credential_create_test.go` (1 hunks)
* `xrpl/transaction/credential_delete.go` (1 hunks)
* `xrpl/transaction/credential_delete_test.go` (1 hunks)
* `xrpl/transaction/errors.go` (2 hunks)
* `xrpl/transaction/integration/credential_create.go` (1 hunks)
* `xrpl/transaction/results/results.go` (1 hunks)
* `xrpl/transaction/types/credential_type.go` (1 hunks)
* `xrpl/transaction/types/credential_type_test.go` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (4)</summary>

* CHANGELOG.md
* xrpl/transaction/credential_accept.go
* xrpl/transaction/credential_accept_test.go
* xrpl/transaction/credential_create_test.go

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (5)</summary>

<details>
<summary>📓 Common learnings</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: The CredentialCreate struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

</details>
<details>
<summary>xrpl/transaction/credential_delete.go (3)</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: The CredentialCreate struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In the PR #113 for xrpl-go, the initial flagging of duplicated fields (`CredentialType` and `Expiration`) in the `xrpl/transaction/credential_create.go` file was determined to be irrelevant and not an actual issue.
```

</details>
<details>
<summary>xrpl/ledger-entry-types/credential.go (1)</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In the PR #113 for xrpl-go, the initial flagging of duplicated fields (`CredentialType` and `Expiration`) in the `xrpl/transaction/credential_create.go` file was determined to be irrelevant and not an actual issue.
```

</details>
<details>
<summary>xrpl/transaction/credential_create.go (3)</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: The CredentialCreate struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In the PR #113 for xrpl-go, the initial flagging of duplicated fields (`CredentialType` and `Expiration`) in the `xrpl/transaction/credential_create.go` file was determined to be irrelevant and not an actual issue.
```

</details>
<details>
<summary>xrpl/transaction/integration/credential_create.go (2)</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: The CredentialCreate struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

</details>

</details><details>
<summary>🪛 GitHub Actions: XRPL-GO Lint and Test</summary>

<details>
<summary>xrpl/transaction/integration/credential_create.go</summary>

[error] 30-30: Error return value of `runner.Teardown` is not checked (errcheck)

---

[error] 20-20: var-naming: don't use underscores in Go names; func TestIntegrationCredentialCreate_Websocket should be TestIntegrationCredentialCreateWebsocket (revive)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (13)</summary><blockquote>

<details>
<summary>xrpl/transaction/credential_delete_test.go (1)</summary>

`10-146`: **Well-structured test with comprehensive test cases**

The test is well-organized using table-driven tests and covers a good range of validation scenarios including various invalid inputs. This approach makes it easy to add more test cases in the future.

</details>
<details>
<summary>xrpl/transaction/results/results.go (2)</summary>

`3-5`: **Looks good!**

The `TxResult` type definition is clear and will serve as a good foundation for representing transaction results.

---

`14-17`: **Simple and effective implementation.**

The `String()` method follows the standard pattern for string representation of custom types.

</details>
<details>
<summary>xrpl/transaction/errors.go (2)</summary>

`14-15`: **Error message fixed as requested.**

The grammar and content of the error message have been corrected based on previous feedback.

---

`28-29`: **Error message is clear and consistent.**

The error message for invalid Subject field follows the same pattern as other invalid address errors in the file.

</details>
<details>
<summary>xrpl/transaction/types/credential_type_test.go (1)</summary>

`42-81`: <details>
<summary>✅ Verification successful</summary>

**Comprehensive test cases for validation.**

The test cases for `IsValid` cover important scenarios including empty strings, valid/invalid hex, and length constraints. However, ensure that the constants `MinCredentialTypeLength` and `MaxCredentialTypeLength` are properly defined and documented.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for credential type length constants
rg "MinCredentialTypeLength|MaxCredentialTypeLength" --type go
```

Length of output: 1247

---



</details>

**Verified: CredentialType Test Cases and Constant Documentation**  
The test cases in `xrpl/transaction/types/credential_type_test.go` comprehensively cover validation scenarios including empty strings, valid/invalid hex strings, and length boundary cases. The related constants (`MinCredentialTypeLength` and `MaxCredentialTypeLength`) are properly defined in `xrpl/transaction/types/credential_type.go` (with values 2 and 128, respectively) and are accompanied by inline comments explaining their purpose. No further changes are required regarding the constants' definitions or documentation.

</details>
<details>
<summary>xrpl/transaction/credential_create.go (4)</summary>

`8-14`: **Ensure constants align with documented ranges.**  
The constants `MinURILength` and `MaxURILength` correctly reflect “1 byte” to “256 bytes” in their hex-encoded forms (2 to 512 hex characters). This is consistent with the doc comments, so no changes needed here. Good approach for clarity.

---

`16-34`: **Struct layout appears valid and consistent.**  
The fields `Subject`, `CredentialType`, `Expiration`, and `URI` clearly represent the required data for credential creation. The docstrings match the usage, and marking `Expiration` and `URI` with `omitempty` is a good choice to keep JSON outputs clean.

---

`36-60`: **Flatten method is well-structured.**  
Using `BaseTx.Flatten()` and then layering transaction-specific fields is a clean way to unify base transaction data with new fields. No issues observed.

---

`61-82`: **Validation logic is thorough.**  
The method checks base transaction validity, address correctness for `Subject`, credential type validity, and URI length constraints. This is a solid approach. Ensure you maintain unit tests covering edge cases (e.g. empty subject, out-of-range URI).

</details>
<details>
<summary>xrpl/transaction/types/credential_type.go (1)</summary>

`5-11`: **Clear definition of min/max length constants.**  
Defining 2 as the minimum (1 byte → 2 hex chars) and 128 as the maximum (64 bytes → 128 hex chars) clarifies the constraints. These constants correctly align with the doc comments referencing byte-to-hex conversion.

</details>
<details>
<summary>xrpl/transaction/credential_delete.go (2)</summary>

`28-44`: **Flatten method mirrors the create transaction well.**  
Properties like `Subject` and `Issuer` are flattened only if present, matching the “omitempty” pattern. Looks consistent and maintainable.

---

`46-67`: **Validation correctly checks optional fields.**  
Enforcing valid addresses for non-empty `Subject` and `Issuer` is a good approach. If the ledger sets defaults when they’re empty, consider adding a note or doc references for clarity.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T09:52:39Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>xrpl/transaction/integration/credential_create.go (4)</summary><blockquote>

`20-30`: **Consider adding proper function teardown**

The current teardown approach may cause test failures to be missed if teardown fails. When `runner.Teardown()` is called directly, an error during cleanup might override a test failure.


```diff
func TestIntegrationCredentialCreateWebsocket(t *testing.T) {
	env := integration.GetWebsocketEnv(t)
	client := websocket.NewClient(websocket.NewClientConfig().WithHost(env.Host).WithFaucetProvider(env.FaucetProvider))

	runner := integration.NewRunner(t, client, &integration.RunnerConfig{
		WalletCount: 1,
	})

	err := runner.Setup()
	require.NoError(t, err)
+	defer func() {
+		err := runner.Teardown()
+		require.NoError(t, err)
+	}()
```

---

`33-46`: **Add more test cases for comprehensive coverage**

The current test suite only includes a "happy path" scenario. Consider adding negative test cases to validate error handling and edge cases such as:
- Invalid credential type
- Missing required fields
- Invalid subject format
- Expired credentials


For example, add a test case that expects validation to fail:

```go
{
    Name: "fail - invalid CredentialType",
    CredentialCreate: &transaction.CredentialCreate{
        BaseTx: transaction.BaseTx{
            Account: sender.GetAddress(),
        },
        CredentialType: "invalid", // Too short
        Subject:        sender.GetAddress(),
        Expiration:     10000,
        URI:            hex.EncodeToString([]byte("https://example.com")),
    },
    ExpectedError: "invalid credential type",
},
```

---

`43-43`: **Add a comment explaining the hex encoding**

The URI is hex-encoded without explanation. Add a comment clarifying why hex encoding is used for better code maintainability.


```diff
-				URI:            hex.EncodeToString([]byte("https://example.com")),
+				// URI must be hex-encoded as per XRPL specification
+				URI:            hex.EncodeToString([]byte("https://example.com")),
```

---

`69-71`: **Move teardown to a defer statement**

The current approach with teardown at the end of the function means it won't be executed if a test fails and the function exits early.


Move the teardown logic to a defer statement at the beginning of the function, as suggested in the first comment, and remove these lines.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d8a3d9f32fdba913faeda912d0196388c74d81fc and 9217b1633de2db40416c2b78c2208cba0639aa47.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `xrpl/transaction/integration/credential_create.go` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (2)</summary>

<details>
<summary>📓 Common learnings</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

</details>
<details>
<summary>xrpl/transaction/integration/credential_create.go (2)</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: The CredentialCreate struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Lint and Test

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>xrpl/transaction/integration/credential_create.go (1)</summary>

`1-72`: <details>
<summary>✅ Verification successful</summary>

**Overall feedback: Good integration test implementation**

The integration test for CredentialCreate transaction is well-structured and follows good testing practices. It correctly sets up the test environment, validates the transaction structure, and verifies successful transaction submission through the websocket client.



---

🏁 Script executed:

```shell
#!/bin/bash
# Check for other integration tests to understand the normal pattern
echo "Checking other integration test files for comparison..."
fd -e go -p "integration.*test" | head -n 5
```

Length of output: 211

---



</details>

**Verified: CredentialCreate Integration Test Looks Solid**

The integration test in `xrpl/transaction/integration/credential_create.go` adheres to the established test patterns (as also seen in `xrpl/transaction/integration/payment_test.go`) and demonstrates good practices in setting up the environment, validating the transaction, and executing the WebSocket submission tests.

- Proper setup and teardown are implemented.
- Transaction validation and flattening logic are correctly exercised.
- The test structure aligns with other integration tests in the repository.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T09:54:45Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T09:58:48Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T09:59:10Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>xrpl/transaction/credential_create.go (2)</summary><blockquote>

`68-89`: **Consider enhancing URI validation for hex string format.**

The Validate method correctly checks all required fields, but could be improved:

1. The URI validation only checks for length constraints but not that it's a valid hex string as mentioned in the error message.

2. Consider adding validation to ensure the URI is actually a valid hex string, using a regex pattern or conversion function.


```diff
func (c *CredentialCreate) Validate() (bool, error) {
	// validate the base transaction
	_, err := c.BaseTx.Validate()
	if err != nil {
		return false, err
	}

	if !addresscodec.IsValidAddress(c.Subject.String()) {
		return false, ErrInvalidSubject
	}

	if !c.CredentialType.IsValid() {
		return false, ErrInvalidCredentialType
	}

	if c.URI != "" {
-		if len(c.URI) < MinURILength || len(c.URI) > MaxURILength {
+		if len(c.URI) < MinURILength || len(c.URI) > MaxURILength || !isValidHexString(c.URI) {
			return false, ErrInvalidCredentialURI
		}
	}

	return true, nil
}

// Helper function to validate hex string
+func isValidHexString(s string) bool {
+	// Each byte is represented by 2 hex characters
+	if len(s)%2 != 0 {
+		return false
+	}
+	
+	// Check if all characters are valid hex
+	for _, r := range s {
+		if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) {
+			return false
+		}
+	}
+	return true
+}
```

---

`36-37`: **Consider adding context for "Ripple Epoch" in documentation.**

The comment for the Expiration field mentions "Ripple Epoch" without explaining what this reference point is. Consider adding a brief explanation or reference to documentation about this epoch to help developers who might not be familiar with XRPL-specific time references.


```diff
// Time after which this credential expires, in seconds since the Ripple Epoch.
+ // The Ripple Epoch is January 1, 2000 (00:00 UTC), represented as Unix time 946684800.
Expiration uint32 `json:",omitempty"`
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b40cfaa72d79e1d8abd7aca01a7981be2046acf0 and 6ab3405ec176c78dff50418c371df123089afe43.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `xrpl/transaction/credential_create.go` (1 hunks)
* `xrpl/transaction/credential_delete.go` (1 hunks)
* `xrpl/transaction/errors.go` (2 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* xrpl/transaction/errors.go
* xrpl/transaction/credential_delete.go

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (2)</summary>

<details>
<summary>📓 Common learnings</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

</details>
<details>
<summary>xrpl/transaction/credential_create.go (3)</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: The CredentialCreate struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:58.330Z
Learning: In the PR #113 for xrpl-go, the initial flagging of duplicated fields (`CredentialType` and `Expiration`) in the `xrpl/transaction/credential_create.go` file was determined to be irrelevant and not an actual issue.
```

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Lint and Test

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>xrpl/transaction/credential_create.go (4)</summary>

`10-21`: **Well-defined error and constants, in adherence with best practices.**

The error and constants are properly defined with clear documentation. Following the suggestion to wrap constants in a single block maintains consistency with the codebase's style.

---

`23-41`: **Clear struct definition with well-documented fields.**

The `CredentialCreate` struct is well-structured with appropriate field types and documentation. The optional fields are correctly marked with JSON omitempty tags.

---

`43-66`: **Correctly implemented TxType and Flatten methods.**

Both methods follow the established patterns for transaction types in this codebase. The Flatten method properly handles conditional fields by only including them when they have values.

---

`1-90`: **Overall, this is a robust implementation of the CredentialCreate transaction type.**

The implementation follows the established patterns in the codebase with proper error handling, validation, and structure. It correctly implements all the required functionality for creating a credential in the ledger, with appropriate validations for all fields.

The code is well-documented and follows good Go coding practices.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-12T10:05:57Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-14T12:22:47Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-14T12:23:42Z
user: login: GuillemGarciaDev
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-17T08:47:04Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T12:00:50Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T12:06:39Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T12:07:45Z
user: login: florent-uzio
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-18T12:22:08Z
user: login: GuillemGarciaDev
state: APPROVED
author_association: NONE
submitted_at: 2025-03-19T08:51:01Z

Review comments:
diff_hunk: @@ -11,6 +11,10 @@ var (
 	ErrInvalidAccount = errors.New("invalid xrpl address for Account")
 	// ErrInvalidCheckID is returned when the CheckID is not a valid 64-character hexadecimal string.
 	ErrInvalidCheckID = errors.New("invalid CheckID, must be a valid 64-character hexadecimal string")
+	// ErrInvalidCredentialType is returned when the CredentialType is not a valid hexadecimal string between 1 and 64 bytes.
+	ErrInvalidCredentialType = errors.New("invalid credential type, must be an hexadecimal between 1 and 64 bytes")
path: xrpl/transaction/errors.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Inconsistency in error message for ErrInvalidCredentialType**

The error message states "must be an hexadecimal between 1 and 64 bytes", but there are two issues:

1. There's a grammatical error: "an hexadecimal" should be "a hexadecimal"
2. The constants in `common/constants.go` define the range as 2-128 hex characters (equivalent to 1-64 bytes)

The error message should be consistent with how the validation is performed in `IsValidCredentialType`.


```diff
-	// ErrInvalidCredentialType is returned when the CredentialType is not a valid hexadecimal string between 1 and 64 bytes.
-	ErrInvalidCredentialType = errors.New("invalid credential type, must be an hexadecimal between 1 and 64 bytes")
+	// ErrInvalidCredentialType is returned when the CredentialType is not a valid hexadecimal string between 1 and 64 bytes.
+	ErrInvalidCredentialType = errors.New("invalid credential type, must be a hexadecimal string between 1 and 64 bytes")
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	// ErrInvalidCredentialType is returned when the CredentialType is not a valid hexadecimal string between 1 and 64 bytes.
	ErrInvalidCredentialType = errors.New("invalid credential type, must be a hexadecimal string between 1 and 64 bytes")
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-11T09:51:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+package transaction
+
+import (
+	addresscodec "github.com/Peersyst/xrpl-go/address-codec"
+	"github.com/Peersyst/xrpl-go/xrpl/common"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+)
+
+// Minimum length of a credential type is 1 byte (1 byte = 2 hex characters).
+const MinURILength = 2
+
+// Maximum of 256 bytes for the URI field (1 byte = 2 hex characters)
+const MaxURILength = 512
+
+// A CredentialCreate transaction creates a credential in the ledger.
+// The issuer of the credential uses this transaction to provisionally issue a credential.
+// The credential is not valid until the subject of the credential accepts it with a CredentialAccept transaction.
+type CredentialCreate struct {
+	// Base transaction fields
+	BaseTx
+
+	// The subject of the credential.
+	Subject types.Address
+
+	// Arbitrary data defining the type of credential this entry represents. The minimum length is 1 byte and the maximum length is 64 bytes.
+	CredentialType types.CredentialType
+
+	// Time after which this credential expires, in seconds since the Ripple Epoch.
+	Expiration uint32 `json:",omitempty"`
+
+	// Arbitrary additional data about the credential, such as the URL where users can look up an associated Verifiable Credential document. If present, the minimum length is 1 byte and the maximum is 256 bytes.
+	URI string `json:",omitempty"`
+}
+
path: xrpl/transaction/credential_create.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Remove duplicated fields.**

`CredentialType` and `Expiration` are duplicated at lines 30–34. This likely results from a merge conflict and will cause compile-time errors. Remove the duplicated fields to keep just one definition of each.


```diff
-	CredentialType types.CredentialType
-
-	// Time after which this credential expires, in seconds since the Ripple Epoch.
-	Expiration uint32 `json:",omitempty"`
```


> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-11T09:51:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+package transaction
+
+import (
+	addresscodec "github.com/Peersyst/xrpl-go/address-codec"
+	"github.com/Peersyst/xrpl-go/xrpl/common"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+)
+
+// Minimum length of a credential type is 1 byte (1 byte = 2 hex characters).
+const MinURILength = 2
+
+// Maximum of 256 bytes for the URI field (1 byte = 2 hex characters)
+const MaxURILength = 512
+
+// A CredentialCreate transaction creates a credential in the ledger.
+// The issuer of the credential uses this transaction to provisionally issue a credential.
+// The credential is not valid until the subject of the credential accepts it with a CredentialAccept transaction.
+type CredentialCreate struct {
+	// Base transaction fields
+	BaseTx
+
+	// The subject of the credential.
+	Subject types.Address
+
+	// Arbitrary data defining the type of credential this entry represents. The minimum length is 1 byte and the maximum length is 64 bytes.
+	CredentialType types.CredentialType
+
+	// Time after which this credential expires, in seconds since the Ripple Epoch.
+	Expiration uint32 `json:",omitempty"`
+
+	// Arbitrary additional data about the credential, such as the URL where users can look up an associated Verifiable Credential document. If present, the minimum length is 1 byte and the maximum is 256 bytes.
+	URI string `json:",omitempty"`
+}
+
path: xrpl/transaction/credential_create.go
user: login: florent-uzio
body: Not relevant
created_at: 2025-03-11T11:50:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+package transaction
+
+import (
+	addresscodec "github.com/Peersyst/xrpl-go/address-codec"
+	"github.com/Peersyst/xrpl-go/xrpl/common"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+)
+
+// Minimum length of a credential type is 1 byte (1 byte = 2 hex characters).
+const MinURILength = 2
+
+// Maximum of 256 bytes for the URI field (1 byte = 2 hex characters)
+const MaxURILength = 512
+
+// A CredentialCreate transaction creates a credential in the ledger.
+// The issuer of the credential uses this transaction to provisionally issue a credential.
+// The credential is not valid until the subject of the credential accepts it with a CredentialAccept transaction.
+type CredentialCreate struct {
+	// Base transaction fields
+	BaseTx
+
+	// The subject of the credential.
+	Subject types.Address
+
+	// Arbitrary data defining the type of credential this entry represents. The minimum length is 1 byte and the maximum length is 64 bytes.
+	CredentialType types.CredentialType
+
+	// Time after which this credential expires, in seconds since the Ripple Epoch.
+	Expiration uint32 `json:",omitempty"`
+
+	// Arbitrary additional data about the credential, such as the URL where users can look up an associated Verifiable Credential document. If present, the minimum length is 1 byte and the maximum is 256 bytes.
+	URI string `json:",omitempty"`
+}
+
path: xrpl/transaction/credential_create.go
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

@florent-uzio I apologize for the confusion in my previous comment. After checking the file, I can see that my assessment about duplicated fields was incorrect. Thank you for pointing out that it's not relevant to this PR.



---

🏁 Script executed:
```
#!/bin/bash
# Checking the full content of the credential_create.go file
cat xrpl/transaction/credential_create.go
```

Length of output: 2545

---

@florent-uzio I apologize for my error. After reviewing the complete file, I can confirm there are no duplicated fields in the `CredentialCreate` struct. You were absolutely right that my comment was not relevant. Thank you for the correction.



---

🏁 Script executed:
```
#!/bin/bash
# Let's check if there are any duplicated field declarations in the file
grep -A 20 "type CredentialCreate struct" xrpl/transaction/credential_create.go
```

Length of output: 894

---



</details>

@florent-uzio I apologize for my mistake. After thoroughly reviewing the file structure, I confirm there are no duplicated fields in the `CredentialCreate` struct as I incorrectly suggested. Your response "Not relevant" was absolutely correct. Thank you for pointing this out.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:57.792Z
Learning: In the PR #113 for xrpl-go, the initial flagging of duplicated fields (`CredentialType` and `Expiration`) in the `xrpl/transaction/credential_create.go` file was determined to be irrelevant and not an actual issue.
```

---

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:57.792Z
Learning: The CredentialCreate struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

---

```
Learnt from: florent-uzio
PR: Peersyst/xrpl-go#113
File: xrpl/transaction/credential_create.go:30-34
Timestamp: 2025-03-11T11:50:57.792Z
Learning: In PR #113 for xrpl-go, the `CredentialCreate` struct in xrpl/transaction/credential_create.go does not have any duplicated fields as was incorrectly flagged in a previous review comment.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-11T11:51:04Z
author_association: NONE
diff_hunk: @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [v0.1.8]
path: CHANGELOG.md
user: login: GuillemGarciaDev
body: instead of specifying the next version number, add the `Unreleased` tag
created_at: 2025-03-11T14:28:20Z
author_association: NONE
diff_hunk: @@ -0,0 +1,24 @@
+package common
+
+import (
+	"github.com/Peersyst/xrpl-go/pkg/typecheck"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+)
+
+// IsValidCredentialType checks if a credential type meets all the requirements:
+// - Not empty
+// - Valid hex string
+// - Length between MinCredentialTypeLength and MaxCredentialTypeLength
+func IsValidCredentialType(credType types.CredentialType) bool {
path: xrpl/common/credential.go
user: login: GuillemGarciaDev
body: This validation method should be called directly from the `CredentialType` imo.

It would be easier to validate the CredentialType.

```go
valid := credentialType.IsValid()
if !valid {
// ...
}
```
created_at: 2025-03-11T14:36:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,50 @@
+package common
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIsValidCredentialType(t *testing.T) {
path: xrpl/common/credential_test.go
user: login: GuillemGarciaDev
body: Move to ledger-entry-types/credential
created_at: 2025-03-11T14:37:27Z
author_association: NONE
diff_hunk: @@ -16,4 +16,10 @@ const (
 
 	// 5 seconds default timeout
 	DefaultTimeout = 5 * time.Second
+
+	// Transaction constants
+	// Minimum length of a credential type is 1 byte (1 byte = 2 hex characters).
+	MinCredentialTypeLength = 2
path: xrpl/common/constants.go
user: login: GuillemGarciaDev
body: Move this constants to the credential ledger-entry-type
created_at: 2025-03-11T14:41:13Z
author_association: NONE
diff_hunk: @@ -0,0 +1,50 @@
+package ledger
+
+import "github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+
+const (
+	// If enabled, the subject of the credential has accepted the credential.
+	// Otherwise, the issuer created the credential but the subject has not yet accepted it, meaning it is not yet valid.
+	lsfAccepted uint32 = 0x00010000
+)
+
+// A Credential entry represents a credential, which contains an attestation about a subject account from a credential issuer account.
+// The meaning of the attestation is defined by the issuer.
+// Requires the Credentials amendment to be enabled: https://xrpl.org/resources/known-amendments#credentials
+type Credential struct {
+	// The unique ID for this ledger entry. In JSON, this field is represented with different names depending on the
+	// context and API method. (Note, even though this is specified as "optional" in the code, every ledger entry
+	// should have one unless it's legacy data from very early in the XRP Ledger's history.)
+	Index types.Hash256 `json:"index,omitempty"`
+	// The value 0x0044, mapped to the string Credential, indicates that this is a Credential ledger entry.
+	LedgerEntryType EntryType
+	// Set of bit-flags for this ledger entry.
+	Flags uint32
+	// Arbitrary data defining the type of credential this entry represents. The minimum length is 1 byte and the maximum length is 64 bytes.
+	CredentialType types.CredentialType
+	// Time after which the credential is expired, in seconds since the Ripple Epoch.
+	Expiration uint32 `json:",omitempty"`
+	// The account that issued the credential.
+	Issuer types.Address
+	// A hint indicating which page of the issuer's directory links to this entry, in case the directory consists of multiple pages.
+	IssuerNode string
+	// The identifying hash of the transaction that most recently modified this entry.
+	PreviousTxnID types.Hash256
+	// The index of the ledger that contains the transaction that most recently modified this entry.
+	PreviousTxnLgrSeq uint32
+	// The account that this credential is for.
+	Subject types.Address
+	// A hint indicating which page of the subject's owner directory links to this entry, in case the directory consists of multiple pages.
+	SubjectNode string
+	// Arbitrary additional data about the credential, for example a URL where a W3C-formatted Verifiable Credential can be retrieved.
+	URI string `json:",omitempty"`
+}
+
+func (*Credential) EntryType() EntryType {
+	return CredentialEntry
+}
+
+// SetLsfAccepted sets the one owner count flag.
+func (c *Credential) SetLsfAccepted() {
+	c.Flags |= lsfAccepted
+}
path: xrpl/ledger-entry-types/credential.go
user: login: GuillemGarciaDev
body: Add here `IsValid` or `Validate` method
created_at: 2025-03-11T14:47:28Z
author_association: NONE
diff_hunk: @@ -0,0 +1,54 @@
+package ledger
+
+import (
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/testutil"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCredential_EntryType(t *testing.T) {
+	credential := &Credential{}
+	assert.Equal(t, credential.EntryType(), CredentialEntry)
path: xrpl/ledger-entry-types/credential_test.go
user: login: GuillemGarciaDev
body: To standarize the code, let's use `require` instead of `assert`
created_at: 2025-03-11T14:47:56Z
author_association: NONE
diff_hunk: @@ -0,0 +1,50 @@
+package common
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIsValidCredentialType(t *testing.T) {
+	tests := []struct {
+		name     string
+		credType types.CredentialType
+		expected bool
+	}{
+		{
+			name:     "empty string",
path: xrpl/common/credential_test.go
user: login: GuillemGarciaDev
body: Add `pass` and `fail` preffixes
created_at: 2025-03-11T14:48:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,170 @@
+package transaction
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/common"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCredentialAccept_TxType(t *testing.T) {
+	tx := &CredentialAccept{}
+	assert.Equal(t, CredentialAcceptTx, tx.TxType())
+}
+
+func TestCredentialAccept_Flatten(t *testing.T) {
path: xrpl/transaction/credential_accept_test.go
user: login: GuillemGarciaDev
body: Should be table driven tests
created_at: 2025-03-11T14:54:21Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+package transaction
+
+import (
+	addresscodec "github.com/Peersyst/xrpl-go/address-codec"
+	"github.com/Peersyst/xrpl-go/xrpl/common"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+)
+
+// Minimum length of a credential type is 1 byte (1 byte = 2 hex characters).
+const MinURILength = 2
path: xrpl/transaction/credential_create.go
user: login: GuillemGarciaDev
body: Wrap both constants into a single `const ( ... )` to maintain code structure
created_at: 2025-03-11T14:55:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,140 @@
+package transaction
+
+import (
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCredentialDelete_TxType(t *testing.T) {
+	tx := &CredentialDelete{}
+	assert.Equal(t, CredentialDeleteTx, tx.TxType())
+}
+
+func TestCredentialDelete_Flatten(t *testing.T) {
+	tx := &CredentialDelete{
path: xrpl/transaction/credential_delete_test.go
user: login: GuillemGarciaDev
body: table drive test
created_at: 2025-03-11T14:57:40Z
author_association: NONE
diff_hunk: @@ -0,0 +1,24 @@
+package common
+
+import (
+	"github.com/Peersyst/xrpl-go/pkg/typecheck"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+)
+
+// IsValidCredentialType checks if a credential type meets all the requirements:
+// - Not empty
+// - Valid hex string
+// - Length between MinCredentialTypeLength and MaxCredentialTypeLength
+func IsValidCredentialType(credType types.CredentialType) bool {
path: xrpl/common/credential.go
user: login: florent-uzio
body: Agree, good point, I will make the change
created_at: 2025-03-12T08:43:58Z
author_association: NONE
diff_hunk: @@ -0,0 +1,50 @@
+package ledger
+
+import "github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+
+const (
+	// If enabled, the subject of the credential has accepted the credential.
+	// Otherwise, the issuer created the credential but the subject has not yet accepted it, meaning it is not yet valid.
+	lsfAccepted uint32 = 0x00010000
+)
+
+// A Credential entry represents a credential, which contains an attestation about a subject account from a credential issuer account.
+// The meaning of the attestation is defined by the issuer.
+// Requires the Credentials amendment to be enabled: https://xrpl.org/resources/known-amendments#credentials
+type Credential struct {
+	// The unique ID for this ledger entry. In JSON, this field is represented with different names depending on the
+	// context and API method. (Note, even though this is specified as "optional" in the code, every ledger entry
+	// should have one unless it's legacy data from very early in the XRP Ledger's history.)
+	Index types.Hash256 `json:"index,omitempty"`
+	// The value 0x0044, mapped to the string Credential, indicates that this is a Credential ledger entry.
+	LedgerEntryType EntryType
+	// Set of bit-flags for this ledger entry.
+	Flags uint32
+	// Arbitrary data defining the type of credential this entry represents. The minimum length is 1 byte and the maximum length is 64 bytes.
+	CredentialType types.CredentialType
+	// Time after which the credential is expired, in seconds since the Ripple Epoch.
+	Expiration uint32 `json:",omitempty"`
+	// The account that issued the credential.
+	Issuer types.Address
+	// A hint indicating which page of the issuer's directory links to this entry, in case the directory consists of multiple pages.
+	IssuerNode string
+	// The identifying hash of the transaction that most recently modified this entry.
+	PreviousTxnID types.Hash256
+	// The index of the ledger that contains the transaction that most recently modified this entry.
+	PreviousTxnLgrSeq uint32
+	// The account that this credential is for.
+	Subject types.Address
+	// A hint indicating which page of the subject's owner directory links to this entry, in case the directory consists of multiple pages.
+	SubjectNode string
+	// Arbitrary additional data about the credential, for example a URL where a W3C-formatted Verifiable Credential can be retrieved.
+	URI string `json:",omitempty"`
+}
+
+func (*Credential) EntryType() EntryType {
+	return CredentialEntry
+}
+
+// SetLsfAccepted sets the one owner count flag.
+func (c *Credential) SetLsfAccepted() {
+	c.Flags |= lsfAccepted
+}
path: xrpl/ledger-entry-types/credential.go
user: login: florent-uzio
body: Why do we need such method here? I can see there is no `IsValid` or `Validate` method in the other ledger entry types @GuillemGarciaDev 
created_at: 2025-03-12T08:54:14Z
author_association: NONE
diff_hunk: @@ -0,0 +1,50 @@
+package common
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestIsValidCredentialType(t *testing.T) {
+	tests := []struct {
+		name     string
+		credType types.CredentialType
+		expected bool
+	}{
+		{
+			name:     "empty string",
path: xrpl/common/credential_test.go
user: login: florent-uzio
body: ah right, forgot to add them here. Thanks for the catch
created_at: 2025-03-12T08:56:31Z
author_association: NONE
diff_hunk: @@ -0,0 +1,69 @@
+package integration
+
+import (
+	"encoding/hex"
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/testutil/integration"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/results"
+	"github.com/Peersyst/xrpl-go/xrpl/websocket"
+	"github.com/stretchr/testify/require"
+)
+
+type CredentialCreateTest struct {
+	Name             string
+	CredentialCreate *transaction.CredentialCreate
+	ExpectedError    string
+}
+
+func TestIntegrationCredentialCreate_Websocket(t *testing.T) {
path: xrpl/transaction/integration/credential_create.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Fix function naming to follow Go conventions.**

According to the linter error, function names in Go should not use underscores.


```diff
-func TestIntegrationCredentialCreate_Websocket(t *testing.T) {
+func TestIntegrationCredentialCreateWebsocket(t *testing.T) {
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
func TestIntegrationCredentialCreateWebsocket(t *testing.T) {
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: XRPL-GO Lint and Test</summary>

[error] 20-20: var-naming: don't use underscores in Go names; func TestIntegrationCredentialCreate_Websocket should be TestIntegrationCredentialCreateWebsocket (revive)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-12T09:50:20Z
author_association: NONE
diff_hunk: @@ -0,0 +1,69 @@
+package integration
+
+import (
+	"encoding/hex"
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/testutil/integration"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/results"
+	"github.com/Peersyst/xrpl-go/xrpl/websocket"
+	"github.com/stretchr/testify/require"
+)
+
+type CredentialCreateTest struct {
+	Name             string
+	CredentialCreate *transaction.CredentialCreate
+	ExpectedError    string
+}
+
+func TestIntegrationCredentialCreate_Websocket(t *testing.T) {
+	env := integration.GetWebsocketEnv(t)
+	client := websocket.NewClient(websocket.NewClientConfig().WithHost(env.Host).WithFaucetProvider(env.FaucetProvider))
+
+	runner := integration.NewRunner(t, client, &integration.RunnerConfig{
+		WalletCount: 1,
+	})
+
+	err := runner.Setup()
+	require.NoError(t, err)
+	defer runner.Teardown()
path: xrpl/transaction/integration/credential_create.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Check the error returned by runner.Teardown().**

The linter indicates that the error return value of `runner.Teardown()` is not being checked.


```diff
-defer runner.Teardown()
+defer func() {
+	err := runner.Teardown()
+	if err != nil {
+		t.Logf("Error during teardown: %v", err)
+	}
+}()
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	defer func() {
		err := runner.Teardown()
		if err != nil {
			t.Logf("Error during teardown: %v", err)
		}
	}()
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: XRPL-GO Lint and Test</summary>

[error] 30-30: Error return value of `runner.Teardown` is not checked (errcheck)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-12T09:50:20Z
author_association: NONE
diff_hunk: @@ -13,6 +13,7 @@ const (
 	AMMEntry                             EntryType = "AMM"
 	BridgeEntry                          EntryType = "Bridge"
 	CheckEntry                           EntryType = "Check"
+	CredentialEntry                      EntryType = "Credential"
path: xrpl/ledger-entry-types/ledger_object.go
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Handle `CredentialEntry` in `EmptyLedgerObject` and `UnmarshalLedgerObject`**  
Currently, the switch statements in `EmptyLedgerObject()` (lines 47–94) and `UnmarshalLedgerObject()` (lines 108–150) do not include a case for `CredentialEntry`. This will cause the code to reject or fail to parse ledger objects of type `"Credential"`, returning an error.  


```diff
 case XChainOwnedCreateAccountClaimIDEntry:
 	return &XChainOwnedCreateAccountClaimID{}, nil
+case CredentialEntry:
+	return &Credential{}, nil
 }
 
 return nil, fmt.Errorf("unrecognized LedgerObject type \"%s\"", t)
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
 case XChainOwnedCreateAccountClaimIDEntry:
 	return &XChainOwnedCreateAccountClaimID{}, nil
+case CredentialEntry:
+	return &Credential{}, nil
 }
 
 return nil, fmt.Errorf("unrecognized LedgerObject type \"%s\"", t)
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-12T09:50:20Z
author_association: NONE
diff_hunk: @@ -0,0 +1,69 @@
+package integration
+
+import (
+	"encoding/hex"
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/testutil/integration"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/results"
+	"github.com/Peersyst/xrpl-go/xrpl/websocket"
+	"github.com/stretchr/testify/require"
+)
+
+type CredentialCreateTest struct {
+	Name             string
+	CredentialCreate *transaction.CredentialCreate
+	ExpectedError    string
+}
+
+func TestIntegrationCredentialCreate_Websocket(t *testing.T) {
path: xrpl/transaction/integration/credential_create.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix function naming to follow Go conventions**

Function names in Go should not use underscores. Rename the function to follow Go naming conventions.


```diff
-func TestIntegrationCredentialCreate_Websocket(t *testing.T) {
+func TestIntegrationCredentialCreateWebsocket(t *testing.T) {
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
func TestIntegrationCredentialCreateWebsocket(t *testing.T) {
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: XRPL-GO Lint and Test</summary>

[error] 20-20: var-naming: don't use underscores in Go names; func TestIntegrationCredentialCreate_Websocket should be TestIntegrationCredentialCreateWebsocket (revive)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-12T09:52:37Z
author_association: NONE
diff_hunk: @@ -0,0 +1,69 @@
+package integration
+
+import (
+	"encoding/hex"
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/testutil/integration"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/results"
+	"github.com/Peersyst/xrpl-go/xrpl/websocket"
+	"github.com/stretchr/testify/require"
+)
+
+type CredentialCreateTest struct {
+	Name             string
+	CredentialCreate *transaction.CredentialCreate
+	ExpectedError    string
+}
+
+func TestIntegrationCredentialCreate_Websocket(t *testing.T) {
+	env := integration.GetWebsocketEnv(t)
+	client := websocket.NewClient(websocket.NewClientConfig().WithHost(env.Host).WithFaucetProvider(env.FaucetProvider))
+
+	runner := integration.NewRunner(t, client, &integration.RunnerConfig{
+		WalletCount: 1,
+	})
+
+	err := runner.Setup()
+	require.NoError(t, err)
+	defer runner.Teardown()
path: xrpl/transaction/integration/credential_create.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Check error return value from Teardown**

The error return value from `runner.Teardown()` is not checked. You should handle this error according to your error handling strategy.


```diff
-	defer runner.Teardown()
+	defer func() {
+		err := runner.Teardown()
+		if err != nil {
+			t.Logf("Error during teardown: %v", err)
+		}
+	}()
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	defer func() {
		err := runner.Teardown()
		if err != nil {
			t.Logf("Error during teardown: %v", err)
		}
	}()
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 GitHub Actions: XRPL-GO Lint and Test</summary>

[error] 30-30: Error return value of `runner.Teardown` is not checked (errcheck)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-12T09:52:37Z
author_association: NONE
diff_hunk: @@ -13,6 +13,7 @@ const (
 	AMMEntry                             EntryType = "AMM"
 	BridgeEntry                          EntryType = "Bridge"
 	CheckEntry                           EntryType = "Check"
+	CredentialEntry                      EntryType = "Credential"
path: xrpl/ledger-entry-types/ledger_object.go
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Make sure to update the `EmptyLedgerObject` and `UnmarshalLedgerObject` functions for the new entry type.**

You've added the `CredentialEntry` to the list of entry types, but I don't see it included in the `EmptyLedgerObject` function (lines 46-94) or the `UnmarshalLedgerObject` function (lines 96-158). These functions need to be updated to handle the new entry type for full compatibility.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there's a Credential struct that needs to be returned in these functions
rg -A 3 "type Credential struct" --type go
```

Length of output: 536

---



</details>

**Action Required: Add Credential Handling in Ledger Object Functions**

- The new `CredentialEntry` is now defined in `xrpl/ledger-entry-types/ledger_object.go` (line 16) and its corresponding `Credential` struct is located in `xrpl/ledger-entry-types/credential.go`.
- However, the functions `EmptyLedgerObject` (lines 46–94) and `UnmarshalLedgerObject` (lines 96–158) currently lack support for this new entry type.
- **Please update both functions to include logic for initializing and unmarshaling the `CredentialEntry` so that the new ledger entry type is fully supported.**

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-12T09:52:38Z
author_association: NONE
diff_hunk: @@ -11,6 +11,10 @@ var (
 	ErrInvalidAccount = errors.New("invalid xrpl address for Account")
 	// ErrInvalidCheckID is returned when the CheckID is not a valid 64-character hexadecimal string.
 	ErrInvalidCheckID = errors.New("invalid CheckID, must be a valid 64-character hexadecimal string")
+	// ErrInvalidCredentialType is returned when the CredentialType is not a valid hexadecimal string between 1 and 64 bytes.
+	ErrInvalidCredentialType = errors.New("invalid credential type, must be a hexadecimal string between 1 and 64 bytes")
+	// ErrInvalidCredentialURI is returned when the URI field does not meet the maximum length of 256 bytes.
+	ErrInvalidCredentialURI = errors.New("credential create: invalid URI, must have a maximum length of 256 bytes")
path: xrpl/transaction/errors.go
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Ensure error message is consistent with validation logic.**

Make sure the maximum length of 256 bytes for the URI field is enforced in the validation code for credential transactions.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for validation logic related to credential URI length
rg "ErrInvalidCredentialURI|256 bytes" --type go -A 5 -B 5
```

Length of output: 11747

---



</details>

**Action Required: Update Credential URI Error Message Consistency**

- The validation logic in `xrpl/transaction/credential_create.go` enforces that the URI field must be between 2 and 512 characters long. The comment there explains that 1 byte is represented by 2 hex characters (i.e. 256 bytes → 512 hex characters).
- The current error message in `xrpl/transaction/errors.go` states "must have a maximum length of 256 bytes," which can be misleading because the check is performed on the hex-encoded string length.
- **Recommendation:** Update the error message to explicitly mention the maximum hex string length (512 characters) along with its byte-equivalent (256 bytes) for clarity.

```diff
-// ErrInvalidCredentialURI is returned when the URI field does not meet the maximum length of 256 bytes.
-ErrInvalidCredentialURI = errors.New("credential create: invalid URI, must have a maximum length of 256 bytes")
+// ErrInvalidCredentialURI is returned when the URI field does not meet the maximum allowed hex-encoded length of 512 characters (256 bytes).
+ErrInvalidCredentialURI = errors.New("credential create: invalid URI, must have a maximum hex string length of 512 characters (256 bytes)")
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	// ErrInvalidCredentialURI is returned when the URI field does not meet the maximum allowed hex-encoded length of 512 characters (256 bytes).
	ErrInvalidCredentialURI = errors.New("credential create: invalid URI, must have a maximum hex string length of 512 characters (256 bytes)")
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-12T09:52:38Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+package types
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCredentialType_String(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    CredentialType
+		expected string
+	}{
+		{
+			name:     "pass - valid CredentialType",
+			input:    CredentialType("my_credential"),
+			expected: "my_credential",
+		},
+		{
+			name:     "pass - empty URI",
+			input:    CredentialType(""),
+			expected: "",
+		},
+		{
+			name:     "pass - CredentialType with special characters",
+			input:    CredentialType("https://example.com/nft/1?query=param&another=param"),
+			expected: "https://example.com/nft/1?query=param&another=param",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.input.String(); got != tt.expected {
+				t.Errorf("CredentialType.String(), got: %v but we want: %v", got, tt.expected)
+			}
+		})
+	}
+}
path: xrpl/transaction/types/credential_type_test.go
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Inconsistency between test cases and validation logic.**

In the `TestCredentialType_String` test, you're using non-hex strings like "my_credential" (line 18), but the `IsValid` method seems to expect hex strings based on the `TestCredentialType_IsValid` test. Make sure your test cases accurately reflect the expected behavior of the type.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check the implementation of CredentialType.IsValid to understand validation requirements
rg "func \(.*CredentialType\) IsValid\(\)" --type go -A 10
```

Length of output: 716

---



</details>

**Attention: Inconsistency Between Test Cases and Validation Logic**

The `TestCredentialType_String` tests are currently using values such as `"my_credential"` and a URL, which would fail validation in the `CredentialType.IsValid` method—this method returns false unless the string is a valid hex (as indicated by the call to `typecheck.IsHex`). Please clarify the intended usage of `CredentialType`:

- **If `CredentialType` is meant to represent only hex strings:**  
  Update the test cases in `xrpl/transaction/types/credential_type_test.go` (lines 10–40) to supply valid hex strings, ensuring consistency across tests.

- **If arbitrary strings are acceptable for display purposes:**  
  Consider revising the validation logic in `CredentialType.IsValid` to accommodate non-hex values or document the separate responsibilities of string formatting versus validation.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-12T09:52:38Z
author_association: NONE
diff_hunk: @@ -0,0 +1,67 @@
+package transaction
+
+import (
+	addresscodec "github.com/Peersyst/xrpl-go/address-codec"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+)
+
+// A CredentialDelete transaction removes a credential from the ledger, effectively revoking it.
+// Users may also want to delete an unwanted credential to reduce their reserve requirement.
+type CredentialDelete struct {
+	BaseTx
+
+	// Arbitrary data defining the type of credential to delete. The minimum length is 1 byte and the maximum length is 256 bytes.
+	CredentialType types.CredentialType
+
path: xrpl/transaction/credential_delete.go
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix mismatch in doc comment vs. actual max length.**  
The doc here states a maximum of “256 bytes,” but `CredentialType` only allows up to 64 bytes (128 hex characters). This inconsistency can cause confusion or incorrect usage.  

Please update the comment to align with the enforced 64-byte max. For example:
```diff
-	// Arbitrary data defining the type of credential to delete. The minimum length is 1 byte and the maximum length is 256 bytes.
+	// Arbitrary data defining the type of credential to delete. The minimum length is 1 byte and the maximum length is 64 bytes (128 hex characters).
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
	// Arbitrary data defining the type of credential to delete. The minimum length is 1 byte and the maximum length is 64 bytes (128 hex characters).
	CredentialType types.CredentialType
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-12T09:52:38Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+package types
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCredentialType_String(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    CredentialType
+		expected string
+	}{
+		{
+			name:     "pass - valid CredentialType",
+			input:    CredentialType("my_credential"),
+			expected: "my_credential",
+		},
+		{
+			name:     "pass - empty URI",
+			input:    CredentialType(""),
+			expected: "",
+		},
+		{
+			name:     "pass - CredentialType with special characters",
+			input:    CredentialType("https://example.com/nft/1?query=param&another=param"),
+			expected: "https://example.com/nft/1?query=param&another=param",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.input.String(); got != tt.expected {
+				t.Errorf("CredentialType.String(), got: %v but we want: %v", got, tt.expected)
+			}
+		})
+	}
+}
path: xrpl/transaction/types/credential_type_test.go
user: login: florent-uzio
body: Not relevant here
created_at: 2025-03-12T09:58:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,81 @@
+package types
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCredentialType_String(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    CredentialType
+		expected string
+	}{
+		{
+			name:     "pass - valid CredentialType",
+			input:    CredentialType("my_credential"),
+			expected: "my_credential",
+		},
+		{
+			name:     "pass - empty URI",
+			input:    CredentialType(""),
+			expected: "",
+		},
+		{
+			name:     "pass - CredentialType with special characters",
+			input:    CredentialType("https://example.com/nft/1?query=param&another=param"),
+			expected: "https://example.com/nft/1?query=param&another=param",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := tt.input.String(); got != tt.expected {
+				t.Errorf("CredentialType.String(), got: %v but we want: %v", got, tt.expected)
+			}
+		})
+	}
+}
path: xrpl/transaction/types/credential_type_test.go
user: login: coderabbitai[bot]
body: @florent-uzio I apologize for the confusion. You're right - my previous comment was not relevant in this context. The test is correctly focused on the `String()` method's functionality (string representation) rather than validation logic. Thank you for the clarification.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-12T09:59:10Z
author_association: NONE
diff_hunk: @@ -30,34 +30,35 @@
     "Metadata": 10004
   },
   "LEDGER_ENTRY_TYPES": {
-    "Invalid": -1,
     "AccountRoot": 97,
-    "DirectoryNode": 100,
-    "RippleState": 114,
-    "Ticket": 84,
-    "SignerList": 83,
-    "Offer": 111,
-    "Bridge": 105,
-    "LedgerHashes": 104,
     "Amendments": 102,
-    "XChainOwnedClaimID": 113,
-    "XChainOwnedCreateAccountClaimID": 116,
-    "FeeSettings": 115,
-    "Escrow": 117,
-    "PayChannel": 120,
+    "AMM": 121,
+    "Any": -3,
+    "Bridge": 105,
     "Check": 67,
+    "Child": -2,
+    "Contract": 99,
+    "Credential": 129,
path: binary-codec/definitions/definitions.json
user: login: florent-uzio
body: This line only is new, the rest is sorted alphabetically to better read the LEDGER_ENTRY_TYPES object.
created_at: 2025-03-14T12:22:47Z
author_association: NONE
diff_hunk: @@ -2926,56 +3008,59 @@
     "tecARRAY_TOO_LARGE": 191
   },
   "TRANSACTION_TYPES": {
-    "Invalid": -1,
-    "Payment": 0,
-    "EscrowCreate": 1,
-    "EscrowFinish": 2,
+    "AccountDelete": 21,
     "AccountSet": 3,
+    "AMMBid": 39,
+    "AMMCreate": 35,
+    "AMMDelete": 40,
+    "AMMDeposit": 36,
+    "AMMVote": 38,
+    "AMMWithdraw": 37,
+    "CheckCancel": 18,
+    "CheckCash": 17,
+    "CheckCreate": 16,
+    "Clawback": 30,
+    "Contract": 9,
+    "CredentialAccept": 59,
path: binary-codec/definitions/definitions.json
user: login: florent-uzio
body: The 3 Credential (Accept, Create and Delete) are new, the rest is sorted alphabetically.
created_at: 2025-03-14T12:23:42Z
author_association: NONE
diff_hunk: @@ -0,0 +1,55 @@
+package helpers
path: examples/helpers/clients.go
user: login: GuillemGarciaDev
body: Rename package to `clients`
created_at: 2025-03-17T08:35:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,53 @@
+package helpers
path: examples/helpers/submission.go
user: login: GuillemGarciaDev
body: Rename to `clients`
created_at: 2025-03-17T08:35:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,50 @@
+package ledger
+
+import "github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+
+const (
+	// If enabled, the subject of the credential has accepted the credential.
+	// Otherwise, the issuer created the credential but the subject has not yet accepted it, meaning it is not yet valid.
+	lsfAccepted uint32 = 0x00010000
+)
+
+// A Credential entry represents a credential, which contains an attestation about a subject account from a credential issuer account.
+// The meaning of the attestation is defined by the issuer.
+// Requires the Credentials amendment to be enabled: https://xrpl.org/resources/known-amendments#credentials
+type Credential struct {
+	// The unique ID for this ledger entry. In JSON, this field is represented with different names depending on the
+	// context and API method. (Note, even though this is specified as "optional" in the code, every ledger entry
+	// should have one unless it's legacy data from very early in the XRP Ledger's history.)
+	Index types.Hash256 `json:"index,omitempty"`
+	// The value 0x0044, mapped to the string Credential, indicates that this is a Credential ledger entry.
+	LedgerEntryType EntryType
+	// Set of bit-flags for this ledger entry.
+	Flags uint32
+	// Arbitrary data defining the type of credential this entry represents. The minimum length is 1 byte and the maximum length is 64 bytes.
+	CredentialType types.CredentialType
+	// Time after which the credential is expired, in seconds since the Ripple Epoch.
+	Expiration uint32 `json:",omitempty"`
+	// The account that issued the credential.
+	Issuer types.Address
+	// A hint indicating which page of the issuer's directory links to this entry, in case the directory consists of multiple pages.
+	IssuerNode string
+	// The identifying hash of the transaction that most recently modified this entry.
+	PreviousTxnID types.Hash256
+	// The index of the ledger that contains the transaction that most recently modified this entry.
+	PreviousTxnLgrSeq uint32
+	// The account that this credential is for.
+	Subject types.Address
+	// A hint indicating which page of the subject's owner directory links to this entry, in case the directory consists of multiple pages.
+	SubjectNode string
+	// Arbitrary additional data about the credential, for example a URL where a W3C-formatted Verifiable Credential can be retrieved.
+	URI string `json:",omitempty"`
+}
+
+func (*Credential) EntryType() EntryType {
+	return CredentialEntry
+}
+
+// SetLsfAccepted sets the one owner count flag.
+func (c *Credential) SetLsfAccepted() {
+	c.Flags |= lsfAccepted
+}
path: xrpl/ledger-entry-types/credential.go
user: login: GuillemGarciaDev
body: This is to move the `IsValidCredential` utility to the Credential struct itself.

It is the same case for the transactions, if we want to validate them it is good that they have that logic the same structs and not to have separate functions.

If there's no logic to test we can avoid adding a validation method atm 👍🏻 
created_at: 2025-03-17T08:39:27Z
author_association: NONE
path: xrpl/transaction/integration/credential/credential_create_test.go
user: login: GuillemGarciaDev
body: Nice test!
created_at: 2025-03-17T08:41:32Z
author_association: NONE
path: xrpl/transaction/integration/credential/credential_workflow_test.go
user: login: GuillemGarciaDev
body: This looks more like an e2e test or an example. Integration test should be more similar to unit test but integrating with an environment, isolating each transaction features. We can refactor it to an example 👍🏻 
created_at: 2025-03-17T08:43:19Z
author_association: NONE
path: xrpl/transaction/results/results.go
user: login: GuillemGarciaDev
body: We can move the `results.go` to `xrpl/transaction` itself to avoid importing new packages
created_at: 2025-03-17T08:44:45Z
author_association: NONE
diff_hunk: @@ -0,0 +1,54 @@
+package ledger
+
+import (
+	"testing"
+
+	"github.com/Peersyst/xrpl-go/xrpl/testutil"
+	"github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+	"github.com/stretchr/testify/require"
+)
+
+func TestCredential_EntryType(t *testing.T) {
+	credential := &Credential{}
+	require.Equal(t, credential.EntryType(), CredentialEntry)
+}
+
+func TestCredential_SetLsfAccepted(t *testing.T) {
+	credential := &Credential{}
+	credential.SetLsfAccepted()
+	require.Equal(t, credential.Flags, lsfAccepted)
+}
+
+func TestCredential_Flatten(t *testing.T) {
+	credential := &Credential{
path: xrpl/ledger-entry-types/credential_test.go
user: login: GuillemGarciaDev
body: Refactor to table driven test
created_at: 2025-03-17T08:45:47Z
author_association: NONE
diff_hunk: @@ -1070,6 +1071,16 @@
         "type": "UInt64"
       }
     ],
+    [
path: binary-codec/definitions/definitions.json
user: login: GuillemGarciaDev
body: Are `SubjectNode` and `Subject` new entries?
created_at: 2025-03-17T08:47:01Z
author_association: NONE
diff_hunk: @@ -0,0 +1,50 @@
+package ledger
+
+import "github.com/Peersyst/xrpl-go/xrpl/transaction/types"
+
+const (
+	// If enabled, the subject of the credential has accepted the credential.
+	// Otherwise, the issuer created the credential but the subject has not yet accepted it, meaning it is not yet valid.
+	lsfAccepted uint32 = 0x00010000
+)
+
+// A Credential entry represents a credential, which contains an attestation about a subject account from a credential issuer account.
+// The meaning of the attestation is defined by the issuer.
+// Requires the Credentials amendment to be enabled: https://xrpl.org/resources/known-amendments#credentials
+type Credential struct {
+	// The unique ID for this ledger entry. In JSON, this field is represented with different names depending on the
+	// context and API method. (Note, even though this is specified as "optional" in the code, every ledger entry
+	// should have one unless it's legacy data from very early in the XRP Ledger's history.)
+	Index types.Hash256 `json:"index,omitempty"`
+	// The value 0x0044, mapped to the string Credential, indicates that this is a Credential ledger entry.
+	LedgerEntryType EntryType
+	// Set of bit-flags for this ledger entry.
+	Flags uint32
+	// Arbitrary data defining the type of credential this entry represents. The minimum length is 1 byte and the maximum length is 64 bytes.
+	CredentialType types.CredentialType
+	// Time after which the credential is expired, in seconds since the Ripple Epoch.
+	Expiration uint32 `json:",omitempty"`
+	// The account that issued the credential.
+	Issuer types.Address
+	// A hint indicating which page of the issuer's directory links to this entry, in case the directory consists of multiple pages.
+	IssuerNode string
+	// The identifying hash of the transaction that most recently modified this entry.
+	PreviousTxnID types.Hash256
+	// The index of the ledger that contains the transaction that most recently modified this entry.
+	PreviousTxnLgrSeq uint32
+	// The account that this credential is for.
+	Subject types.Address
+	// A hint indicating which page of the subject's owner directory links to this entry, in case the directory consists of multiple pages.
+	SubjectNode string
+	// Arbitrary additional data about the credential, for example a URL where a W3C-formatted Verifiable Credential can be retrieved.
+	URI string `json:",omitempty"`
+}
+
+func (*Credential) EntryType() EntryType {
+	return CredentialEntry
+}
+
+// SetLsfAccepted sets the one owner count flag.
+func (c *Credential) SetLsfAccepted() {
+	c.Flags |= lsfAccepted
+}
path: xrpl/ledger-entry-types/credential.go
user: login: florent-uzio
body: I don't think there is a need for Validation for Credential at the moment.
The validation is for `Credential Type` which I've set in the credential_type.go file.
So I think we are good here.
created_at: 2025-03-18T12:00:50Z
author_association: NONE
path: xrpl/transaction/integration/credential/credential_create_test.go
user: login: florent-uzio
body: Thanks!
created_at: 2025-03-18T12:06:39Z
author_association: NONE
path: xrpl/transaction/integration/credential/credential_workflow_test.go
user: login: florent-uzio
body: Yes this is an e2e and I wrote a similar one in the `examples` directory.
I will remove this file then.
created_at: 2025-03-18T12:07:44Z
author_association: NONE
diff_hunk: @@ -1070,6 +1071,16 @@
         "type": "UInt64"
       }
     ],
+    [
path: binary-codec/definitions/definitions.json
user: login: florent-uzio
body: Yes I found them in xrpl.js, without them the `CredentialCreate` transaction was malformed and the XRPL rejected it.
created_at: 2025-03-18T12:22:07Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-03-11T09:03:02Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on base/target branches other than the default branch.
> 
> 
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough
This pull request introduces comprehensive support for credential transactions in the XRPL. It adds new transaction types for creating, accepting, and deleting credentials along with their corresponding methods (TxType, Flatten, and Validate). Additionally, the PR defines a new CredentialType with validation logic and associated error variables. Ledger entry support for credentials is integrated through a new Credential struct, its methods, and constant definitions. The changes also include unit tests, integration tests (using websockets), and updates to the changelog to document the new credential functionalities.

## Changes

| Files | Change Summary |
| --- | --- |
| `xrpl/transaction/credential_create.go`, `xrpl/transaction/credential_accept.go`, `xrpl/transaction/credential_delete.go` | Added new credential transaction structs (`CredentialCreate`, `CredentialAccept`, `CredentialDelete`) with `TxType`, `Flatten`, and `Validate` methods. |
| `xrpl/transaction/credential_create_test.go`, `xrpl/transaction/credential_accept_test.go`, `xrpl/transaction/credential_delete_test.go` | Introduced unit tests for credential transaction methods (TxType, Flatten, Validate) covering valid and invalid cases. |
| `xrpl/transaction/types/credential_type.go`, `xrpl/transaction/types/credential_type_test.go` | Added a new `CredentialType` string type with `String` and `IsValid` methods, alongside unit tests. |
| `xrpl/transaction/errors.go` | Added new error variables: `ErrInvalidCredentialType`, `ErrInvalidSubject` for credential validation. |
| `xrpl/transaction/tx_type.go` | Introduced a `// nolint` directive to ignore lint warnings for credential transaction types. |
| `CHANGELOG.md` | Updated with an `[Unreleased]` section that documents support for credential transactions (`CredentialCreate`, `CredentialAccept`, `CredentialDelete`). |
| `xrpl/ledger-entry-types/credential.go`, `xrpl/ledger-entry-types/credential_test.go`, `xrpl/ledger-entry-types/ledger_object.go` | Added a new ledger entry type for credentials including the `Credential` struct, its methods (`EntryType`, `SetLsfAccepted`), and the `CredentialEntry` constant, with corresponding tests. |
| `xrpl/transaction/integration/credential_create.go`, `xrpl/transaction/results/results.go` | Added an integration test for `CredentialCreate` (websocket based) and introduced a new `TxResult` type with constant `TesSUCCESS` and a `String()` method. |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
    participant Client
    participant WSRunner as Websocket Runner
    participant Credential as Credential Transaction
    participant Ledger

    Client->>WSRunner: Submit Credential Transaction
    WSRunner->>Credential: Validate()
    alt Valid Transaction
        Credential->>Credential: Flatten transaction
        Credential->>Ledger: Submit flat transaction
        Ledger-->>WSRunner: Return tesSUCCESS response
        WSRunner-->>Client: Forward success response
    else Invalid Transaction
        Credential-->>WSRunner: Return validation error
        WSRunner-->>Client: Forward error response
    end
```

## Suggested labels
`feature`, `xrpl`

## Suggested reviewers
- AgustinMJ
- banasa44
- AdriaCarrera

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Peersyst/xrpl-go&utm_content=113):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: GuillemGarciaDev
created_at: 2025-03-11T09:45:05Z
body: @coderabbit review
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-11T09:45:12Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: NONE
user: login: GuillemGarciaDev
created_at: 2025-03-11T14:59:12Z
body: @florent-uzio have you been able to test all credential transactions using the integration runner? Every new transaction should be tested with an integration environment
author_association: NONE