diff --git a/assets/translations/en.json b/assets/translations/en.json
index e180cddc7ae4123bd451604f40287ceab3f60acf..3bcaf34b57c18ea3bb454d5f661049e01e38bcd8 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -190,5 +190,6 @@
   "telegram_group": "Telegram Group",
   "payment_waiting_internet_title": "No Internet",
   "payment_waiting_internet_desc": "We'll retry your payment when internet is back",
-  "payment_waiting_internet_desc_beta": "Retry your payment when internet is back"
+  "payment_waiting_internet_desc_beta": "Retry your payment when internet is back",
+  "your_name_here": "Name this card"
 }
diff --git a/assets/translations/es.json b/assets/translations/es.json
index 8e772b94ec78d307306fec6601cf435af6632880..1b27cb327bdcfb0e2d4276f6ad6d1985acf26506 100644
--- a/assets/translations/es.json
+++ b/assets/translations/es.json
@@ -192,5 +192,6 @@
   "telegram_group": "Grupo de Telegram",
   "payment_waiting_internet_title": "Sin Internet",
   "payment_waiting_internet_desc": "Reintentaremos el pago cuando vuelvas a tener conexión",
-  "payment_waiting_internet_desc_beta": "Reintenta el pago cuando vuelvas a tener conexión"
+  "payment_waiting_internet_desc_beta": "Reintenta el pago cuando vuelvas a tener conexión",
+  "your_name_here": "Da un nombre a esta tarjeta"
 }
diff --git a/lib/data/models/cesium_card.dart b/lib/data/models/cesium_card.dart
index cdce326d91269fcc614b60e5df86c6fea01b27f6..5702960c917326b5d6d767d9357f6101fcd250b8 100644
--- a/lib/data/models/cesium_card.dart
+++ b/lib/data/models/cesium_card.dart
@@ -1,3 +1,4 @@
+import 'package:copy_with_extension/copy_with_extension.dart';
 import 'package:json_annotation/json_annotation.dart';
 
 import 'credit_card_themes.dart';
@@ -6,6 +7,7 @@ import 'is_json_serializable.dart';
 part 'cesium_card.g.dart';
 
 @JsonSerializable()
+@CopyWith()
 class CesiumCard implements IsJsonSerializable<CesiumCard> {
   CesiumCard(
       {required this.seed,
diff --git a/lib/data/models/cesium_card.g.dart b/lib/data/models/cesium_card.g.dart
index 8822d1bca96d8d869922b991eb8f6a16ab32500c..a4047c19202d615617df49832dc4bc4cec970f2f 100644
--- a/lib/data/models/cesium_card.g.dart
+++ b/lib/data/models/cesium_card.g.dart
@@ -2,6 +2,92 @@
 
 part of 'cesium_card.dart';
 
+// **************************************************************************
+// CopyWithGenerator
+// **************************************************************************
+
+abstract class _$CesiumCardCWProxy {
+  CesiumCard seed(String seed);
+
+  CesiumCard pubKey(String pubKey);
+
+  CesiumCard name(String name);
+
+  CesiumCard theme(CreditCardTheme theme);
+
+  /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `CesiumCard(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
+  ///
+  /// Usage
+  /// ```dart
+  /// CesiumCard(...).copyWith(id: 12, name: "My name")
+  /// ````
+  CesiumCard call({
+    String? seed,
+    String? pubKey,
+    String? name,
+    CreditCardTheme? theme,
+  });
+}
+
+/// Proxy class for `copyWith` functionality. This is a callable class and can be used as follows: `instanceOfCesiumCard.copyWith(...)`. Additionally contains functions for specific fields e.g. `instanceOfCesiumCard.copyWith.fieldName(...)`
+class _$CesiumCardCWProxyImpl implements _$CesiumCardCWProxy {
+  const _$CesiumCardCWProxyImpl(this._value);
+
+  final CesiumCard _value;
+
+  @override
+  CesiumCard seed(String seed) => this(seed: seed);
+
+  @override
+  CesiumCard pubKey(String pubKey) => this(pubKey: pubKey);
+
+  @override
+  CesiumCard name(String name) => this(name: name);
+
+  @override
+  CesiumCard theme(CreditCardTheme theme) => this(theme: theme);
+
+  @override
+
+  /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `CesiumCard(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
+  ///
+  /// Usage
+  /// ```dart
+  /// CesiumCard(...).copyWith(id: 12, name: "My name")
+  /// ````
+  CesiumCard call({
+    Object? seed = const $CopyWithPlaceholder(),
+    Object? pubKey = const $CopyWithPlaceholder(),
+    Object? name = const $CopyWithPlaceholder(),
+    Object? theme = const $CopyWithPlaceholder(),
+  }) {
+    return CesiumCard(
+      seed: seed == const $CopyWithPlaceholder() || seed == null
+          ? _value.seed
+          // ignore: cast_nullable_to_non_nullable
+          : seed as String,
+      pubKey: pubKey == const $CopyWithPlaceholder() || pubKey == null
+          ? _value.pubKey
+          // ignore: cast_nullable_to_non_nullable
+          : pubKey as String,
+      name: name == const $CopyWithPlaceholder() || name == null
+          ? _value.name
+          // ignore: cast_nullable_to_non_nullable
+          : name as String,
+      theme: theme == const $CopyWithPlaceholder() || theme == null
+          ? _value.theme
+          // ignore: cast_nullable_to_non_nullable
+          : theme as CreditCardTheme,
+    );
+  }
+}
+
+extension $CesiumCardCopyWith on CesiumCard {
+  /// Returns a callable class that can be used as follows: `instanceOfCesiumCard.copyWith(...)` or like so:`instanceOfCesiumCard.copyWith.fieldName(...)`.
+  // ignore: library_private_types_in_public_api
+  _$CesiumCardCWProxy get copyWith => _$CesiumCardCWProxyImpl(this);
+}
+
 // **************************************************************************
 // JsonSerializableGenerator
 // **************************************************************************
diff --git a/lib/g1/api.dart b/lib/g1/api.dart
index 77dd154519e36481991120a9e141445ad2a69284..3531c79da0866b5fdd004f3356c495bee2878f15 100644
--- a/lib/g1/api.dart
+++ b/lib/g1/api.dart
@@ -1,6 +1,7 @@
 import 'dart:convert';
 import 'dart:io';
 
+import 'package:crypto/crypto.dart';
 import 'package:durt/durt.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter_dotenv/flutter_dotenv.dart';
@@ -30,7 +31,7 @@ final String currency = currencyDotEnv.isEmpty ? 'g1' : currencyDotEnv;
 
 Future<String> getTxHistory(String publicKey) async {
   final Response response =
-      await requestWithRetry(NodeType.duniter, '/tx/history/$publicKey');
+  await requestWithRetry(NodeType.duniter, '/tx/history/$publicKey');
   if (response.statusCode == 200) {
     return response.body;
   } else {
@@ -58,7 +59,7 @@ Future<Response> searchCPlusUser(String searchTerm) async {
       '/user/profile/_search?q=title:$searchTermLower OR issuer:$searchTerm OR title:$searchTermCapitalized OR title:$searchTerm';
 
   final Response response =
-      await requestCPlusWithRetry(query, retryWith404: false);
+  await requestCPlusWithRetry(query, retryWith404: false);
   return response;
 }
 
@@ -70,12 +71,12 @@ Future<Contact> getProfile(String pubKeyRaw,
         '/user/profile/$pubKey',
         retryWith404: false);
     final Map<String, dynamic> result =
-        const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
+    const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
     if (result['found'] == false) {
       return Contact(pubKey: pubKey);
     }
     final Map<String, dynamic> profile =
-        const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
+    const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
     final Contact c = await contactFromResultSearch(profile);
     if (!onlyCPlusProfile) {
       // This penalize the gva rate limit
@@ -116,7 +117,7 @@ Future<List<Contact>> searchWot(String searchTermRaw) async {
   final List<Contact> contacts = <Contact>[];
   if (response.statusCode == HttpStatus.ok) {
     final Map<String, dynamic> data =
-        json.decode(response.body) as Map<String, dynamic>;
+    json.decode(response.body) as Map<String, dynamic>;
     final List<dynamic> results = data['results'] as List<dynamic>;
     // logger('Returning wot results ${results.length}');
     if (results.isNotEmpty) {
@@ -143,11 +144,11 @@ Future<Contact> getWot(Contact contact) async {
   // Will be better to analyze the 404 response (to detect faulty node)
   if (response.statusCode == HttpStatus.ok) {
     final Map<String, dynamic> data =
-        json.decode(response.body) as Map<String, dynamic>;
+    json.decode(response.body) as Map<String, dynamic>;
     final List<dynamic> results = data['results'] as List<dynamic>;
     if (results.isNotEmpty) {
       final List<dynamic> uids =
-          (results[0] as Map<String, dynamic>)['uids'] as List<dynamic>;
+      (results[0] as Map<String, dynamic>)['uids'] as List<dynamic>;
       if (uids.isNotEmpty) {
         // ignore: avoid_dynamic_calls
         return contact.copyWith(nick: uids[0]!['uid'] as String);
@@ -160,14 +161,14 @@ Future<Contact> getWot(Contact contact) async {
 @Deprecated('use getProfile')
 Future<String> _getDataImageFromKey(String publicKey) async {
   final Response response =
-      await requestCPlusWithRetry('/user/profile/$publicKey');
+  await requestCPlusWithRetry('/user/profile/$publicKey');
   if (response.statusCode == HttpStatus.ok) {
     final Map<String, dynamic> data =
-        json.decode(response.body) as Map<String, dynamic>;
+    json.decode(response.body) as Map<String, dynamic>;
     final Map<String, dynamic> source = data['_source'] as Map<String, dynamic>;
     if (source.containsKey('avatar')) {
       final Map<String, dynamic> avatarData =
-          source['avatar'] as Map<String, dynamic>;
+      source['avatar'] as Map<String, dynamic>;
       if (avatarData.containsKey('_content')) {
         final String content = avatarData['_content'] as String;
         return 'data:image/png;base64,$content';
@@ -262,16 +263,18 @@ Future<void> _fetchGvaNodes({bool force = false}) async {
   NodeManager().loading = false;
 }
 
-int nodesWorking(NodeType type) => NodeManager()
-    .nodeList(type)
-    .where((Node n) => n.errors < NodeManager.maxNodeErrors)
-    .toList()
-    .length;
+int nodesWorking(NodeType type) =>
+    NodeManager()
+        .nodeList(type)
+        .where((Node n) => n.errors < NodeManager.maxNodeErrors)
+        .toList()
+        .length;
 
-List<Node> nodesWorkingList(NodeType type) => NodeManager()
-    .nodeList(type)
-    .where((Node n) => n.errors < NodeManager.maxNodeErrors)
-    .toList();
+List<Node> nodesWorkingList(NodeType type) =>
+    NodeManager()
+        .nodeList(type)
+        .where((Node n) => n.errors < NodeManager.maxNodeErrors)
+        .toList();
 
 Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
   final List<Node> lNodes = <Node>[];
@@ -283,14 +286,14 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
     final Response response = await getPeers();
     if (response.statusCode == 200) {
       final Map<String, dynamic> peerList =
-          jsonDecode(response.body) as Map<String, dynamic>;
+      jsonDecode(response.body) as Map<String, dynamic>;
       final List<dynamic> peers = (peerList['peers'] as List<dynamic>)
           .where((dynamic peer) =>
-              (peer as Map<String, dynamic>)['currency'] == currency)
+      (peer as Map<String, dynamic>)['currency'] == currency)
           .where(
               (dynamic peer) => (peer as Map<String, dynamic>)['version'] == 10)
           .where((dynamic peer) =>
-              (peer as Map<String, dynamic>)['status'] == 'UP')
+      (peer as Map<String, dynamic>)['status'] == 'UP')
           .toList();
       // reorder peer list
       peers.shuffle();
@@ -298,7 +301,7 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
         final Map<String, dynamic> peer = peerR as Map<String, dynamic>;
         if (peer['endpoints'] != null) {
           final List<String> endpoints =
-              List<String>.from(peer['endpoints'] as List<dynamic>);
+          List<String>.from(peer['endpoints'] as List<dynamic>);
           for (int j = 0; j < endpoints.length; j++) {
             if (endpoints[j].startsWith(apyType)) {
               final String endpointUnParsed = endpoints[j];
@@ -310,7 +313,9 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
                   final NodeCheck nodeCheck = await _pingNode(endpoint, type);
                   final Duration latency = nodeCheck.latency;
                   logger(
-                      'Evaluating node: $endpoint, latency ${latency.inMicroseconds} currentBlock: ${nodeCheck.currentBlock}');
+                      'Evaluating node: $endpoint, latency ${latency
+                          .inMicroseconds} currentBlock: ${nodeCheck
+                          .currentBlock}');
                   final Node node = Node(
                       url: endpoint,
                       latency: latency.inMicroseconds,
@@ -343,7 +348,8 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
       }
     }
     logger(
-        'Fetched ${lNodes.length} ${type.name} nodes ordered by latency ${lNodes.isNotEmpty ? '(first: ${lNodes.first.url})' : '(zero nodes)'}');
+        'Fetched ${lNodes.length} ${type.name} nodes ordered by latency ${lNodes
+            .isNotEmpty ? '(first: ${lNodes.first.url})' : '(zero nodes)'}');
   } catch (e, stacktrace) {
     await Sentry.captureException(e, stackTrace: stacktrace);
     logger('General error in fetch ${type.name} nodes: $e');
@@ -395,7 +401,8 @@ Future<List<Node>> _fetchNodes(NodeType type) async {
     }
 
     logger(
-        'Fetched ${lNodes.length} ${type.name} nodes ordered by latency (first: ${lNodes.first.url})');
+        'Fetched ${lNodes.length} ${type
+            .name} nodes ordered by latency (first: ${lNodes.first.url})');
   } catch (e, stacktrace) {
     await Sentry.captureException(e, stackTrace: stacktrace);
     logger('General error in fetch ${type.name}: $e');
@@ -412,7 +419,8 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
   int currentBlock = 0;
   Duration latency;
   try {
-    final Stopwatch stopwatch = Stopwatch()..start();
+    final Stopwatch stopwatch = Stopwatch()
+      ..start();
     if (type == NodeType.duniter) {
       final Response response = await http
           .get(Uri.parse('$node/blockchain/current'))
@@ -421,7 +429,7 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
       latency = stopwatch.elapsed;
       if (response.statusCode == 200) {
         final Map<String, dynamic> json =
-            jsonDecode(response.body) as Map<String, dynamic>;
+        jsonDecode(response.body) as Map<String, dynamic>;
         currentBlock = json['number'] as int;
       } else {
         latency = wrongNodeDuration;
@@ -430,7 +438,7 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
       // see: http://g1.data.e-is.pro/network/peering
       await http
           .get(Uri.parse('$node/network/peering'))
-          // Decrease http timeout during ping
+      // Decrease http timeout during ping
           .timeout(timeout);
       stopwatch.stop();
       latency = stopwatch.elapsed;
@@ -446,7 +454,8 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
       latency = balance >= 0 ? stopwatch.elapsed : wrongNodeDuration;
     }
     logger(
-        'Ping tested in node $node ($type), latency ${latency.inMicroseconds}, current block $currentBlock');
+        'Ping tested in node $node ($type), latency ${latency
+            .inMicroseconds}, current block $currentBlock');
     return NodeCheck(latency: latency, currentBlock: currentBlock);
   } catch (e) {
     // Handle exception when node is unavailable etc
@@ -471,12 +480,21 @@ Future<http.Response> requestCPlusWithRetry(String path,
 }
 
 Future<http.Response> requestGvaWithRetry(String path,
-    {bool retryWith404 = true}) async {
-  return _requestWithRetry(NodeType.gva, path, true, retryWith404);
-}
-
-Future<http.Response> _requestWithRetry(
-    NodeType type, String path, bool dontRecord, bool retryWith404) async {
+    {bool retryWith404 = true,
+      bool isGet = true,
+      Map<String, String>? headers,
+      Object? body,
+      Encoding? encoding}) async {
+  return _requestWithRetry(NodeType.gva, path, true, retryWith404,
+      isGet: isGet, headers: headers, body: body, encoding: encoding);
+}
+
+Future<http.Response> _requestWithRetry(NodeType type, String path,
+    bool dontRecord, bool retryWith404,
+    {bool isGet = true,
+      Map<String, String>? headers,
+      Object? body,
+      Encoding? encoding}) async {
   final List<Node> nodes = NodeManager()
       .nodeList(type)
       .where((Node node) => node.errors <= NodeManager.maxNodeErrors)
@@ -485,8 +503,8 @@ Future<http.Response> _requestWithRetry(
     nodes.addAll(type == NodeType.duniter
         ? defaultDuniterNodes
         : type == NodeType.cesiumPlus
-            ? defaultCesiumPlusNodes
-            : defaultGvaNodes);
+        ? defaultCesiumPlusNodes
+        : defaultGvaNodes);
   }
   for (final int timeout in <int>[10]) {
     // only one timeout for now
@@ -495,10 +513,17 @@ Future<http.Response> _requestWithRetry(
       try {
         final Uri url = Uri.parse('${node.url}$path');
         logger('Fetching $url (${type.name})');
-        final int startTime = DateTime.now().millisecondsSinceEpoch;
-        final Response response =
-            await http.get(url).timeout(Duration(seconds: timeout));
-        final int endTime = DateTime.now().millisecondsSinceEpoch;
+        final int startTime = DateTime
+            .now()
+            .millisecondsSinceEpoch;
+        final Response response = isGet
+            ? await http.get(url).timeout(Duration(seconds: timeout))
+            : await http
+            .post(url, body: body, headers: headers, encoding: encoding)
+            .timeout(Duration(seconds: timeout));
+        final int endTime = DateTime
+            .now()
+            .millisecondsSinceEpoch;
         final int newLatency = endTime - startTime;
         if (!kReleaseMode) {
           logger('response.statusCode: ${response.statusCode}');
@@ -543,11 +568,10 @@ Future<http.Response> _requestWithRetry(
       'Cannot make the request to any of the ${nodes.length} nodes');
 }
 
-Future<PayResult> pay(
-    {required String to,
-    required double amount,
-    String? comment,
-    bool? useMempool}) async {
+Future<PayResult> pay({required String to,
+  required double amount,
+  String? comment,
+  bool? useMempool}) async {
   try {
     final SelectedGvaNode selected = getGvaNode();
 
@@ -556,7 +580,8 @@ Future<PayResult> pay(
       final Gva gva = Gva(node: nodeUrl);
       final CesiumWallet wallet = await SharedPreferencesHelper().getWallet();
       logger(
-          'Trying $nodeUrl to send $amount to $to with comment ${comment ?? ''}');
+          'Trying $nodeUrl to send $amount to $to with comment ${comment ??
+              ''}');
 
       final String response = await gva.pay(
           recipient: extractPublicKey(to),
@@ -616,7 +641,9 @@ class PayResult {
 
 String proxyfyNode(String nodeUrl) {
   final String url = inProduction && kIsWeb
-      ? '${window.location.protocol}//${window.location.hostname}/proxy/${nodeUrl.replaceFirst('https://', '').replaceFirst('http://', '')}/'
+      ? '${window.location.protocol}//${window.location
+      .hostname}/proxy/${nodeUrl.replaceFirst('https://', '').replaceFirst(
+      'http://', '')}/'
       : nodeUrl;
   return url;
 }
@@ -624,7 +651,7 @@ String proxyfyNode(String nodeUrl) {
 Future<Tuple2<Map<String, dynamic>?, Node>> gvaHistoryAndBalance(
     String pubKeyRaw,
     [int? pageSize,
-    String? cursor]) async {
+      String? cursor]) async {
   logger('Get tx history (page size: $pageSize: cursor $cursor)');
   final String pubKey = extractPublicKey(pubKeyRaw);
   return gvaFunctionWrapper<Map<String, dynamic>>(
@@ -641,8 +668,8 @@ Future<Tuple2<String?, Node>> gvaNick(String pubKey) async {
       pubKey, (Gva gva) => gva.getUsername(extractPublicKey(pubKey)));
 }
 
-Future<Tuple2<T?, Node>> gvaFunctionWrapper<T>(
-    String pubKey, Future<T?> Function(Gva) specificFunction) async {
+Future<Tuple2<T?, Node>> gvaFunctionWrapper<T>(String pubKey,
+    Future<T?> Function(Gva) specificFunction) async {
   final List<Node> nodes = _getBestGvaNodes();
   for (int i = 0; i < nodes.length; i++) {
     final Node node = nodes[i];
@@ -673,8 +700,8 @@ List<Node> _getBestGvaNodes() {
       .toList();
   final int maxCurrentBlock = fnodes.fold(
       0,
-      (int max, Node node) =>
-          node.currentBlock > max ? node.currentBlock : max);
+          (int max, Node node) =>
+      node.currentBlock > max ? node.currentBlock : max);
   final List<Node> nodes = fnodes
       .where((Node node) => node.currentBlock == maxCurrentBlock)
       .toList();
@@ -699,3 +726,81 @@ void increaseNodeErrors(NodeType type, Node node) {
   logger('Increasing node errors of ${node.url} (${node.errors})');
   NodeManager().updateNode(type, node.copyWith(errors: node.errors + 1));
 }
+
+// http://doc.e-is.pro/cesium-plus-pod/REST_API.html#userprofile
+// https://git.p2p.legal/axiom-team/jaklis/src/branch/master/lib/cesiumCommon.py
+Future<void> createOrUpdateCesiumPlusUser(String name) async {
+  final CesiumWallet wallet = await SharedPreferencesHelper().getWallet();
+  // Define the endpoints
+  final String pubKey = wallet.pubkey;
+
+  // Make the HTTP GET request to check if the user exists
+  final http.Response getResponse = await _requestWithRetry(
+      NodeType.cesiumPlus, '/api/user/profile/$pubKey', false, false);
+
+  // Prepare the user profile data
+  final Map<String, dynamic> userProfile = <String, dynamic>{
+    'version': 2,
+    'issuer': pubKey,
+    'title': name + userNameSuffix,
+    'time': DateTime
+        .now()
+        .millisecondsSinceEpoch ~/
+        1000, // current time in seconds
+  };
+
+  final String userProfileJson = jsonEncode(userProfile);
+  final String signature = wallet.sign(userProfileJson);
+  userProfile['hash'] = calculateHash(userProfileJson);
+  userProfile['signature'] = signature;
+
+  // Convert the user profile data into a JSON string again, now including hash and signature
+  final String userProfileJsonWithHashAndSignature = jsonEncode(userProfile);
+
+  // Prepare the request headers
+  final Map<String, String> headers = <String, String>{
+    'Content-Type': 'application/json',
+  };
+
+  if (getResponse.statusCode == 200) {
+    // User exists, update the user profile
+    final http.Response updateResponse = await _requestWithRetry(
+        NodeType.cesiumPlus, '/api/user/profile/_update', false, false,
+        isGet: false,
+        headers: headers,
+        body: userProfileJsonWithHashAndSignature);
+    if (updateResponse.statusCode == 200) {
+      logger('User profile updated successfully.');
+    } else {
+      logger(
+          'Failed to update user profile. Status code: ${updateResponse
+              .statusCode}');
+    }
+  } else if (getResponse.statusCode == 404) {
+    // User does not exist, create a new user profile
+    final http.Response createResponse = await _requestWithRetry(
+        NodeType.cesiumPlus, '/api/user/profile', false, false,
+        isGet: false,
+        headers: headers,
+        body: userProfileJsonWithHashAndSignature);
+
+    if (createResponse.statusCode == 200) {
+      logger('User profile created successfully.');
+    } else {
+      logger(
+          'Failed to create user profile. Status code: ${createResponse
+              .statusCode}');
+    }
+  } else {
+    logger(
+        'Failed to check if user exists. Status code: ${getResponse
+            .statusCode}');
+  }
+}
+
+
+String calculateHash(String input) {
+  final List<int> bytes = utf8.encode(input); // data being hashed
+  final Digest digest = sha256.convert(bytes);
+  return digest.toString().toUpperCase();
+}
diff --git a/lib/shared_prefs.dart b/lib/shared_prefs.dart
index 5bfa7ef0abf84078df4a1f6283676fee09fbe432..b988686f3736a79d209a03d109a477223dfeab78 100644
--- a/lib/shared_prefs.dart
+++ b/lib/shared_prefs.dart
@@ -113,6 +113,17 @@ class SharedPreferencesHelper {
     return '$pubKey:$checksum';
   }
 
+  String getName({int index = 0}) {
+    final CesiumCard card = cesiumCards[index];
+    return card.name;
+  }
+
+  void setName({int index = 0, required String name}) {
+    final CesiumCard card = cesiumCards[index];
+    cesiumCards[index] = card.copyWith(name: name);
+    saveCesiumCards();
+  }
+
   List<CesiumCard> get cards => cesiumCards;
 
   static const String _currentWalletIndexKey = 'current_wallet_index';
diff --git a/lib/ui/screens/sandbox.dart b/lib/ui/screens/sandbox.dart
index cbe282a92350672e61cf85927c83ce57db2019bb..554e713d8e1b43e1d2dee42a262a46d71f747d31 100644
--- a/lib/ui/screens/sandbox.dart
+++ b/lib/ui/screens/sandbox.dart
@@ -1,5 +1,8 @@
 import 'package:flutter_neumorphic/flutter_neumorphic.dart';
 
+import '../../shared_prefs.dart';
+import '../widgets/first_screen/card_name_editable.dart';
+
 class Sandbox extends StatefulWidget {
   const Sandbox({super.key});
 
@@ -14,6 +17,6 @@ class _SandboxState extends State<Sandbox> {
         appBar: AppBar(
           title: const Text('Sandbox'),
         ),
-        body: const Placeholder());
+        body: const CardNameEditable());
   }
 }
diff --git a/lib/ui/ui_helpers.dart b/lib/ui/ui_helpers.dart
index 72ab13ed385a8d6a6121a9ade43b8f9ec12bcc9b..b91605020daa998e06766f9af8c8923f943683f3 100644
--- a/lib/ui/ui_helpers.dart
+++ b/lib/ui/ui_helpers.dart
@@ -516,3 +516,5 @@ void showQrDialog({
 }
 
 bool get isIOS => !kIsWeb && Platform.isIOS;
+
+const String userNameSuffix = ' (Äž1nkgo)';
diff --git a/lib/ui/widgets/first_screen/card_name_editable.dart b/lib/ui/widgets/first_screen/card_name_editable.dart
new file mode 100644
index 0000000000000000000000000000000000000000..ae0d3fbbc94f92dfbc125cbbcb2cfdc551d09da2
--- /dev/null
+++ b/lib/ui/widgets/first_screen/card_name_editable.dart
@@ -0,0 +1,141 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+
+import '../../../shared_prefs.dart';
+import '../../logger.dart';
+import '../../ui_helpers.dart';
+import 'card_text_style.dart';
+
+class CardNameEditable extends StatefulWidget {
+  const CardNameEditable({super.key});
+
+  @override
+  State<CardNameEditable> createState() => _CardNameEditableState();
+}
+
+class _CardNameEditableState extends State<CardNameEditable> {
+  bool _isEditingText = false;
+  late TextEditingController _editingController;
+  late String currentText;
+  late String defValue;
+  late String name;
+
+  @override
+  void initState() {
+    super.initState();
+    defValue = tr('your_name_here');
+    name = SharedPreferencesHelper().getName();
+    currentText = _currentTextOrDef();
+    _editingController = TextEditingController(text: currentText);
+  }
+
+  String _currentTextOrDef() =>
+      (name == tr('g1_wallet') || name.isEmpty) ? defValue : name;
+
+  @override
+  void dispose() {
+    _editingController.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final bool isSetted = currentText.isNotEmpty && currentText != defValue;
+    return _isEditingText
+        ? SizedBox(
+            width: 150.0,
+            child: SizedBox(
+                height: 40.0,
+                child: TextField(
+                  decoration: InputDecoration(
+                    contentPadding: const EdgeInsets.symmetric(
+                        vertical: 5.0, horizontal: 7.0),
+                    filled: true,
+                    fillColor: Colors.white,
+                    enabledBorder: const OutlineInputBorder(
+                      borderSide: BorderSide(color: Colors.grey),
+                    ),
+                    focusedBorder: const OutlineInputBorder(
+                      borderSide: BorderSide(width: 2.0),
+                    ),
+                    suffixIcon: IconButton(
+                      icon: const Icon(Icons.check),
+                      onPressed: () {
+                        updateName(_editingController.text);
+                      },
+                    ),
+                  ),
+                  cursorColor: Colors.black,
+                  onSubmitted: (String newValue) {
+                    updateName(newValue);
+                  },
+                  // maxLength: 15,
+                  autofocus: true,
+                  controller: _editingController,
+                )))
+        : Tooltip(
+            message: tr('your_name_here'),
+            child: InkWell(
+              child: RichText(
+                // softWrap: true,
+                maxLines: 2,
+                overflow: TextOverflow.ellipsis,
+                text: TextSpan(
+                  style: DefaultTextStyle.of(context).style,
+                  children: <TextSpan>[
+                    if (currentText == defValue)
+                      TextSpan(
+                          text: currentText.toUpperCase(),
+                          style: const TextStyle(
+                              fontFamily: 'SourceCodePro', color: Colors.grey)),
+                    if (isSetted)
+                      TextSpan(
+                          text: currentText, style: cardTextStyle(context, 15)),
+                    /*  TextSpan(
+              text: ' Lorem ipsum dolor sit amet, consectetur adipiscing elit',
+              style: cardTextStyle(context, 15),
+            ), */
+                    if (isSetted)
+                      TextSpan(
+                        text: userNameSuffix,
+                        style: cardTextStyle(context, 10),
+                      ),
+                  ],
+                ),
+              ),
+              onTap: () {
+                setState(() {
+                  try {
+                    _editingController.selection = TextSelection(
+                        baseOffset: 0, extentOffset: currentText.length);
+                  } catch (e) {
+                    logger(e);
+                  }
+                  _isEditingText = true;
+                });
+              },
+            ));
+  }
+
+  void updateName(String newValue) {
+    setState(() {
+      if (newValue.isEmpty) {
+        // FIXME delete old name
+        SharedPreferencesHelper().setName(name: '');
+        currentText = _currentTextOrDef();
+      } else if (newValue == defValue) {
+        currentText = newValue;
+      } else {
+        try {
+          // await createOrUpdateCesiumPlusUser(newValue);
+          SharedPreferencesHelper().setName(name: newValue);
+          currentText = newValue;
+        } catch (e) {
+          logger(e);
+          // FIXME show message
+        }
+      }
+      _isEditingText = false;
+    });
+  }
+}
diff --git a/lib/ui/widgets/first_screen/card_text_style.dart b/lib/ui/widgets/first_screen/card_text_style.dart
index f345ca9967dcb8697de33f69df8ab96e3f91f5aa..a72c9db0b8fab1bab466eabee1e90cbd006faaf3 100644
--- a/lib/ui/widgets/first_screen/card_text_style.dart
+++ b/lib/ui/widgets/first_screen/card_text_style.dart
@@ -1,11 +1,12 @@
 import 'package:flutter/material.dart';
 
-TextStyle cardTextStyle(BuildContext context) {
+TextStyle cardTextStyle(BuildContext context,
+    [double? fontSize, Color? color = Colors.white]) {
   return TextStyle(
     fontFamily: 'SourceCodePro',
     // decoration: TextDecoration.underline,
-    color: Colors.white,
-    fontSize: MediaQuery.of(context).size.width * 0.06,
+    color: color,
+    fontSize: fontSize ?? MediaQuery.of(context).size.width * 0.06,
     fontWeight: FontWeight.bold,
     shadows: <Shadow>[
       Shadow(
diff --git a/lib/ui/widgets/first_screen/credit_card.dart b/lib/ui/widgets/first_screen/credit_card.dart
index 5b57b23f88754efd83a6b0aecbd3710bd05c4667..6326d0ca1c37a6a4b21465345fbbaae94d641c33 100644
--- a/lib/ui/widgets/first_screen/credit_card.dart
+++ b/lib/ui/widgets/first_screen/credit_card.dart
@@ -6,6 +6,7 @@ import 'package:flutter_svg/svg.dart';
 import '../../../shared_prefs.dart';
 import '../../tutorial_keys.dart';
 import '../../ui_helpers.dart';
+import 'card_name_editable.dart';
 import 'card_text_style.dart';
 
 class CreditCard extends StatelessWidget {
@@ -20,6 +21,7 @@ class CreditCard extends StatelessWidget {
     final double cardPadding = bigDevice ? 26.0 : 16.0;
 
     final String pubKey = SharedPreferencesHelper().getPubKey();
+
     return Card(
         elevation: 8.0,
         shape: RoundedRectangleBorder(
@@ -76,18 +78,22 @@ class CreditCard extends StatelessWidget {
                       Padding(
                           padding:
                               EdgeInsets.symmetric(horizontal: cardPadding),
-                          child: GestureDetector(
-                              onTap: () {
-                                showQrDialog(
-                                    context: context, publicKey: pubKey);
-                              },
-                              child: SvgPicture.asset(
-                                width: MediaQuery.of(context).size.width <
-                                        smallScreenWidth
-                                    ? 25
-                                    : 40,
-                                'assets/img/chip.svg',
-                              ))),
+                          child: Row(children: <Widget>[
+                            GestureDetector(
+                                onTap: () {
+                                  showQrDialog(
+                                      context: context, publicKey: pubKey);
+                                },
+                                child: SvgPicture.asset(
+                                  width: MediaQuery.of(context).size.width <
+                                          smallScreenWidth
+                                      ? 25
+                                      : 40,
+                                  'assets/img/chip.svg',
+                                )),
+                            const SizedBox(width: 10.0),
+                            const Expanded(child: CardNameEditable())
+                          ])),
                       const SizedBox(height: 6.0),
                       Padding(
                           padding: