SVGTemplatesLib Library¶
Overview¶
The SVGTemplatesLib library provides core utilities for creating and managing on-chain SVG templates within the Gemforce platform. This library enables dynamic generation of SVG graphics for NFT metadata, supporting template-based rendering with variable substitution and multi-part composition.
Key Features¶
- On-Chain SVG Storage: Store SVG templates directly on the blockchain
- Deterministic Deployment: CREATE2-based template contract deployment
- Template Management: Named template registry with unique addressing
- Dynamic Rendering: Variable substitution for customizable graphics
- Multi-Part Support: Composition of complex SVG graphics from components
- Ownership Transfer: Template ownership management for creators
Library Definition¶
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;
library SVGTemplatesLib {
// Storage management
function svgStorage() internal pure returns (SVGStorage storage);
// Template management
function _svgs(SVGTemplatesContract storage self) internal view returns (string[] memory);
function _svgAddress(SVGTemplatesContract storage, string memory _name) internal view returns (address);
function _svgString(SVGTemplatesContract storage self, string memory _name) internal view returns (string memory);
function _svgData(SVGTemplatesContract storage self, string memory _name) internal view returns (address);
// Template creation
function _createSVG(SVGTemplatesContract storage self, address sender, string memory _name) internal returns (address);
}
Data Structures¶
SVGTemplatesContract Struct¶
struct SVGTemplatesContract {
mapping(string => address) _templates; // Template name to contract address mapping
string[] _templateNames; // Array of all template names
}
Purpose: Core storage structure for managing SVG template contracts.
Components: - _templates: Maps template names to deployed contract addresses - _templateNames: Maintains ordered list of all template names
SaltStorage Struct¶
struct SaltStorage {
uint256 salt; // Salt for CREATE2 operations
}
Purpose: Storage for CREATE2 salt values to ensure deterministic addressing.
MultiPartContract Struct¶
struct MultiPartContract {
string name_; // Name of the multi-part template
bytes[] data_; // Array of SVG component data
}
Purpose: Storage structure for multi-part SVG compositions.
SVGStorage Struct¶
struct SVGStorage {
SVGTemplatesContract svgTemplates; // Main template storage
SaltStorage salt; // CREATE2 salt storage
address svgManager; // SVG manager contract address
MultiPartContract multiPart; // Multi-part template storage
}
Purpose: Complete Diamond storage structure for SVG functionality.
Storage Position:
bytes32 internal constant DIAMOND_STORAGE_POSITION =
keccak256("diamond.nextblock.bitgem.app.SVGStorage.storage");
Replacement Struct¶
struct Replacement {
string matchString; // String to find in template
string replaceString; // String to replace with
}
Purpose: Variable substitution data for dynamic SVG rendering.
Core Functions¶
Storage Management¶
svgStorage()¶
function svgStorage() internal pure returns (SVGStorage storage ds)
Purpose: Access Diamond storage for SVG template data using assembly.
Implementation:
function svgStorage() internal pure returns (SVGStorage storage ds) {
bytes32 position = DIAMOND_STORAGE_POSITION;
assembly {
ds.slot := position
}
}
Returns: Storage reference to SVG template data
Usage: All SVG functions use this to access persistent storage.
Template Management¶
_svgs()¶
function _svgs(SVGTemplatesContract storage self) internal view returns (string[] memory)
Purpose: Retrieve all template names stored in the contract.
Parameters:
- self: Storage reference to SVG templates contract
Returns: Array of all template names
Example Usage:
// Get all available template names
string[] memory templateNames = SVGTemplatesLib._svgs(svgTemplates);
console.log("Available templates:", templateNames.length);
_svgAddress()¶
function _svgAddress(
SVGTemplatesContract storage,
string memory _name
) internal view returns (address)
Purpose: Calculate the deterministic address for a template contract.
Parameters:
- _name: Name of the template
Implementation:
return Create2.computeAddress(
keccak256(abi.encodePacked(_name)),
keccak256(type(SVGTemplate).creationCode)
);
Returns: Predicted contract address for the named template
Benefits: - Enables address prediction before deployment - Supports deterministic template addressing - Allows for efficient template lookup
_svgString()¶
function _svgString(
SVGTemplatesContract storage self,
string memory _name
) internal view returns (string memory data_)
Purpose: Retrieve the SVG string content from a deployed template.
Parameters:
- self: Storage reference to SVG templates contract
- _name: Name of the template
Implementation:
try SVGTemplate(_svgAddress(self, _name)).svgString() returns (string memory _data) {
data_ = _data;
} catch (bytes memory) {}
Returns: SVG string content or empty string if template doesn't exist
Error Handling: Uses try-catch to gracefully handle non-existent templates
Example Usage:
// Get SVG content for rendering
string memory svgContent = SVGTemplatesLib._svgString(svgTemplates, "avatar_base");
if (bytes(svgContent).length > 0) {
// Process SVG content
}
_svgData()¶
function _svgData(
SVGTemplatesContract storage self,
string memory _name
) internal view returns (address)
Purpose: Get the stored contract address for a named template.
Parameters:
- self: Storage reference to SVG templates contract
- _name: Name of the template
Returns: Contract address of the template or zero address if not found
Example Usage:
// Check if template exists
address templateAddress = SVGTemplatesLib._svgData(svgTemplates, "background");
require(templateAddress != address(0), "Template not found");
Template Creation¶
_createSVG()¶
function _createSVG(
SVGTemplatesContract storage self,
address sender,
string memory _name
) internal returns (address _tplAddress)
Purpose: Deploy a new SVG template contract with deterministic addressing.
Parameters:
- self: Storage reference to SVG templates contract
- sender: Address that will own the template
- _name: Unique name for the template
Process: 1. Verify template name is unique 2. Calculate target address using CREATE2 3. Deploy template contract with deterministic salt 4. Verify deployment address matches prediction 5. Transfer ownership to sender 6. Update storage with new template
Security Features: - Prevents duplicate template names - Verifies deployment integrity - Transfers ownership to creator
Example Usage:
// Create a new SVG template
address templateAddress = SVGTemplatesLib._createSVG(
svgTemplates,
msg.sender,
"character_base"
);
// Template is now deployed and owned by msg.sender
console.log("Template deployed at:", templateAddress);
Integration Examples¶
NFT Avatar System¶
// NFT collection with dynamic SVG avatars
contract AvatarNFT {
using SVGTemplatesLib for SVGTemplatesLib.SVGStorage;
struct AvatarTraits {
string background;
string body;
string eyes;
string mouth;
string accessory;
uint256 colorScheme;
}
mapping(uint256 => AvatarTraits) public avatarTraits;
mapping(string => string[]) public traitOptions;
event TemplateCreated(string indexed templateName, address templateAddress);
event AvatarGenerated(uint256 indexed tokenId, AvatarTraits traits);
function initializeTemplates() external onlyOwner {
SVGTemplatesLib.SVGStorage storage svgStorage = SVGTemplatesLib.svgStorage();
// Create base templates for avatar components
string[] memory templateNames = new string[](5);
templateNames[0] = "background_template";
templateNames[1] = "body_template";
templateNames[2] = "eyes_template";
templateNames[3] = "mouth_template";
templateNames[4] = "accessory_template";
for (uint256 i = 0; i < templateNames.length; i++) {
address templateAddress = SVGTemplatesLib._createSVG(
svgStorage.svgTemplates,
address(this),
templateNames[i]
);
emit TemplateCreated(templateNames[i], templateAddress);
}
// Initialize trait options
traitOptions["background"] = ["forest", "ocean", "space", "city"];
traitOptions["body"] = ["human", "robot", "alien", "animal"];
traitOptions["eyes"] = ["normal", "glowing", "mechanical", "large"];
traitOptions["mouth"] = ["smile", "frown", "neutral", "fangs"];
traitOptions["accessory"] = ["hat", "glasses", "necklace", "none"];
}
function generateAvatar(uint256 tokenId, uint256 seed) external {
require(_exists(tokenId), "Token does not exist");
// Generate random traits based on seed
AvatarTraits memory traits = AvatarTraits({
background: traitOptions["background"][seed % traitOptions["background"].length],
body: traitOptions["body"][(seed / 10) % traitOptions["body"].length],
eyes: traitOptions["eyes"][(seed / 100) % traitOptions["eyes"].length],
mouth: traitOptions["mouth"][(seed / 1000) % traitOptions["mouth"].length],
accessory: traitOptions["accessory"][(seed / 10000) % traitOptions["accessory"].length],
colorScheme: seed % 10
});
avatarTraits[tokenId] = traits;
emit AvatarGenerated(tokenId, traits);
}
function tokenURI(uint256 tokenId) public view override returns (string memory) {
require(_exists(tokenId), "Token does not exist");
string memory svgContent = generateSVG(tokenId);
string memory json = Base64.encode(
bytes(
string(
abi.encodePacked(
'{"name": "Avatar #',
Strings.toString(tokenId),
'", "description": "Dynamic SVG Avatar", "image": "data:image/svg+xml;base64,',
Base64.encode(bytes(svgContent)),
'", "attributes": ',
generateAttributes(tokenId),
'}'
)
)
)
);
return string(abi.encodePacked("data:application/json;base64,", json));
}
function generateSVG(uint256 tokenId) public view returns (string memory) {
SVGTemplatesLib.SVGStorage storage svgStorage = SVGTemplatesLib.svgStorage();
AvatarTraits memory traits = avatarTraits[tokenId];
// Get base templates
string memory backgroundSVG = SVGTemplatesLib._svgString(svgStorage.svgTemplates, "background_template");
string memory bodySVG = SVGTemplatesLib._svgString(svgStorage.svgTemplates, "body_template");
string memory eyesSVG = SVGTemplatesLib._svgString(svgStorage.svgTemplates, "eyes_template");
string memory mouthSVG = SVGTemplatesLib._svgString(svgStorage.svgTemplates, "mouth_template");
string memory accessorySVG = SVGTemplatesLib._svgString(svgStorage.svgTemplates, "accessory_template");
// Create replacements for dynamic content
Replacement[] memory replacements = new Replacement[](6);
replacements[0] = Replacement("{{BACKGROUND_TYPE}}", traits.background);
replacements[1] = Replacement("{{BODY_TYPE}}", traits.body);
replacements[2] = Replacement("{{EYES_TYPE}}", traits.eyes);
replacements[3] = Replacement("{{MOUTH_TYPE}}", traits.mouth);
replacements[4] = Replacement("{{ACCESSORY_TYPE}}", traits.accessory);
replacements[5] = Replacement("{{COLOR_SCHEME}}", Strings.toString(traits.colorScheme));
// Build complete SVG
return string(
abi.encodePacked(
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">',
applyReplacements(backgroundSVG, replacements),
applyReplacements(bodySVG, replacements),
applyReplacements(eyesSVG, replacements),
applyReplacements(mouthSVG, replacements),
applyReplacements(accessorySVG, replacements),
'</svg>'
)
);
}
function generateAttributes(uint256 tokenId) public view returns (string memory) {
AvatarTraits memory traits = avatarTraits[tokenId];
return string(
abi.encodePacked(
'[',
'{"trait_type": "Background", "value": "', traits.background, '"},',
'{"trait_type": "Body", "value": "', traits.body, '"},',
'{"trait_type": "Eyes", "value": "', traits.eyes, '"},',
'{"trait_type": "Mouth", "value": "', traits.mouth, '"},',
'{"trait_type": "Accessory", "value": "', traits.accessory, '"},',
'{"trait_type": "Color Scheme", "value": ', Strings.toString(traits.colorScheme), '}',
']'
)
);
}
function applyReplacements(string memory template, Replacement[] memory replacements) internal pure returns (string memory) {
string memory result = template;
for (uint256 i = 0; i < replacements.length; i++) {
result = StringsLib.replace(result, replacements[i].matchString, replacements[i].replaceString);
}
return result;
}
modifier onlyOwner() {
// Implementation
_;
}
function _exists(uint256 tokenId) internal view returns (bool) {
// Implementation
return true;
}
}
Gaming Asset Renderer¶
// Dynamic SVG rendering for gaming assets
contract GameAssetRenderer {
using SVGTemplatesLib for SVGTemplatesLib.SVGStorage;
struct AssetTemplate {
string name;
string category;
string[] requiredVariables;
bool active;
}
struct AssetInstance {
string templateName;
mapping(string => string) variables;
uint256 lastUpdated;
}
mapping(string => AssetTemplate) public assetTemplates;
mapping(uint256 => AssetInstance) public assetInstances;
mapping(string => string[]) public categoryTemplates;
event AssetTemplateRegistered(string indexed templateName, string category);
event AssetRendered(uint256 indexed assetId, string templateName);
event AssetVariableUpdated(uint256 indexed assetId, string variable, string value);
function registerAssetTemplate(
string memory templateName,
string memory category,
string[] memory requiredVariables,
string memory svgTemplate
) external onlyGameMaster {
SVGTemplatesLib.SVGStorage storage svgStorage = SVGTemplatesLib.svgStorage();
// Create SVG template contract
address templateAddress = SVGTemplatesLib._createSVG(
svgStorage.svgTemplates,
address(this),
templateName
);
// Initialize template with SVG content
ISVGTemplate(templateAddress).clear();
ISVGTemplate(templateAddress).add(svgTemplate);
// Register template metadata
assetTemplates[templateName] = AssetTemplate({
name: templateName,
category: category,
requiredVariables: requiredVariables,
active: true
});
categoryTemplates[category].push(templateName);
emit AssetTemplateRegistered(templateName, category);
}
function createAsset(
uint256 assetId,
string memory templateName,
string[] memory variableNames,
string[] memory variableValues
) external {
AssetTemplate storage template = assetTemplates[templateName];
require(bytes(template.name).length > 0, "Template not found");
require(template.active, "Template not active");
require(variableNames.length == variableValues.length, "Variable arrays length mismatch");
AssetInstance storage instance = assetInstances[assetId];
instance.templateName = templateName;
instance.lastUpdated = block.timestamp;
for (uint256 i = 0; i < variableNames.length; i++) {
instance.variables[variableNames[i]] = variableValues[i];
}
emit AssetRendered(assetId, templateName);
}
function updateAssetVariable(uint256 assetId, string memory variableName, string memory value) external {
AssetInstance storage instance = assetInstances[assetId];
require(bytes(instance.templateName).length > 0, "Asset not found"); // Check if asset exists
instance.variables[variableName] = value;
instance.lastUpdated = block.timestamp;
emit AssetVariableUpdated(assetId, variableName, value);
}
function getAssetSVG(uint256 assetId) external view returns (string memory) {
AssetInstance memory instance = assetInstances[assetId];
require(bytes(instance.templateName).length > 0, "Asset not found");
SVGTemplatesLib.SVGStorage storage svgStorage = SVGTemplatesLib.svgStorage();
string memory templateSVG = SVGTemplatesLib._svgString(svgStorage.svgTemplates, instance.templateName);
Replacement[] memory replacements = new Replacement[](instance.variables.length); // Dynamic array
uint256 i = 0;
// Iterate through mapping (not directly possible, would require storing keys)
// For demonstration, assume keys are known or passed
// Placeholder for variable substitution:
string memory result = templateSVG;
// In reality, iterate over stored instance.variables and replace
return result;
}
function getTemplateListByCategory(string memory category) external view returns (string[] memory) {
return categoryTemplates[category];
}
modifier onlyGameMaster() {
// Placeholder for access control
_;
}
}
Certificate Generator¶
// On-chain certificate generation with dynamic SVG and data
contract CertificateGenerator {
using SVGTemplatesLib for SVGTemplatesLib.SVGStorage;
struct CertificateConfig {
string templateName;
string[] requiredFields;
bool active;
}
mapping(string => CertificateConfig) public certificateConfigs;
event CertificateConfigured(string indexed configName, string templateName);
event CertificateGenerated(uint256 indexed certificateId, string configName, address indexed recipient);
function configureCertificate(
string memory configName,
string memory templateName,
string[] memory requiredFields,
string memory svgTemplate
) external onlyOwner {
SVGTemplatesLib.SVGStorage storage svgStorage = SVGTemplatesLib.svgStorage();
// Create or update SVG template
address templateAddress = SVGTemplatesLib._createSVG(
svgStorage.svgTemplates,
address(this),
templateName
);
ISVGTemplate(templateAddress).clear();
ISVGTemplate(templateAddress).add(svgTemplate);
certificateConfigs[configName] = CertificateConfig({
templateName: templateName,
requiredFields: requiredFields,
active: true
});
emit CertificateConfigured(configName, templateName);
}
function generateCertificate(
string memory configName,
uint256 certificateId,
address recipient,
string[] memory fieldNames,
string[] memory fieldValues
) external returns (string memory) {
CertificateConfig storage config = certificateConfigs[configName];
require(config.active, "Certificate config not active");
require(fieldNames.length == fieldValues.length, "Field arrays mismatch");
// Validate required fields
for (uint256 i = 0; i < config.requiredFields.length; i++) {
bool found = false;
for (uint256 j = 0; j < fieldNames.length; j++) {
if (keccak256(abi.encodePacked(config.requiredFields[i])) == keccak256(abi.encodePacked(fieldNames[j]))) {
found = true;
break;
}
}
require(found, "Missing required field");
}
SVGTemplatesLib.SVGStorage storage svgStorage = SVGTemplatesLib.svgStorage();
string memory svgTemplate = SVGTemplatesLib._svgString(svgStorage.svgTemplates, config.templateName);
Replacement[] memory replacements = new Replacement[](fieldNames.length);
for (uint256 i = 0; i < fieldNames.length; i++) {
replacements[i] = Replacement(
string(abi.encodePacked("{{", fieldNames[i], "}}")), // e.g., {{RECIPIENT_NAME}}
fieldValues[i]
);
}
string memory finalSVG = applyReplacements(svgTemplate, replacements);
// Mint NFT representing the certificate if applicable
// (ERC721 or ERC1155 minting logic here)
emit CertificateGenerated(certificateId, configName, recipient);
return finalSVG;
}
function applyReplacements(string memory template, Replacement[] memory replacements) internal pure returns (string memory) {
string memory result = template;
for (uint256 i = 0; i < replacements.length; i++) {
result = StringsLib.replace(result, replacements[i].matchString, replacements[i].replaceString);
}
return result;
}
modifier onlyOwner() {
// Implementation
_;
}
}
Security Considerations¶
Template Deployment Security¶
- Access Control: Only authorized parties should be able to create new SVG templates.
- Input Validation: Validate template names and SVG content for malicious code.
- Deterministic Addresses: Reliance on CREATE2 ensures predictable addresses, which can be vulnerable if salt generation is not unique/random enough.
Access Control¶
- Owner-Only Functions: Sensitive manager functions (e.g., setting
svgManageraddress) should be restricted to the contract owner. - Template Ownership: Templates should be owned by the facet or a trusted entity, not external users.
Content Validation¶
- SVG Sanitization: Ensure only safe SVG elements and attributes are allowed.
- XSS Prevention: Prevent Cross-Site Scripting (XSS) if SVG is rendered in a web context.
Gas Optimization¶
Storage Efficiency¶
- The
SVGStoragestruct uses a custom storage slot pattern to avoid storage collisions. - Templates are stored as separate contracts, minimizing the main diamond's storage footprint.
Rendering Efficiency¶
- Dynamic SVG generation on-chain minimizes data stored directly in NFT metadata.
StringsLib.replacefor string manipulation is comparatively expensive; ensure replacements are minimal for gas.
Error Handling¶
Common Errors¶
SVSTLib: Invalid name: Template name is empty or already exists.SVSTLib: Template not found: Attempting to use a non-existent template.SVSTLib: Deployment failed: CREATE2 deployment failed.SVSTLib: Not template owner: Unauthorized attempt to modify template.
Best Practices¶
Template Design¶
- Design SVG templates to be modular and reusable.
- Use placeholders (
{{VARIABLE_NAME}}) for dynamic content. - Keep templates as small as possible to minimize gas costs.
Development Guidelines¶
- Thoroughly test SVG rendering logic off-chain before deployment.
- Implement robust access control for all template management functions.
- Monitor events for template creation and updates.
Integration Checklist¶
- Ensure off-chain rendering applications properly handle SVG rendering from
data:image/svg+xml;base64URIs. - Provide clear documentation for template variable names and expected types.
Related Documentation¶
- SVGTemplatesFacet - Reference for the SVGTemplates Facet implementation.
- ISVG Interface - Interface definition.
- NFT Metadata Standards
- SVG Generation Tools & Libraries (External)