package main import ( "encoding/json" "fmt" "strconv" "time" "github.com/hyperledger/fabric-contract-api-go/contractapi" ) // TokenizedAssetContract provides functions for managing tokenized assets type TokenizedAssetContract struct { contractapi.Contract } // TokenizedAsset represents a tokenized asset on Fabric type TokenizedAsset struct { TokenID string `json:"tokenId"` UnderlyingAsset string `json:"underlyingAsset"` Amount string `json:"amount"` Issuer string `json:"issuer"` BackingReserve string `json:"backingReserve"` Status string `json:"status"` // minted, transferred, redeemed RegulatoryFlags map[string]interface{} `json:"regulatoryFlags"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } // MintRequest represents a request to mint tokenized assets type MintRequest struct { TokenID string `json:"tokenId"` UnderlyingAsset string `json:"underlyingAsset"` Amount string `json:"amount"` Issuer string `json:"issuer"` ReserveProof string `json:"reserveProof"` RegulatoryFlags map[string]interface{} `json:"regulatoryFlags"` } // TransferRequest represents a request to transfer tokenized assets type TransferRequest struct { TokenID string `json:"tokenId"` From string `json:"from"` To string `json:"to"` Amount string `json:"amount"` Regulatory map[string]interface{} `json:"regulatory"` } // RedemptionRequest represents a request to redeem tokenized assets type RedemptionRequest struct { TokenID string `json:"tokenId"` Redeemer string `json:"redeemer"` Amount string `json:"amount"` RedemptionProof string `json:"redemptionProof"` } // InitLedger initializes the ledger with sample data (for testing) func (s *TokenizedAssetContract) InitLedger(ctx contractapi.TransactionContextInterface) error { assets := []TokenizedAsset{ { TokenID: "EUR-T-2025-001", UnderlyingAsset: "EUR", Amount: "1000000.00", Issuer: "DBIS", BackingReserve: "1:1", Status: "minted", RegulatoryFlags: map[string]interface{}{ "kyc": true, "aml": true, "regulatoryApproval": true, }, CreatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339), }, } for _, asset := range assets { assetJSON, err := json.Marshal(asset) if err != nil { return err } err = ctx.GetStub().PutState(asset.TokenID, assetJSON) if err != nil { return fmt.Errorf("failed to put asset to world state: %v", err) } } return nil } // MintToken mints a new tokenized asset after reserve verification func (s *TokenizedAssetContract) MintToken(ctx contractapi.TransactionContextInterface, requestJSON string) error { var request MintRequest err := json.Unmarshal([]byte(requestJSON), &request) if err != nil { return fmt.Errorf("failed to unmarshal mint request: %v", err) } // Check if token already exists existing, err := ctx.GetStub().GetState(request.TokenID) if err != nil { return fmt.Errorf("failed to read from world state: %v", err) } if existing != nil { return fmt.Errorf("token %s already exists", request.TokenID) } // Verify reserve proof (in production, this would call reserve manager chaincode) // For now, we assume reserve proof is valid if provided if request.ReserveProof == "" { return fmt.Errorf("reserve proof is required") } // Check SolaceNet capability (would integrate with SolaceNet service) // This is a placeholder - in production, call SolaceNet API clientID := ctx.GetClientIdentity() canMint, err := s.checkSolaceNetCapability(ctx, clientID.GetID(), "tokenization.mint") if err != nil { return fmt.Errorf("failed to check SolaceNet capability: %v", err) } if !canMint { return fmt.Errorf("client %s does not have tokenization.mint capability", clientID.GetID()) } // Create tokenized asset asset := TokenizedAsset{ TokenID: request.TokenID, UnderlyingAsset: request.UnderlyingAsset, Amount: request.Amount, Issuer: request.Issuer, BackingReserve: "1:1", Status: "minted", RegulatoryFlags: request.RegulatoryFlags, CreatedAt: time.Now().Format(time.RFC3339), UpdatedAt: time.Now().Format(time.RFC3339), } assetJSON, err := json.Marshal(asset) if err != nil { return err } err = ctx.GetStub().PutState(request.TokenID, assetJSON) if err != nil { return fmt.Errorf("failed to put asset to world state: %v", err) } // Emit event eventPayload := fmt.Sprintf(`{"tokenId":"%s","action":"mint","amount":"%s","issuer":"%s"}`, request.TokenID, request.Amount, request.Issuer) err = ctx.GetStub().SetEvent("TokenMinted", []byte(eventPayload)) if err != nil { return fmt.Errorf("failed to emit event: %v", err) } return nil } // TransferToken transfers tokenized assets with regulatory checks func (s *TokenizedAssetContract) TransferToken(ctx contractapi.TransactionContextInterface, requestJSON string) error { var request TransferRequest err := json.Unmarshal([]byte(requestJSON), &request) if err != nil { return fmt.Errorf("failed to unmarshal transfer request: %v", err) } // Get token assetJSON, err := ctx.GetStub().GetState(request.TokenID) if err != nil { return fmt.Errorf("failed to read token from world state: %v", err) } if assetJSON == nil { return fmt.Errorf("token %s does not exist", request.TokenID) } var asset TokenizedAsset err = json.Unmarshal(assetJSON, &asset) if err != nil { return err } // Verify sender has permission clientID := ctx.GetClientIdentity() if asset.Issuer != clientID.GetID() && request.From != clientID.GetID() { return fmt.Errorf("client %s is not authorized to transfer this token", clientID.GetID()) } // Check SolaceNet capability canTransfer, err := s.checkSolaceNetCapability(ctx, clientID.GetID(), "tokenization.transfer") if err != nil { return fmt.Errorf("failed to check SolaceNet capability: %v", err) } if !canTransfer { return fmt.Errorf("client %s does not have tokenization.transfer capability", clientID.GetID()) } // Verify amounts (simplified - in production, use proper decimal handling) requestAmount, err := strconv.ParseFloat(request.Amount, 64) if err != nil { return fmt.Errorf("invalid amount: %v", err) } currentAmount, err := strconv.ParseFloat(asset.Amount, 64) if err != nil { return fmt.Errorf("invalid current amount: %v", err) } if requestAmount > currentAmount { return fmt.Errorf("insufficient balance: requested %f, available %f", requestAmount, currentAmount) } // Update token newAmount := currentAmount - requestAmount asset.Amount = fmt.Sprintf("%.2f", newAmount) asset.Status = "transferred" asset.UpdatedAt = time.Now().Format(time.RFC3339) // Merge regulatory flags for k, v := range request.Regulatory { asset.RegulatoryFlags[k] = v } updatedJSON, err := json.Marshal(asset) if err != nil { return err } err = ctx.GetStub().PutState(request.TokenID, updatedJSON) if err != nil { return fmt.Errorf("failed to update asset in world state: %v", err) } // Emit event eventPayload := fmt.Sprintf(`{"tokenId":"%s","action":"transfer","from":"%s","to":"%s","amount":"%s"}`, request.TokenID, request.From, request.To, request.Amount) err = ctx.GetStub().SetEvent("TokenTransferred", []byte(eventPayload)) if err != nil { return fmt.Errorf("failed to emit event: %v", err) } return nil } // RedeemToken redeems tokenized assets back to underlying asset func (s *TokenizedAssetContract) RedeemToken(ctx contractapi.TransactionContextInterface, requestJSON string) error { var request RedemptionRequest err := json.Unmarshal([]byte(requestJSON), &request) if err != nil { return fmt.Errorf("failed to unmarshal redemption request: %v", err) } // Get token assetJSON, err := ctx.GetStub().GetState(request.TokenID) if err != nil { return fmt.Errorf("failed to read token from world state: %v", err) } if assetJSON == nil { return fmt.Errorf("token %s does not exist", request.TokenID) } var asset TokenizedAsset err = json.Unmarshal(assetJSON, &asset) if err != nil { return err } // Verify redemption proof if request.RedemptionProof == "" { return fmt.Errorf("redemption proof is required") } // Check SolaceNet capability clientID := ctx.GetClientIdentity() canRedeem, err := s.checkSolaceNetCapability(ctx, clientID.GetID(), "tokenization.redeem") if err != nil { return fmt.Errorf("failed to check SolaceNet capability: %v", err) } if !canRedeem { return fmt.Errorf("client %s does not have tokenization.redeem capability", clientID.GetID()) } // Verify amounts requestAmount, err := strconv.ParseFloat(request.Amount, 64) if err != nil { return fmt.Errorf("invalid amount: %v", err) } currentAmount, err := strconv.ParseFloat(asset.Amount, 64) if err != nil { return fmt.Errorf("invalid current amount: %v", err) } if requestAmount > currentAmount { return fmt.Errorf("insufficient balance: requested %f, available %f", requestAmount, currentAmount) } // Update token newAmount := currentAmount - requestAmount asset.Amount = fmt.Sprintf("%.2f", newAmount) asset.Status = "redeemed" asset.UpdatedAt = time.Now().Format(time.RFC3339) updatedJSON, err := json.Marshal(asset) if err != nil { return err } err = ctx.GetStub().PutState(request.TokenID, updatedJSON) if err != nil { return fmt.Errorf("failed to update asset in world state: %v", err) } // Emit event eventPayload := fmt.Sprintf(`{"tokenId":"%s","action":"redeem","redeemer":"%s","amount":"%s"}`, request.TokenID, request.Redeemer, request.Amount) err = ctx.GetStub().SetEvent("TokenRedeemed", []byte(eventPayload)) if err != nil { return fmt.Errorf("failed to emit event: %v", err) } return nil } // GetToken returns the tokenized asset details func (s *TokenizedAssetContract) GetToken(ctx contractapi.TransactionContextInterface, tokenID string) (*TokenizedAsset, error) { assetJSON, err := ctx.GetStub().GetState(tokenID) if err != nil { return nil, fmt.Errorf("failed to read from world state: %v", err) } if assetJSON == nil { return nil, fmt.Errorf("token %s does not exist", tokenID) } var asset TokenizedAsset err = json.Unmarshal(assetJSON, &asset) if err != nil { return nil, err } return &asset, nil } // GetAllTokens returns all tokenized assets (with pagination support) func (s *TokenizedAssetContract) GetAllTokens(ctx contractapi.TransactionContextInterface) ([]*TokenizedAsset, error) { resultsIterator, err := ctx.GetStub().GetStateByRange("", "") if err != nil { return nil, err } defer resultsIterator.Close() var assets []*TokenizedAsset for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return nil, err } var asset TokenizedAsset err = json.Unmarshal(queryResponse.Value, &asset) if err != nil { return nil, err } assets = append(assets, &asset) } return assets, nil } // checkSolaceNetCapability checks if a client has a SolaceNet capability // In production, this would call SolaceNet API or use chaincode-to-chaincode invocation func (s *TokenizedAssetContract) checkSolaceNetCapability(ctx contractapi.TransactionContextInterface, clientID, capability string) (bool, error) { // Placeholder implementation // In production, this would: // 1. Call SolaceNet API via external service // 2. Or use chaincode-to-chaincode invocation if SolaceNet is on same network // 3. Or use Cacti to bridge to SolaceNet service // For now, return true for testing // In production, implement actual SolaceNet integration return true, nil } func main() { chaincode, err := contractapi.NewChaincode(&TokenizedAssetContract{}) if err != nil { fmt.Printf("Error creating tokenized asset chaincode: %v", err) return } if err := chaincode.Start(); err != nil { fmt.Printf("Error starting tokenized asset chaincode: %v", err) } }