diff --git a/api/auth/presign_test.go b/api/auth/presign_test.go
index 898a6e993..8c4aa218d 100644
--- a/api/auth/presign_test.go
+++ b/api/auth/presign_test.go
@@ -46,6 +46,10 @@ func (m credentialsMock) Put(context.Context, cid.ID, user.ID, *accessbox.Access
 	return oid.Address{}, nil
 }
 
+func (m credentialsMock) Update(context.Context, oid.Address, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error) {
+	return oid.Address{}, nil
+}
+
 func TestCheckSign(t *testing.T) {
 	var accessKeyAddr oid.Address
 	err := accessKeyAddr.DecodeString("8N7CYBY74kxZXoyvA5UNdmovaXqFpwNfvEPsqaN81es2/3tDwq5tR8fByrJcyJwyiuYX7Dae8tyDT7pd8oaL1MBto")
diff --git a/authmate/authmate.go b/authmate/authmate.go
index 451b027ed..0faed2c61 100644
--- a/authmate/authmate.go
+++ b/authmate/authmate.go
@@ -105,6 +105,7 @@ type (
 		Lifetime              time.Duration
 		AwsCliCredentialsFile string
 		ContainerPolicies     ContainerPolicies
+		UpdateCreds           *UpdateOptions
 	}
 
 	// ContainerOptions groups parameters of auth container to put the secret into.
@@ -114,6 +115,12 @@ type (
 		PlacementPolicy string
 	}
 
+	// UpdateOptions groups parameters to update existing the secret into.
+	UpdateOptions struct {
+		Address         oid.Address
+		SecretAccessKey []byte
+	}
+
 	// ObtainSecretOptions contains options for passing to Agent.ObtainSecret method.
 	ObtainSecretOptions struct {
 		SecretAddress  string
@@ -129,11 +136,12 @@ type lifetimeOptions struct {
 
 type (
 	issuingResult struct {
-		AccessKeyID     string `json:"access_key_id"`
-		SecretAccessKey string `json:"secret_access_key"`
-		OwnerPrivateKey string `json:"owner_private_key"`
-		WalletPublicKey string `json:"wallet_public_key"`
-		ContainerID     string `json:"container_id"`
+		AccessKeyID        string `json:"access_key_id"`
+		InitialAccessKeyID string `json:"initial_access_key_id"`
+		SecretAccessKey    string `json:"secret_access_key"`
+		OwnerPrivateKey    string `json:"owner_private_key"`
+		WalletPublicKey    string `json:"wallet_public_key"`
+		ContainerID        string `json:"container_id"`
 	}
 
 	obtainingResult struct {
@@ -144,9 +152,14 @@ type (
 
 func (a *Agent) checkContainer(ctx context.Context, opts ContainerOptions, idOwner user.ID) (cid.ID, error) {
 	if !opts.ID.Equals(cid.ID{}) {
+		a.log.Info("check container", zap.Stringer("cid", opts.ID))
 		return opts.ID, a.frostFS.ContainerExists(ctx, opts.ID)
 	}
 
+	a.log.Info("create container",
+		zap.String("friendly_name", opts.FriendlyName),
+		zap.String("placement_policy", opts.PlacementPolicy))
+
 	var prm PrmContainerCreate
 
 	err := prm.Policy.DecodeString(opts.PlacementPolicy)
@@ -224,7 +237,12 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
 		return fmt.Errorf("create tokens: %w", err)
 	}
 
-	box, secrets, err := accessbox.PackTokens(gatesData)
+	var secret []byte
+	if options.UpdateCreds != nil {
+		secret = options.UpdateCreds.SecretAccessKey
+	}
+
+	box, secrets, err := accessbox.PackTokens(gatesData, secret)
 	if err != nil {
 		return fmt.Errorf("pack tokens: %w", err)
 	}
@@ -233,10 +251,6 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
 
 	var idOwner user.ID
 	user.IDFromKey(&idOwner, options.FrostFSKey.PrivateKey.PublicKey)
-
-	a.log.Info("check container or create", zap.Stringer("cid", options.Container.ID),
-		zap.String("friendly_name", options.Container.FriendlyName),
-		zap.String("placement_policy", options.Container.PlacementPolicy))
 	id, err := a.checkContainer(ctx, options.Container, idOwner)
 	if err != nil {
 		return fmt.Errorf("check container: %w", err)
@@ -245,24 +259,30 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
 	a.log.Info("store bearer token into FrostFS",
 		zap.Stringer("owner_tkn", idOwner))
 
-	addr, err := tokens.
-		New(a.frostFS, secrets.EphemeralKey, cache.DefaultAccessBoxConfig(a.log)).
-		Put(ctx, id, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
+	creds := tokens.New(a.frostFS, secrets.EphemeralKey, cache.DefaultAccessBoxConfig(a.log))
+
+	var addr oid.Address
+	var oldAddr oid.Address
+	if options.UpdateCreds != nil {
+		oldAddr = options.UpdateCreds.Address
+		addr, err = creds.Update(ctx, oldAddr, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
+	} else {
+		addr, err = creds.Put(ctx, id, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
+		oldAddr = addr
+	}
 	if err != nil {
-		return fmt.Errorf("failed to put bearer token: %w", err)
+		return fmt.Errorf("failed to put creds: %w", err)
 	}
 
-	objID := addr.Object()
-	strIDObj := objID.EncodeToString()
-
-	accessKeyID := addr.Container().EncodeToString() + "0" + strIDObj
+	accessKeyID := addr.Container().EncodeToString() + "0" + addr.Object().EncodeToString()
 
 	ir := &issuingResult{
-		AccessKeyID:     accessKeyID,
-		SecretAccessKey: secrets.AccessKey,
-		OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
-		WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
-		ContainerID:     id.EncodeToString(),
+		AccessKeyID:        accessKeyID,
+		InitialAccessKeyID: oldAddr.Container().EncodeToString() + "0" + oldAddr.Object().EncodeToString(),
+		SecretAccessKey:    secrets.AccessKey,
+		OwnerPrivateKey:    hex.EncodeToString(secrets.EphemeralKey.Bytes()),
+		WalletPublicKey:    hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
+		ContainerID:        id.EncodeToString(),
 	}
 
 	enc := json.NewEncoder(w)
@@ -272,7 +292,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
 	}
 
 	if options.AwsCliCredentialsFile != "" {
-		profileName := "authmate_cred_" + strIDObj
+		profileName := "authmate_cred_" + addr.Object().EncodeToString()
 		if _, err = os.Stat(options.AwsCliCredentialsFile); os.IsNotExist(err) {
 			profileName = "default"
 		}
diff --git a/cmd/s3-authmate/main.go b/cmd/s3-authmate/main.go
index 26a0599c1..fded16e08 100644
--- a/cmd/s3-authmate/main.go
+++ b/cmd/s3-authmate/main.go
@@ -3,6 +3,7 @@ package main
 import (
 	"context"
 	"crypto/ecdsa"
+	"encoding/hex"
 	"encoding/json"
 	"fmt"
 	"os"
@@ -19,6 +20,7 @@ import (
 	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
 	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
 	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
+	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
 	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/credentials"
@@ -88,6 +90,7 @@ var (
 const (
 	envWalletPassphrase     = "wallet.passphrase"
 	envWalletGatePassphrase = "wallet.gate.passphrase"
+	envSecretAccessKey      = "secret.access.key"
 )
 
 var zapConfig = zap.Config{
@@ -229,6 +232,12 @@ func issueSecret() *cli.Command {
 				Required:    false,
 				Destination: &containerIDFlag,
 			},
+			&cli.StringFlag{
+				Name:        "access-key-id",
+				Usage:       "access key id for s3 (use this flag to update existing creds, if this flag is provided '--container-id', '--container-friendly-name' and '--container-placement-policy' are ineffective)",
+				Required:    false,
+				Destination: &accessKeyIDFlag,
+			},
 			&cli.StringFlag{
 				Name:        "container-friendly-name",
 				Usage:       "friendly name of auth container to put the secret into",
@@ -333,6 +342,32 @@ It will be ceil rounded to the nearest amount of epoch.`,
 				}
 			}
 
+			var credsToUpdate *authmate.UpdateOptions
+			if len(accessKeyIDFlag) > 0 {
+				secretAccessKeyStr := wallet.GetPassword(viper.GetViper(), envSecretAccessKey)
+				if secretAccessKeyStr == nil {
+					return fmt.Errorf("you must provide AUTHMATE_SECRET_ACCESS_KEY env to update existing creds")
+				}
+
+				secretAccessKey, err := hex.DecodeString(*secretAccessKeyStr)
+				if err != nil {
+					return fmt.Errorf("access key must be hex encoded")
+				}
+
+				var addr oid.Address
+				credAddr := strings.Replace(accessKeyIDFlag, "0", "/", 1)
+				if err = addr.DecodeString(credAddr); err != nil {
+					return fmt.Errorf("failed to parse creds address: %w", err)
+				}
+				// we can create new creds version only in the same container
+				containerID = addr.Container()
+
+				credsToUpdate = &authmate.UpdateOptions{
+					Address:         addr,
+					SecretAccessKey: secretAccessKey,
+				}
+			}
+
 			var gatesPublicKeys []*keys.PublicKey
 			for _, key := range gatesPublicKeysFlag.Value() {
 				gpk, err := keys.NewPublicKeyFromString(key)
@@ -380,6 +415,7 @@ It will be ceil rounded to the nearest amount of epoch.`,
 				ContainerPolicies:     policies,
 				Lifetime:              lifetimeFlag,
 				AwsCliCredentialsFile: awcCliCredFile,
+				UpdateCreds:           credsToUpdate,
 			}
 
 			var tcancel context.CancelFunc
diff --git a/creds/accessbox/accessbox.go b/creds/accessbox/accessbox.go
index 19bf69f0e..a98025e1b 100644
--- a/creds/accessbox/accessbox.go
+++ b/creds/accessbox/accessbox.go
@@ -95,7 +95,8 @@ func (x *AccessBox) Unmarshal(data []byte) error {
 
 // PackTokens adds bearer and session tokens to BearerTokens and SessionToken lists respectively.
 // Session token can be nil.
-func PackTokens(gatesData []*GateData) (*AccessBox, *Secrets, error) {
+// Secret can be nil. In such case secret will be generated.
+func PackTokens(gatesData []*GateData, secret []byte) (*AccessBox, *Secrets, error) {
 	box := &AccessBox{}
 	ephemeralKey, err := keys.NewPrivateKey()
 	if err != nil {
@@ -103,9 +104,11 @@ func PackTokens(gatesData []*GateData) (*AccessBox, *Secrets, error) {
 	}
 	box.OwnerPublicKey = ephemeralKey.PublicKey().Bytes()
 
-	secret, err := generateSecret()
-	if err != nil {
-		return nil, nil, fmt.Errorf("failed to generate accessKey as hex: %w", err)
+	if secret == nil {
+		secret, err = generateSecret()
+		if err != nil {
+			return nil, nil, fmt.Errorf("failed to generate accessKey as hex: %w", err)
+		}
 	}
 
 	if err := box.addTokens(gatesData, ephemeralKey, secret); err != nil {
diff --git a/creds/accessbox/bearer_token_test.go b/creds/accessbox/bearer_token_test.go
index bf95fd5bb..57739dbac 100644
--- a/creds/accessbox/bearer_token_test.go
+++ b/creds/accessbox/bearer_token_test.go
@@ -60,7 +60,7 @@ func TestBearerTokenInAccessBox(t *testing.T) {
 	require.NoError(t, tkn.Sign(sec.PrivateKey))
 
 	gate := NewGateData(cred.PublicKey(), &tkn)
-	box, _, err = PackTokens([]*GateData{gate})
+	box, _, err = PackTokens([]*GateData{gate}, nil)
 	require.NoError(t, err)
 
 	data, err := box.Marshal()
@@ -95,7 +95,7 @@ func TestSessionTokenInAccessBox(t *testing.T) {
 	var newTkn bearer.Token
 	gate := NewGateData(cred.PublicKey(), &newTkn)
 	gate.SessionTokens = []*session.Container{tkn}
-	box, _, err = PackTokens([]*GateData{gate})
+	box, _, err = PackTokens([]*GateData{gate}, nil)
 	require.NoError(t, err)
 
 	data, err := box.Marshal()
@@ -135,7 +135,7 @@ func TestAccessboxMultipleKeys(t *testing.T) {
 		}
 	}
 
-	box, _, err = PackTokens(gates)
+	box, _, err = PackTokens(gates, nil)
 	require.NoError(t, err)
 
 	for i, k := range privateKeys {
@@ -164,7 +164,7 @@ func TestUnknownKey(t *testing.T) {
 	require.NoError(t, tkn.Sign(sec.PrivateKey))
 
 	gate := NewGateData(cred.PublicKey(), &tkn)
-	box, _, err = PackTokens([]*GateData{gate})
+	box, _, err = PackTokens([]*GateData{gate}, nil)
 	require.NoError(t, err)
 
 	_, err = box.GetTokens(wrongCred)
diff --git a/creds/tokens/credentials.go b/creds/tokens/credentials.go
index a9e0befa7..e850e348a 100644
--- a/creds/tokens/credentials.go
+++ b/creds/tokens/credentials.go
@@ -20,6 +20,7 @@ type (
 	Credentials interface {
 		GetBox(context.Context, oid.Address) (*accessbox.Box, error)
 		Put(context.Context, cid.ID, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error)
+		Update(context.Context, oid.Address, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error)
 	}
 
 	cred struct {
@@ -40,6 +41,10 @@ type PrmObjectCreate struct {
 	// File path.
 	Filepath string
 
+	// Optional.
+	// If provided cred object will be created using crdt approach.
+	NewVersionFor *oid.ID
+
 	// Last FrostFS epoch of the object lifetime.
 	ExpirationEpoch uint64
 
@@ -57,12 +62,13 @@ type FrostFS interface {
 	// prevented the object from being created.
 	CreateObject(context.Context, PrmObjectCreate) (oid.ID, error)
 
-	// ReadObjectPayload reads payload of the object from FrostFS network by address
-	// into memory.
+	// GetCredsPayload gets payload of the credential object from FrostFS network.
+	// It uses search by system name and select using CRDT 2PSet. In case of absence CRDT header
+	// it heads object by address.
 	//
 	// It returns exactly one non-nil value. It returns any error encountered which
 	// prevented the object payload from being read.
-	ReadObjectPayload(context.Context, oid.Address) ([]byte, error)
+	GetCredsPayload(context.Context, oid.Address) ([]byte, error)
 }
 
 var (
@@ -103,7 +109,7 @@ func (c *cred) GetBox(ctx context.Context, addr oid.Address) (*accessbox.Box, er
 }
 
 func (c *cred) getAccessBox(ctx context.Context, addr oid.Address) (*accessbox.AccessBox, error) {
-	data, err := c.frostFS.ReadObjectPayload(ctx, addr)
+	data, err := c.frostFS.GetCredsPayload(ctx, addr)
 	if err != nil {
 		return nil, fmt.Errorf("read payload: %w", err)
 	}
@@ -118,6 +124,15 @@ func (c *cred) getAccessBox(ctx context.Context, addr oid.Address) (*accessbox.A
 }
 
 func (c *cred) Put(ctx context.Context, idCnr cid.ID, issuer user.ID, box *accessbox.AccessBox, expiration uint64, keys ...*keys.PublicKey) (oid.Address, error) {
+	return c.createObject(ctx, idCnr, nil, issuer, box, expiration, keys...)
+}
+
+func (c *cred) Update(ctx context.Context, addr oid.Address, issuer user.ID, box *accessbox.AccessBox, expiration uint64, keys ...*keys.PublicKey) (oid.Address, error) {
+	objID := addr.Object()
+	return c.createObject(ctx, addr.Container(), &objID, issuer, box, expiration, keys...)
+}
+
+func (c *cred) createObject(ctx context.Context, cnrID cid.ID, newVersionFor *oid.ID, issuer user.ID, box *accessbox.AccessBox, expiration uint64, keys ...*keys.PublicKey) (oid.Address, error) {
 	if len(keys) == 0 {
 		return oid.Address{}, ErrEmptyPublicKeys
 	} else if box == nil {
@@ -130,9 +145,10 @@ func (c *cred) Put(ctx context.Context, idCnr cid.ID, issuer user.ID, box *acces
 
 	idObj, err := c.frostFS.CreateObject(ctx, PrmObjectCreate{
 		Creator:         issuer,
-		Container:       idCnr,
+		Container:       cnrID,
 		Filepath:        strconv.FormatInt(time.Now().Unix(), 10) + "_access.box",
 		ExpirationEpoch: expiration,
+		NewVersionFor:   newVersionFor,
 		Payload:         data,
 	})
 	if err != nil {
@@ -141,7 +157,7 @@ func (c *cred) Put(ctx context.Context, idCnr cid.ID, issuer user.ID, box *acces
 
 	var addr oid.Address
 	addr.SetObject(idObj)
-	addr.SetContainer(idCnr)
+	addr.SetContainer(cnrID)
 
 	return addr, nil
 }
diff --git a/internal/frostfs/authmate.go b/internal/frostfs/authmate.go
new file mode 100644
index 000000000..7d497affd
--- /dev/null
+++ b/internal/frostfs/authmate.go
@@ -0,0 +1,157 @@
+package frostfs
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io"
+	"strconv"
+	"time"
+
+	objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
+	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
+	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
+	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
+	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/crdt"
+	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
+	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
+	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
+	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
+)
+
+const (
+	accessBoxCRDTNameAttr = "S3-Access-Box-CRDT-Name"
+)
+
+// AuthmateFrostFS is a mediator which implements authmate.FrostFS through pool.Pool.
+type AuthmateFrostFS struct {
+	frostFS *FrostFS
+}
+
+// NewAuthmateFrostFS creates new AuthmateFrostFS using provided pool.Pool.
+func NewAuthmateFrostFS(p *pool.Pool) *AuthmateFrostFS {
+	return &AuthmateFrostFS{frostFS: NewFrostFS(p)}
+}
+
+// ContainerExists implements authmate.FrostFS interface method.
+func (x *AuthmateFrostFS) ContainerExists(ctx context.Context, idCnr cid.ID) error {
+	_, err := x.frostFS.Container(ctx, idCnr)
+	if err != nil {
+		return fmt.Errorf("get container via connection pool: %w", err)
+	}
+
+	return nil
+}
+
+// TimeToEpoch implements authmate.FrostFS interface method.
+func (x *AuthmateFrostFS) TimeToEpoch(ctx context.Context, futureTime time.Time) (uint64, uint64, error) {
+	return x.frostFS.TimeToEpoch(ctx, time.Now(), futureTime)
+}
+
+// CreateContainer implements authmate.FrostFS interface method.
+func (x *AuthmateFrostFS) CreateContainer(ctx context.Context, prm authmate.PrmContainerCreate) (cid.ID, error) {
+	basicACL := acl.Private
+	// allow reading objects to OTHERS in order to provide read access to S3 gateways
+	basicACL.AllowOp(acl.OpObjectGet, acl.RoleOthers)
+	basicACL.AllowOp(acl.OpObjectHead, acl.RoleOthers)
+	basicACL.AllowOp(acl.OpObjectSearch, acl.RoleOthers)
+
+	return x.frostFS.CreateContainer(ctx, layer.PrmContainerCreate{
+		Creator:  prm.Owner,
+		Policy:   prm.Policy,
+		Name:     prm.FriendlyName,
+		BasicACL: basicACL,
+	})
+}
+
+// GetCredsPayload implements authmate.FrostFS interface method.
+func (x *AuthmateFrostFS) GetCredsPayload(ctx context.Context, addr oid.Address) ([]byte, error) {
+	versions, err := x.getCredVersions(ctx, addr)
+	if err != nil {
+		return nil, err
+	}
+
+	credObjID := addr.Object()
+	if last := versions.GetLast(); last != nil {
+		credObjID = last.OjbID
+	}
+
+	res, err := x.frostFS.ReadObject(ctx, layer.PrmObjectRead{
+		Container:   addr.Container(),
+		Object:      credObjID,
+		WithPayload: true,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	defer res.Payload.Close()
+
+	return io.ReadAll(res.Payload)
+}
+
+// CreateObject implements authmate.FrostFS interface method.
+func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObjectCreate) (oid.ID, error) {
+	attributes := [][2]string{{objectv2.SysAttributeExpEpoch, strconv.FormatUint(prm.ExpirationEpoch, 10)}}
+
+	if prm.NewVersionFor != nil {
+		var addr oid.Address
+		addr.SetContainer(prm.Container)
+		addr.SetObject(*prm.NewVersionFor)
+
+		versions, err := x.getCredVersions(ctx, addr)
+		if err != nil {
+			return oid.ID{}, err
+		}
+
+		if versions.GetLast() == nil {
+			versions.AppendVersion(&crdt.ObjectVersion{OjbID: addr.Object()})
+		}
+
+		for key, val := range versions.GetCRDTHeaders() {
+			attributes = append(attributes, [2]string{key, val})
+		}
+
+		attributes = append(attributes, [2]string{accessBoxCRDTNameAttr, versions.Name()})
+	}
+
+	return x.frostFS.CreateObject(ctx, layer.PrmObjectCreate{
+		Creator:    prm.Creator,
+		Container:  prm.Container,
+		Filepath:   prm.Filepath,
+		Attributes: attributes,
+		Payload:    bytes.NewReader(prm.Payload),
+	})
+}
+
+func (x *AuthmateFrostFS) getCredVersions(ctx context.Context, addr oid.Address) (*crdt.ObjectVersions, error) {
+	objCredSystemName := credVersionSysName(addr.Container(), addr.Object())
+	credVersions, err := x.frostFS.SearchObjects(ctx, layer.PrmObjectSearch{
+		Container:      addr.Container(),
+		ExactAttribute: [2]string{accessBoxCRDTNameAttr, objCredSystemName},
+	})
+	if err != nil {
+		return nil, fmt.Errorf("search s3 access boxes: %w", err)
+	}
+
+	versions := crdt.NewObjectVersions(objCredSystemName)
+
+	for _, id := range credVersions {
+		objVersion, err := x.frostFS.ReadObject(ctx, layer.PrmObjectRead{
+			Container:  addr.Container(),
+			Object:     id,
+			WithHeader: true,
+		})
+		if err != nil {
+			return nil, fmt.Errorf("head crdt access box '%s': %w", id.EncodeToString(), err)
+		}
+
+		versions.AppendVersion(crdt.NewObjectVersion(objVersion.Head))
+	}
+
+	return versions, nil
+}
+
+func credVersionSysName(cnrID cid.ID, objID oid.ID) string {
+	return cnrID.EncodeToString() + "0" + objID.EncodeToString()
+}