diff --git a/src/Reown.Core.Crypto/Runtime/Crypto.cs b/src/Reown.Core.Crypto/Runtime/Crypto.cs
index c84eee4..24a8875 100644
--- a/src/Reown.Core.Crypto/Runtime/Crypto.cs
+++ b/src/Reown.Core.Crypto/Runtime/Crypto.cs
@@ -119,10 +119,12 @@ public string Context
/// The hash of the given input as a hex string
public string HashKey(string key)
{
- using (var sha256 = SHA256.Create())
- {
- return sha256.ComputeHash(key.HexToByteArray()).ToHex();
- }
+#if NET7_0_OR_GREATER
+ return SHA256.HashData(key.HexToByteArray()).ToHex();
+#else
+ using var sha256 = SHA256.Create();
+ return sha256.ComputeHash(key.HexToByteArray()).ToHex();
+#endif
}
public void Dispose()
diff --git a/src/Reown.Sign/Runtime/Engine.cs b/src/Reown.Sign/Runtime/Engine.cs
index 235d660..201ee06 100644
--- a/src/Reown.Sign/Runtime/Engine.cs
+++ b/src/Reown.Sign/Runtime/Engine.cs
@@ -929,9 +929,7 @@ public async Task Authenticate(AuthParams authParams)
{
"wc_sessionAuthenticate"
});
-
- ReownLogger.Log($"Generated new pairing: {pairingData.Uri}");
-
+
var publicKey = await Client.CoreClient.Crypto.GenerateKeyPair();
var responseTopic = Client.CoreClient.Crypto.HashKey(publicKey);
@@ -946,22 +944,16 @@ await Task.WhenAll(
);
await Client.CoreClient.Relayer.Subscribe(responseTopic);
-
- ReownLogger.Log($"Subscribed to relayer: {responseTopic}. Pairing topic: {pairingData.Topic}");
-
+
if (authParams.Methods is { Length: > 0 })
{
- ReownLogger.Log("Methods provided, creating recap");
-
var chainId = authParams.Chains[0];
var @namespace = Core.Utils.ExtractChainNamespace(chainId);
- var recapStr = ReCapUtils.CreateEncodedRecap(@namespace, "request", authParams.Methods);
-
- ReownLogger.Log($"Created recap: {recapStr}");
-
+ var recapStr = ReCap.CreateEncodedRecap(@namespace, "request", authParams.Methods);
+
authParams.Resources ??= new List();
- if (!ReCapUtils.TryGetRecapFromResources(authParams.Resources, out var existingRecap))
+ if (!ReCap.TryGetRecapFromResources(authParams.Resources, out var existingRecap))
{
authParams.Resources.Add(recapStr);
}
@@ -971,7 +963,7 @@ await Task.WhenAll(
// using .RemoveAt to remove the last element given we already checked it's a recap and will replace it
authParams.Resources.RemoveAt(authParams.Resources.Count - 1);
- var mergedRecap = ReCapUtils.MergeEncodedRecaps(recapStr, existingRecap);
+ var mergedRecap = ReCap.MergeEncodedRecaps(recapStr, existingRecap);
authParams.Resources.Add(mergedRecap);
}
}
@@ -1146,7 +1138,6 @@ public async Task RejectSessionAuthenticate(RejectParams rejectParams)
if (pendingRequest == null)
throw new InvalidOperationException($"No pending request found for the id {rejectParams.Id}");
- // var receiverPublicKey = pendingRequest.Requester.PublicKey;
var senderPublicKey = await Client.CoreClient.Crypto.GenerateKeyPair();
var responseTopic = Client.CoreClient.Crypto.HashKey(senderPublicKey);
@@ -1197,10 +1188,10 @@ public async Task ApproveSessionAuthenticate(long requestId, Caca
var address = CacaoUtils.ExtractDidAddress(cacao.Payload.Iss);
- if (ReCapUtils.TryGetRecapFromResources(cacao.Payload.Resources, out var recap))
+ if (ReCap.TryGetRecapFromResources(cacao.Payload.Resources, out var encodedRecap))
{
- var methodsFromRecap = ReCapUtils.GetActionsFromRecap(recap);
- var chainsFromRecap = ReCapUtils.GetChainsFromRecap(recap);
+ var methodsFromRecap = ReCap.GetActionsFromEncodedRecap(encodedRecap);
+ var chainsFromRecap = ReCap.GetChainsFromEncodedRecap(encodedRecap);
approvedMethods.UnionWith(methodsFromRecap);
approvedChains.UnionWith(chainsFromRecap);
@@ -1212,9 +1203,6 @@ public async Task ApproveSessionAuthenticate(long requestId, Caca
}
}
- ReownLogger.Log($"Approved methods: {string.Join(", ", approvedMethods)}");
- ReownLogger.Log($"Approved accounts: {string.Join(", ", approvedAccounts)}");
-
var sessionTopic = await Client.CoreClient.Crypto.GenerateSharedKey(senderPublicKey, receiverPublicKey);
SessionStruct session = default;
@@ -1244,15 +1232,12 @@ public async Task ApproveSessionAuthenticate(long requestId, Caca
PairingTopic = pendingRequest.PairingTopic
};
- ReownLogger.Log($"Approving session with topic {sessionTopic}");
await Client.CoreClient.Relayer.Subscribe(sessionTopic);
await Client.Session.Set(sessionTopic, session);
- ReownLogger.Log($"Updating session metadata for pairing topic {pendingRequest.PairingTopic}");
await Client.CoreClient.Pairing.UpdateMetadata(pendingRequest.PairingTopic, session.Peer.Metadata);
}
- ReownLogger.Log("Sending response to session authenticate request");
await MessageHandler.SendResult(requestId, responseTopic, new AuthenticateResponse
{
Cacaos = auths,
diff --git a/src/Reown.Sign/Runtime/Internals/EngineHandler.cs b/src/Reown.Sign/Runtime/Internals/EngineHandler.cs
index 13ca7ba..5ee6258 100644
--- a/src/Reown.Sign/Runtime/Internals/EngineHandler.cs
+++ b/src/Reown.Sign/Runtime/Internals/EngineHandler.cs
@@ -391,10 +391,10 @@ async Task IEnginePrivate.OnAuthenticateResponse(string topic, JsonRpcResponse();
var parsedAddress = CacaoUtils.ExtractDidAddress(cacao.Payload.Iss);
- if (ReCapUtils.TryGetRecapFromResources(cacao.Payload.Resources, out var recapStr))
+ if (ReCap.TryGetRecapFromResources(cacao.Payload.Resources, out var recapStr))
{
- var methodsFromRecap = ReCapUtils.GetActionsFromRecap(recapStr);
- var chainsFromRecap = ReCapUtils.GetChainsFromRecap(recapStr);
+ var methodsFromRecap = ReCap.GetActionsFromEncodedRecap(recapStr);
+ var chainsFromRecap = ReCap.GetChainsFromEncodedRecap(recapStr);
approvedMethods.UnionWith(methodsFromRecap);
approvedChains.UnionWith(chainsFromRecap);
}
@@ -431,12 +431,6 @@ async Task IEnginePrivate.OnAuthenticateResponse(string topic, JsonRpcResponse? Resources;
+ public List? Resources { get; set; }
[JsonProperty("pairingTopic", NullValueHandling = NullValueHandling.Ignore)]
- public string? PairingTopic;
+ public string? PairingTopic { get; set; }
[JsonProperty("methods", NullValueHandling = NullValueHandling.Ignore)]
- public string[]? Methods;
+ public string[]? Methods { get; set; }
[JsonProperty("version", NullValueHandling = NullValueHandling.Ignore)]
- public string? Version;
+ public string? Version { get; set; }
public AuthPayloadParams()
@@ -85,17 +85,17 @@ public void Populate(ICollection supportedCains, ICollection sup
var statement = Statement ?? string.Empty;
- if (!ReCapUtils.TryGetDecodedRecapFromResources(Resources, out var recap))
+ if (!ReCap.TryGetDecodedRecapFromResources(Resources, out var recap))
throw new InvalidOperationException("Recap not found in resources");
- var actionsFromRecap = ReCapUtils.GetActionsFromRecap(recap);
+ var actionsFromRecap = recap.GetActions();
var approvedActions = actionsFromRecap.Intersect(supportedMethods).ToArray();
if (approvedActions.Length == 0)
throw new InvalidOperationException($"Supported methods don't satisfy the requested: {string.Join(", ", actionsFromRecap)}. "
+ $"Supported methods: {string.Join(", ", supportedMethods)}");
var updatedResources = Resources ?? new List();
- var formattedActions = ReCapUtils.AssignAbilityToActions("request", approvedActions, new Dictionary
+ var formattedActions = ReCap.AssignAbilityToActions("request", approvedActions, new Dictionary
{
{ "chains", approvedChains }
});
@@ -103,10 +103,10 @@ public void Populate(ICollection supportedCains, ICollection sup
recap.AddResources("eip115", formattedActions);
updatedResources.RemoveAt(updatedResources.Count - 1);
- updatedResources.Add(ReCapUtils.EncodeRecap(recap));
+ updatedResources.Add(recap.Encode());
Chains = approvedChains;
Methods = approvedActions;
- Statement = ReCapUtils.FormatStatementFromRecap(recap, statement);
+ Statement = recap.FormatStatement(statement);
Resources = updatedResources;
}
}
diff --git a/src/Reown.Sign/Runtime/Models/Cacao/CacaoPayload.cs b/src/Reown.Sign/Runtime/Models/Cacao/CacaoPayload.cs
index 1f5718d..bd70240 100644
--- a/src/Reown.Sign/Runtime/Models/Cacao/CacaoPayload.cs
+++ b/src/Reown.Sign/Runtime/Models/Cacao/CacaoPayload.cs
@@ -10,37 +10,37 @@ namespace Reown.Sign.Models.Cacao
public class CacaoPayload
{
[JsonProperty("domain")]
- public readonly string Domain;
+ public string Domain { get; }
[JsonProperty("iss")]
- public readonly string Iss; // did:pkh
+ public string Iss { get; } // did:pkh
[JsonProperty("aud")]
- public readonly string Aud;
+ public string Aud { get; }
[JsonProperty("version")]
- public readonly string Version;
+ public string Version { get; }
[JsonProperty("nonce")]
- public readonly string Nonce;
+ public string Nonce { get; }
[JsonProperty("iat")]
- public readonly string IssuedAt;
+ public string IssuedAt { get; }
[JsonProperty("nbf")]
- public readonly string? NotBefore;
+ public string? NotBefore { get; }
[JsonProperty("exp")]
- public readonly string? Expiration;
+ public string? Expiration { get; }
[JsonProperty("statement", NullValueHandling = NullValueHandling.Ignore)]
- public readonly string? Statement;
+ public string? Statement { get; }
[JsonProperty("requestId", NullValueHandling = NullValueHandling.Ignore)]
- public readonly string? RequestId;
+ public string? RequestId { get; }
[JsonProperty("resources", NullValueHandling = NullValueHandling.Ignore)]
- public readonly string[]? Resources;
+ public string[]? Resources { get; }
public CacaoPayload(
string domain,
@@ -106,10 +106,10 @@ public string FormatMessage()
? $"Resources:\n{string.Join('\n', Resources.Select(resource => $"- {resource}"))}"
: null;
- if (ReCapUtils.TryGetRecapFromResources(Resources, out var recapStr))
+ if (ReCap.TryGetRecapFromResources(Resources, out var recapStr))
{
- var decoded = ReCapUtils.DecodeRecap(recapStr);
- statement ??= ReCapUtils.FormatStatementFromRecap(decoded, statement);
+ var decoded = ReCap.Decode(recapStr);
+ statement ??= decoded.FormatStatement(statement);
}
var message = string.Join('\n', new[]
diff --git a/src/Reown.Sign/Runtime/Utils/ReCapUtils.cs b/src/Reown.Sign/Runtime/Models/Cacao/ReCap.cs
similarity index 82%
rename from src/Reown.Sign/Runtime/Utils/ReCapUtils.cs
rename to src/Reown.Sign/Runtime/Models/Cacao/ReCap.cs
index 5947560..2a15bfd 100644
--- a/src/Reown.Sign/Runtime/Utils/ReCapUtils.cs
+++ b/src/Reown.Sign/Runtime/Models/Cacao/ReCap.cs
@@ -6,10 +6,111 @@
namespace Reown.Sign.Utils
{
+ public class AttValue
+ {
+ [JsonExtensionData]
+ public Dictionary Properties { get; set; }
+ }
+
+ [Serializable]
public class ReCap
{
[JsonProperty("att")]
- public Dictionary Att;
+ public Dictionary Att { get; private set; }
+
+ [JsonConstructor]
+ public ReCap(Dictionary att)
+ {
+ Att = att;
+ }
+
+ public ReCap(string resource, string ability, string[] actions, Dictionary limits = null)
+ {
+ Att = new Dictionary
+ {
+ {
+ resource, new AttValue
+ {
+ Properties = AssignAbilityToActions(ability, actions, limits)
+ .ToDictionary(
+ kvp => kvp.Key,
+ kvp => JToken.FromObject(kvp.Value)
+ )
+ }
+ }
+ };
+ }
+
+ public string Encode()
+ {
+ Validate(this);
+
+ var recapStr = JsonConvert.SerializeObject(this);
+ var recapBytes = System.Text.Encoding.UTF8.GetBytes(recapStr);
+ var recapBase64 = Convert.ToBase64String(recapBytes);
+ var recapPadded = recapBase64.Replace("=", string.Empty);
+
+ return $"urn:recap:{recapPadded}";
+ }
+
+ public string FormatStatement(string customStatement = "")
+ {
+ Validate(this);
+
+ const string statementBase = "I further authorize the stated URI to perform the following actions on my behalf: ";
+
+ if (!string.IsNullOrWhiteSpace(customStatement) && customStatement.Contains(statementBase))
+ return customStatement;
+
+ var statementForRecap = new List();
+ var currentCounter = 0;
+ foreach (var resource in Att.Keys)
+ {
+ var actions = Att[resource].Properties.Keys.Select(ability => new
+ {
+ Ability = ability.Split("/")[0],
+ Action = ability.Split("/")[1]
+ });
+
+ actions = actions.OrderBy(action => action.Action);
+ var uniqueAbilities = new Dictionary>();
+ foreach (var action in actions)
+ {
+ if (!uniqueAbilities.ContainsKey(action.Ability))
+ uniqueAbilities[action.Ability] = new List();
+
+ uniqueAbilities[action.Ability].Add(action.Action);
+ }
+
+ var abilities = uniqueAbilities.Keys.Select(ability =>
+ {
+ currentCounter++;
+ return $"({currentCounter}) '{ability}': '{string.Join("', '", uniqueAbilities[ability])}' for '{resource}'.";
+ });
+
+ statementForRecap.Add(string.Join(", ", abilities).Replace(".,", "."));
+ }
+
+ var recapStatement = string.Join(" ", statementForRecap);
+ recapStatement = $"{statementBase}{recapStatement}";
+
+ return !string.IsNullOrWhiteSpace(customStatement) ? $"{customStatement} {recapStatement}" : recapStatement;
+ }
+
+ public string[] GetActions()
+ {
+ try
+ {
+ // Methods are only available for eip155 as per the current implementation
+ return Att["eip155"] is { } resources
+ ? resources.Properties.Keys.Select(ability => ability.Split("/")[1]).ToArray()
+ : Array.Empty();
+ }
+ catch (Exception)
+ {
+ return Array.Empty();
+ }
+ }
public void AddResources(string resource, Dictionary[]> actions)
{
@@ -51,80 +152,44 @@ public void AddResources(string resource, Dictionary Properties { get; set; }
- }
-
- public class ReCapUtils
- {
public static string CreateEncodedRecap(string resource, string ability, string[] actions, Dictionary limits = null)
{
- var recap = CreateRecap(resource, ability, actions, limits);
- return EncodeRecap(recap);
+ var recap = new ReCap(resource, ability, actions, limits);
+ return recap.Encode();
}
- public static ReCap CreateRecap(string resource, string ability, string[] actions, Dictionary limits = null)
+ public static ReCap Decode(string recapStr)
{
- return new ReCap
- {
- Att = new Dictionary
- {
- {
- resource, new AttValue
- {
- Properties = AssignAbilityToActions(ability, actions, limits)
- .ToDictionary(
- kvp => kvp.Key,
- kvp => JToken.FromObject(kvp.Value)
- )
- }
- }
- }
- };
- }
-
-
- public static Dictionary[]> AssignAbilityToActions(
- string ability,
- IEnumerable actions,
- Dictionary limits = null)
- {
- if (string.IsNullOrWhiteSpace(ability))
- throw new ArgumentException("Ability cannot be null or whitespace.", nameof(ability));
+ var paddedRecap = recapStr.Replace("urn:recap:", string.Empty);
- if (actions == null)
- throw new ArgumentNullException(nameof(actions), "Actions list cannot be null.");
+ paddedRecap = paddedRecap.TrimEnd('=');
+ var paddingNeeded = (4 - paddedRecap.Length % 4) % 4;
+ paddedRecap = paddedRecap.PadRight(paddedRecap.Length + paddingNeeded, '=');
- // Sort actions alphabetically
- var sortedActions = actions.OrderBy(action => action, StringComparer.Ordinal);
+ var decodedRecap = Convert.FromBase64String(paddedRecap);
+ var decodedRecapStr = System.Text.Encoding.UTF8.GetString(decodedRecap);
+ var recap = JsonConvert.DeserializeObject(decodedRecapStr);
- limits ??= new Dictionary();
+ Validate(recap);
- var abilitiesDictionary = new Dictionary[]>();
- foreach (var action in sortedActions)
- {
- var key = $"{ability}/{action}";
- abilitiesDictionary[key] = new[]
- {
- limits
- };
- }
+ return recap;
+ }
- return abilitiesDictionary;
+ public static string[] GetActionsFromEncodedRecap(string recapStr)
+ {
+ var decodedRecap = Decode(recapStr);
+ return decodedRecap.GetActions();
}
public static bool TryGetDecodedRecapFromResources(IEnumerable resources, out ReCap recap)
{
var success = TryGetRecapFromResources(resources, out var recapStr);
- recap = success ? DecodeRecap(recapStr) : null;
+ recap = success ? Decode(recapStr) : null;
return success;
}
-
+
public static bool TryGetRecapFromResources(IEnumerable resources, out string recap)
{
if (resources == null)
@@ -132,7 +197,7 @@ public static bool TryGetRecapFromResources(IEnumerable resources, out s
recap = null;
return false;
}
-
+
// Per ERC-5573, ReCap is always the last resource in the list
recap = resources.LastOrDefault();
return IsReCap(recap);
@@ -143,30 +208,9 @@ public static bool IsReCap(string resource)
return !string.IsNullOrWhiteSpace(resource) && resource.StartsWith("urn:recap");
}
- public static string[] GetActionsFromRecap(string recapStr)
- {
- var decodedRecap = DecodeRecap(recapStr);
- return GetActionsFromRecap(decodedRecap);
- }
-
- public static string[] GetActionsFromRecap(ReCap recap)
- {
- try
- {
- // Methods are only available for eip155 as per the current implementation
- return recap.Att["eip155"] is { } resources
- ? resources.Properties.Keys.Select(ability => ability.Split("/")[1]).ToArray()
- : Array.Empty();
- }
- catch (Exception)
- {
- return Array.Empty();
- }
- }
-
- public static List GetChainsFromRecap(string recapStr)
+ public static List GetChainsFromEncodedRecap(string recapStr)
{
- var decodedRecap = DecodeRecap(recapStr);
+ var decodedRecap = Decode(recapStr);
var recapChains = new List();
@@ -196,36 +240,7 @@ public static List GetChainsFromRecap(string recapStr)
return recapChains.Distinct().ToList();
}
- public static string EncodeRecap(ReCap recap)
- {
- ValidateRecap(recap);
-
- var recapStr = JsonConvert.SerializeObject(recap);
- var recapBytes = System.Text.Encoding.UTF8.GetBytes(recapStr);
- var recapBase64 = Convert.ToBase64String(recapBytes);
- var recapPadded = recapBase64.Replace("=", string.Empty);
-
- return $"urn:recap:{recapPadded}";
- }
-
- public static ReCap DecodeRecap(string recapStr)
- {
- var paddedRecap = recapStr.Replace("urn:recap:", string.Empty);
-
- paddedRecap = paddedRecap.TrimEnd('=');
- var paddingNeeded = (4 - paddedRecap.Length % 4) % 4;
- paddedRecap = paddedRecap.PadRight(paddedRecap.Length + paddingNeeded, '=');
-
- var decodedRecap = Convert.FromBase64String(paddedRecap);
- var decodedRecapStr = System.Text.Encoding.UTF8.GetString(decodedRecap);
- var recap = JsonConvert.DeserializeObject(decodedRecapStr);
-
- ValidateRecap(recap);
-
- return recap;
- }
-
- public static void ValidateRecap(ReCap recap)
+ public static void Validate(ReCap recap)
{
if (recap?.Att == null)
throw new ArgumentException("No `att` property found");
@@ -248,32 +263,57 @@ public static void ValidateRecap(ReCap recap)
if (limits == null || limits.Count == 0)
throw new ArgumentException($"Value of ability '{key}' is an empty array; it must contain at least one limit object.");
- foreach (var limitToken in limits)
+ foreach (var limitToken in limits.Where(limitToken => limitToken is not JObject))
{
- if (limitToken is not JObject)
- throw new ArgumentException($"Each limit for ability '{key}' must be an object. Invalid limit: {limitToken}");
+ throw new ArgumentException($"Each limit for ability '{key}' must be an object. Invalid limit: {limitToken}");
}
}
}
}
+ public static Dictionary[]> AssignAbilityToActions(
+ string ability,
+ IEnumerable actions,
+ Dictionary limits = null)
+ {
+ if (string.IsNullOrWhiteSpace(ability))
+ throw new ArgumentException("Ability cannot be null or whitespace.", nameof(ability));
+
+ if (actions == null)
+ throw new ArgumentNullException(nameof(actions), "Actions list cannot be null.");
+
+ // Sort actions alphabetically
+ var sortedActions = actions.OrderBy(action => action, StringComparer.Ordinal);
+
+ limits ??= new Dictionary();
+
+ var abilitiesDictionary = new Dictionary[]>();
+ foreach (var action in sortedActions)
+ {
+ var key = $"{ability}/{action}";
+ abilitiesDictionary[key] = new[]
+ {
+ limits
+ };
+ }
+
+ return abilitiesDictionary;
+ }
+
public static string MergeEncodedRecaps(string recapStr1, string recapStr2)
{
- var decoded1 = DecodeRecap(recapStr1);
- var decoded2 = DecodeRecap(recapStr2);
+ var decoded1 = Decode(recapStr1);
+ var decoded2 = Decode(recapStr2);
var mergedRecap = MergeRecaps(decoded1, decoded2);
- return EncodeRecap(mergedRecap);
+ return mergedRecap.Encode();
}
public static ReCap MergeRecaps(ReCap recap1, ReCap recap2)
{
- ValidateRecap(recap1);
- ValidateRecap(recap2);
+ Validate(recap1);
+ Validate(recap2);
- var mergedRecap = new ReCap
- {
- Att = new Dictionary(StringComparer.Ordinal)
- };
+ var mergedRecap = new ReCap(new Dictionary(StringComparer.Ordinal));
var allKeys = recap1.Att.Keys.Union(recap2.Att.Keys, StringComparer.Ordinal).OrderBy(k => k);
@@ -354,52 +394,8 @@ public static ReCap MergeRecaps(ReCap recap1, ReCap recap2)
mergedRecap.Att[key] = mergedValue;
}
- ValidateRecap(mergedRecap);
+ Validate(mergedRecap);
return mergedRecap;
}
-
- public static string FormatStatementFromRecap(ReCap recap, string statement = "")
- {
- ValidateRecap(recap);
-
- const string statementBase = "I further authorize the stated URI to perform the following actions on my behalf: ";
-
- if (!string.IsNullOrWhiteSpace(statement) && statement.Contains(statementBase))
- return statement;
-
- var statementForRecap = new List();
- var currentCounter = 0;
- foreach (var resource in recap.Att.Keys)
- {
- var actions = recap.Att[resource].Properties.Keys.Select(ability => new
- {
- Ability = ability.Split("/")[0],
- Action = ability.Split("/")[1]
- });
-
- actions = actions.OrderBy(action => action.Action);
- var uniqueAbilities = new Dictionary>();
- foreach (var action in actions)
- {
- if (!uniqueAbilities.ContainsKey(action.Ability))
- uniqueAbilities[action.Ability] = new List();
-
- uniqueAbilities[action.Ability].Add(action.Action);
- }
-
- var abilities = uniqueAbilities.Keys.Select(ability =>
- {
- currentCounter++;
- return $"({currentCounter}) '{ability}': '{string.Join("', '", uniqueAbilities[ability])}' for '{resource}'.";
- });
-
- statementForRecap.Add(string.Join(", ", abilities).Replace(".,", "."));
- }
-
- var recapStatement = string.Join(" ", statementForRecap);
- recapStatement = $"{statementBase}{recapStatement}";
-
- return !string.IsNullOrWhiteSpace(statement) ? $"{statement} {recapStatement}" : recapStatement;
- }
}
}
\ No newline at end of file
diff --git a/src/Reown.Sign/Runtime/Utils/ReCapUtils.cs.meta b/src/Reown.Sign/Runtime/Models/Cacao/ReCap.cs.meta
similarity index 100%
rename from src/Reown.Sign/Runtime/Utils/ReCapUtils.cs.meta
rename to src/Reown.Sign/Runtime/Models/Cacao/ReCap.cs.meta
diff --git a/test/Reown.Sign.Test/RecapTests.cs b/test/Reown.Sign.Test/RecapTests.cs
index 29968d2..5be7e53 100644
--- a/test/Reown.Sign.Test/RecapTests.cs
+++ b/test/Reown.Sign.Test/RecapTests.cs
@@ -43,7 +43,7 @@ public void IsReCap_WithValidResource_ReturnsTrue()
{
const string resource =
"urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9leGFtcGxlLmNvbS9waWN0dXJlcy8iOnsiY3J1ZC9kZWxldGUiOlt7fV0sImNydWQvdXBkYXRlIjpbe31dLCJvdGhlci9hY3Rpb24iOlt7fV19LCJtYWlsdG86dXNlcm5hbWVAZXhhbXBsZS5jb20iOnsibXNnL3JlY2VpdmUiOlt7Im1heF9jb3VudCI6NSwidGVtcGxhdGVzIjpbIm5ld3NsZXR0ZXIiLCJtYXJrZXRpbmciXX1dLCJtc2cvc2VuZCI6W3sidG8iOiJzb21lb25lQGVtYWlsLmNvbSJ9LHsidG8iOiJqb2VAZW1haWwuY29tIn1dfX0sInByZiI6WyJ6ZGo3V2o2Rk5TNHJVVWJzaUp2amp4Y3NOcVpkRENTaVlSOHNLUVhmb1BmcFNadUF3Il19\n";
- Assert.True(ReCapUtils.IsReCap(resource));
+ Assert.True(ReCap.IsReCap(resource));
}
[Theory]
@@ -53,7 +53,7 @@ public void IsReCap_WithValidResource_ReturnsTrue()
[InlineData("")]
public void IsReCap_WithInvalidResource_ReturnsFalse(string resource)
{
- Assert.False(ReCapUtils.IsReCap(resource));
+ Assert.False(ReCap.IsReCap(resource));
}
[Fact] [Trait("Category", "unit")]
@@ -61,7 +61,7 @@ public void CreateEncodedRecap_EmptyLimits_ReturnsExpectedResult()
{
var (resource, ability, actions, _) = GetRecapProperties();
- var encodedRecap = ReCapUtils.CreateEncodedRecap(resource, ability, actions, null);
+ var encodedRecap = ReCap.CreateEncodedRecap(resource, ability, actions, null);
const string expectedEncodedRecap = "urn:recap:eyJhdHQiOnsiZWlwMTU1Ijp7InJlcXVlc3QvZXRoX3NpZ25UeXBlZERhdGFfdjQiOlt7fV0sInJlcXVlc3QvcGVyc29uYWxfc2lnbiI6W3t9XX19fQ";
@@ -73,7 +73,7 @@ public void AssignAbilityToActions_WithValidArgs_ReturnsExpectedResult()
{
var (_, ability, actions, limits) = GetRecapProperties();
- Dictionary[]> result = ReCapUtils.AssignAbilityToActions(ability, actions, limits);
+ Dictionary[]> result = ReCap.AssignAbilityToActions(ability, actions, limits);
var expected = new Dictionary[]>
{
@@ -117,7 +117,7 @@ public void CreateRecap_WithValidArgs_ReturnsExpectedResult()
{
var (resource, ability, actions, limits) = GetRecapProperties();
- var result = ReCapUtils.CreateRecap(resource, ability, actions, limits);
+ var result = new ReCap(resource, ability, actions, limits);
Assert.NotNull(result);
@@ -137,14 +137,14 @@ public void CreateEncodedRecap_WithValidArgs_ReturnsExpectedResult()
var (resource, ability, actions, limits) = GetRecapProperties();
var chains = limits["chains"] as string[];
- var encodedRecap = ReCapUtils.CreateEncodedRecap(resource, ability, actions, limits);
+ var encodedRecap = ReCap.CreateEncodedRecap(resource, ability, actions, limits);
const string expectedEncodedRecap =
"urn:recap:eyJhdHQiOnsiZWlwMTU1Ijp7InJlcXVlc3QvZXRoX3NpZ25UeXBlZERhdGFfdjQiOlt7ImNoYWlucyI6WyJlaXAxNTU6MSIsImVpcDE1NToyIiwiZWlwMTU1OjMiXX1dLCJyZXF1ZXN0L3BlcnNvbmFsX3NpZ24iOlt7ImNoYWlucyI6WyJlaXAxNTU6MSIsImVpcDE1NToyIiwiZWlwMTU1OjMiXX1dfX19";
Assert.Equal(expectedEncodedRecap, encodedRecap);
- var decodedRecap = ReCapUtils.DecodeRecap(encodedRecap);
+ var decodedRecap = ReCap.Decode(encodedRecap);
Assert.NotNull(decodedRecap);
@@ -214,8 +214,8 @@ public void ValidateRecap_WithValidRecap_DoesNotThrow()
const string validRecap =
"urn:recap:eyJhdHQiOnsiZWlwMTU1Ijp7InJlcXVlc3QvZXRoX3NpZ25UeXBlZERhdGFfdjQiOlt7ImNoYWlucyI6WyJlaXAxNTU6MSIsImVpcDE1NToyIiwiZWlwMTU1OjMiXX1dLCJyZXF1ZXN0L3BlcnNvbmFsX3NpZ24iOlt7ImNoYWlucyI6WyJlaXAxNTU6MSIsImVpcDE1NToyIiwiZWlwMTU1OjMiXX1dfX19";
- var recap = ReCapUtils.DecodeRecap(validRecap);
- ReCapUtils.ValidateRecap(recap); // Should not throw
+ var recap = ReCap.Decode(validRecap);
+ ReCap.Validate(recap); // Should not throw
}
[Fact] [Trait("Category", "unit")]
@@ -223,7 +223,7 @@ public void ValidateRecap_NullRecap_ThrowsArgumentException()
{
ReCap recap = null;
- var exception = Assert.Throws(() => ReCapUtils.ValidateRecap(recap));
+ var exception = Assert.Throws(() => ReCap.Validate(recap));
Assert.Equal("No `att` property found", exception.Message);
}
@@ -231,12 +231,9 @@ public void ValidateRecap_NullRecap_ThrowsArgumentException()
[Fact] [Trait("Category", "unit")]
public void ValidateRecap_NullAtt_ThrowsArgumentException()
{
- var recap = new ReCap
- {
- Att = null
- };
+ var recap = new ReCap(null);
- var exception = Assert.Throws(() => ReCapUtils.ValidateRecap(recap));
+ var exception = Assert.Throws(() => ReCap.Validate(recap));
Assert.Equal("No `att` property found", exception.Message);
}
@@ -244,9 +241,7 @@ public void ValidateRecap_NullAtt_ThrowsArgumentException()
[Fact] [Trait("Category", "unit")]
public void ValidateRecap_NullProperties_ThrowsArgumentException()
{
- var recap = new ReCap
- {
- Att = new Dictionary
+ var recap = new ReCap(new Dictionary
{
{
"resource1", new AttValue
@@ -255,9 +250,9 @@ public void ValidateRecap_NullProperties_ThrowsArgumentException()
}
}
}
- };
+ );
- var exception = Assert.Throws(() => ReCapUtils.ValidateRecap(recap));
+ var exception = Assert.Throws(() => ReCap.Validate(recap));
Assert.Contains("Resource object is empty or null", exception.Message);
}
@@ -265,9 +260,7 @@ public void ValidateRecap_NullProperties_ThrowsArgumentException()
[Fact] [Trait("Category", "unit")]
public void ValidateRecap_EmptyProperties_ThrowsArgumentException()
{
- var recap = new ReCap
- {
- Att = new Dictionary
+ var recap = new ReCap(new Dictionary
{
{
"resource1", new AttValue
@@ -276,9 +269,9 @@ public void ValidateRecap_EmptyProperties_ThrowsArgumentException()
}
}
}
- };
+ );
- var exception = Assert.Throws(() => ReCapUtils.ValidateRecap(recap));
+ var exception = Assert.Throws(() => ReCap.Validate(recap));
Assert.Contains("Resource object is empty or null", exception.Message);
}
@@ -286,9 +279,7 @@ public void ValidateRecap_EmptyProperties_ThrowsArgumentException()
[Fact] [Trait("Category", "unit")]
public void ValidateRecap_AbilityNotJArray_ThrowsArgumentException()
{
- var recap = new ReCap
- {
- Att = new Dictionary
+ var recap = new ReCap(new Dictionary
{
{
"resource1", new AttValue
@@ -300,9 +291,9 @@ public void ValidateRecap_AbilityNotJArray_ThrowsArgumentException()
}
}
}
- };
+ );
- var exception = Assert.Throws(() => ReCapUtils.ValidateRecap(recap));
+ var exception = Assert.Throws(() => ReCap.Validate(recap));
Assert.Contains("Ability 'ability1' must be an array.", exception.Message);
}
@@ -310,9 +301,7 @@ public void ValidateRecap_AbilityNotJArray_ThrowsArgumentException()
[Fact] [Trait("Category", "unit")]
public void ValidateRecap_EmptyLimitsArray_ThrowsArgumentException()
{
- var recap = new ReCap
- {
- Att = new Dictionary
+ var recap = new ReCap(new Dictionary
{
{
"resource1", new AttValue
@@ -324,9 +313,9 @@ public void ValidateRecap_EmptyLimitsArray_ThrowsArgumentException()
}
}
}
- };
+ );
- var exception = Assert.Throws(() => ReCapUtils.ValidateRecap(recap));
+ var exception = Assert.Throws(() => ReCap.Validate(recap));
Assert.Contains("Value of ability 'ability1' is an empty array; it must contain at least one limit object.", exception.Message);
}
@@ -337,7 +326,7 @@ public void GetMethodsFromRecap_WithValidRecap_ReturnsExpectedMethods()
const string recapStr =
"urn:recap:eyJhdHQiOnsiZWlwMTU1Ijp7InJlcXVlc3QvZXRoX3NpZ25UeXBlZERhdGFfdjQiOlt7ImNoYWlucyI6WyJlaXAxNTU6MSIsImVpcDE1NToyIiwiZWlwMTU1OjMiXX1dLCJyZXF1ZXN0L3BlcnNvbmFsX3NpZ24iOlt7ImNoYWlucyI6WyJlaXAxNTU6MSIsImVpcDE1NToyIiwiZWlwMTU1OjMiXX1dfX19";
- var methods = ReCapUtils.GetActionsFromRecap(recapStr);
+ var methods = ReCap.GetActionsFromEncodedRecap(recapStr);
var expectedMethods = new[]
{
@@ -354,7 +343,7 @@ public void GetChainsFromRecap_WithValidRecap_ReturnsExpectedChains()
const string recapStr =
"urn:recap:eyJhdHQiOnsiZWlwMTU1Ijp7InJlcXVlc3QvZXRoX3NpZ25UeXBlZERhdGFfdjQiOlt7ImNoYWlucyI6WyJlaXAxNTU6MSIsImVpcDE1NToyIiwiZWlwMTU1OjMiXX1dLCJyZXF1ZXN0L3BlcnNvbmFsX3NpZ24iOlt7ImNoYWlucyI6WyJlaXAxNTU6MSIsImVpcDE1NToyIiwiZWlwMTU1OjMiXX1dfX19";
- var chains = ReCapUtils.GetChainsFromRecap(recapStr);
+ var chains = ReCap.GetChainsFromEncodedRecap(recapStr);
var expectedChains = new[]
{
@@ -372,9 +361,9 @@ public void FormatStatementFromRecap_WithValidRecap_ReturnsExpectedStatement()
const string recapStr =
"urn:recap:eyJhdHQiOnsiZWlwMTU1Ijp7InJlcXVlc3QvZXRoX3NpZ25UeXBlZERhdGFfdjQiOlt7ImNoYWlucyI6WyJlaXAxNTU6MSIsImVpcDE1NToyIiwiZWlwMTU1OjMiXX1dLCJyZXF1ZXN0L3BlcnNvbmFsX3NpZ24iOlt7ImNoYWlucyI6WyJlaXAxNTU6MSIsImVpcDE1NToyIiwiZWlwMTU1OjMiXX1dfX19";
- var recap = ReCapUtils.DecodeRecap(recapStr);
+ var recap = ReCap.Decode(recapStr);
- var formattedStatement = ReCapUtils.FormatStatementFromRecap(recap);
+ var formattedStatement = recap.FormatStatement();
const string expectedStatement = "I further authorize the stated URI to perform the following actions on my behalf: (1) 'request': 'eth_signTypedData_v4', 'personal_sign' for 'eip155'.";
@@ -405,7 +394,7 @@ public void MergeRecaps_WithOverlappingRecaps_ReturnsMergedRecap()
{ "chains", chains1 }
};
- var recap1 = ReCapUtils.CreateRecap(resource, ability, actions1, limits1);
+ var recap1 = new ReCap(resource, ability, actions1, limits1);
var actions2 = new[]
{
@@ -426,9 +415,9 @@ public void MergeRecaps_WithOverlappingRecaps_ReturnsMergedRecap()
{ "chains", chains2 }
};
- var recap2 = ReCapUtils.CreateRecap(resource, ability, actions2, limits2);
+ var recap2 = new ReCap(resource, ability, actions2, limits2);
- var mergedRecap = ReCapUtils.MergeRecaps(recap1, recap2);
+ var mergedRecap = ReCap.MergeRecaps(recap1, recap2);
var recapJson = JsonConvert.SerializeObject(mergedRecap);
@@ -480,7 +469,7 @@ public void MergeRecaps_WithEmptyLimits_ReturnsMergedRecap()
{ "chains", chains1 }
};
- var recap1 = ReCapUtils.CreateRecap(resource, ability, actions1, limits1);
+ var recap1 = new ReCap(resource, ability, actions1, limits1);
var actions2 = new[]
{
@@ -491,9 +480,9 @@ public void MergeRecaps_WithEmptyLimits_ReturnsMergedRecap()
Dictionary limits2 = null;
- var recap2 = ReCapUtils.CreateRecap(resource, ability, actions2, limits2);
+ var recap2 = new ReCap(resource, ability, actions2, limits2);
- var mergedRecap = ReCapUtils.MergeRecaps(recap1, recap2);
+ var mergedRecap = ReCap.MergeRecaps(recap1, recap2);
var recapJson = JsonConvert.SerializeObject(mergedRecap);
_testOutputHelper.WriteLine(recapJson);
@@ -522,11 +511,11 @@ public void MergeEncodedRecaps_WithValidRecaps_ReturnsMergedRecap()
const string encodedRecap1 = "urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9leGFtcGxlMS5jb20iOnsiY3J1ZC9yZWFkIjpbe31dfX19";
const string encodedRecap2 = "urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9leGFtcGxlMS5jb20iOnsiY3J1ZC91cGRhdGUiOlt7Im1heF90aW1lcyI6MX1dfSwiaHR0cHM6Ly9leGFtcGxlMi5jb20iOnsiY3J1ZC9kZWxldGUiOlt7fV19fX0==";
- var mergedRecap = ReCapUtils.MergeEncodedRecaps(encodedRecap1, encodedRecap2);
+ var mergedRecap = ReCap.MergeEncodedRecaps(encodedRecap1, encodedRecap2);
const string expectedMergedRecap = "urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9leGFtcGxlMS5jb20iOnsiY3J1ZC9yZWFkIjpbe31dLCJjcnVkL3VwZGF0ZSI6W3sibWF4X3RpbWVzIjoxfV19LCJodHRwczovL2V4YW1wbGUyLmNvbSI6eyJjcnVkL2RlbGV0ZSI6W3t9XX19fQ";
Assert.Equal(expectedMergedRecap, mergedRecap);
- _ = ReCapUtils.DecodeRecap(mergedRecap);
+ _ = ReCap.Decode(mergedRecap);
}
}
\ No newline at end of file