Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connected Address API #148

Merged
merged 3 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions Tests/WalletConnectSharp.Sign.Test/SignTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -382,5 +382,180 @@ public async void TestTwoUniqueSessionRequestResponse()

Assert.True(responseReturned2);
}

[Fact, Trait("Category", "integration")]
public async void TestTwoUniqueSessionRequestResponseUsingAddressProviderDefaults()
{
await _cryptoFixture.WaitForClientsReady();

var testAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
var testMethod = "test_method";
var testMethod2 = "test_method_2";

var dappConnectOptions = new ConnectOptions()
{
RequiredNamespaces = new RequiredNamespaces()
{
{
"eip155", new ProposedNamespace()
{
Methods = new[]
{
testMethod,
testMethod2
},
Chains = new[]
{
"eip155:1"
},
Events = new[]
{
"chainChanged", "accountsChanged"
}
}
}
}
};

var dappClient = ClientA;
var connectData = await dappClient.Connect(dappConnectOptions);

var walletClient = ClientB;
var proposal = await walletClient.Pair(connectData.Uri);

var approveData = await walletClient.Approve(proposal, testAddress);

var sessionData = await connectData.Approval;
await approveData.Acknowledged();

var rnd = new Random();
var a = rnd.Next(100);
var b = rnd.Next(100);
var x = rnd.NextStrings(AllowedChars, (Math.Min(a, b), Math.Max(a, b)), 1).First();
var y = x.Length;

var testData = new TestRequest() { a = a, b = b, };
var testData2 = new TestRequest2() { x = x, y = y };

var pending = new TaskCompletionSource<int>();
var pending2 = new TaskCompletionSource<bool>();

// Step 1. Setup event listener for request

// The wallet client will listen for the request with the "test_method" rpc method
walletClient.Engine.SessionRequestEvents<TestRequest, TestResponse>()
.OnRequest += ( requestData) =>
{
var request = requestData.Request;
var data = request.Params;

requestData.Response = new TestResponse()
{
result = data.a * data.b
};

return Task.CompletedTask;
};

// The wallet client will listen for the request with the "test_method" rpc method
walletClient.Engine.SessionRequestEvents<TestRequest2, bool>()
.OnRequest += ( requestData) =>
{
var request = requestData.Request;
var data = request.Params;

requestData.Response = data.x.Length == data.y;

return Task.CompletedTask;
};

// The dapp client will listen for the response
// Normally, we wouldn't do this and just rely on the return value
// from the dappClient.Engine.Request function call (the response Result or throws an Exception)
// We do it here for the sake of testing
dappClient.Engine.SessionRequestEvents<TestRequest, TestResponse>()
.FilterResponses((r) => r.Topic == sessionData.Topic)
.OnResponse += (responseData) =>
{
var response = responseData.Response;

var data = response.Result;

pending.TrySetResult(data.result);

return Task.CompletedTask;
};

// 2. Send the request from the dapp client
var responseReturned = await dappClient.Engine.Request<TestRequest, TestResponse>(testData);
var responseReturned2 = await dappClient.Engine.Request<TestRequest2, bool>(testData2);

// 3. Wait for the response from the event listener
var eventResult = await pending.Task.WithTimeout(TimeSpan.FromSeconds(5));

Assert.Equal(eventResult, a * b);
Assert.Equal(eventResult, testData.a * testData.b);
Assert.Equal(eventResult, responseReturned.result);

Assert.True(responseReturned2);
}

[Fact, Trait("Category", "integration")]
public async void TestAddressProviderDefaults()
{
await _cryptoFixture.WaitForClientsReady();

var testAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
var testMethod = "test_method";
var testMethod2 = "test_method_2";

var dappConnectOptions = new ConnectOptions()
{
RequiredNamespaces = new RequiredNamespaces()
{
{
"eip155", new ProposedNamespace()
{
Methods = new[]
{
testMethod,
testMethod2
},
Chains = new[]
{
"eip155:1"
},
Events = new[]
{
"chainChanged", "accountsChanged"
}
}
}
}
};

var dappClient = ClientA;
var connectData = await dappClient.Connect(dappConnectOptions);

var walletClient = ClientB;
var proposal = await walletClient.Pair(connectData.Uri);

var approveData = await walletClient.Approve(proposal, testAddress);

await connectData.Approval;
await approveData.Acknowledged();

var address = dappClient.AddressProvider.CurrentAddress();
Assert.Equal(testAddress, address.Address);
Assert.Equal("eip155:1", address.ChainId);
Assert.Equal("eip155:1", dappClient.AddressProvider.DefaultChain);
Assert.Equal("eip155", dappClient.AddressProvider.DefaultNamespace);

address = walletClient.AddressProvider.CurrentAddress();
Assert.Equal(testAddress, address.Address);
Assert.Equal("eip155:1", address.ChainId);
Assert.Equal("eip155:1", dappClient.AddressProvider.DefaultChain);
Assert.Equal("eip155", dappClient.AddressProvider.DefaultNamespace);
}
}
}
150 changes: 150 additions & 0 deletions WalletConnectSharp.Sign/Controllers/AddressProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using WalletConnectSharp.Sign.Interfaces;
using WalletConnectSharp.Sign.Models;
using WalletConnectSharp.Sign.Models.Engine.Events;

namespace WalletConnectSharp.Sign.Controllers;

public class AddressProvider : IAddressProvider
{
public bool HasDefaultSession
{
get
{
return !string.IsNullOrWhiteSpace(DefaultSession.Topic) && DefaultSession.RequiredNamespaces != null;
}
}

public string Name
{
get
{
return $"{_client.Name}-address-provider";
}
}

public string Context
{
get
{
return Name;
}
}

public SessionStruct DefaultSession { get; set; }
public string DefaultNamespace { get; set; }
public string DefaultChain { get; set; }
public ISession Sessions { get; private set; }

private ISignClient _client;

public AddressProvider(ISignClient client)
{
this._client = client;
this.Sessions = client.Session;

// set the first connected session to the default one
client.SessionConnected += ClientOnSessionConnected;
client.SessionDeleted += ClientOnSessionDeleted;
client.SessionUpdated += ClientOnSessionUpdated;
client.SessionApproved += ClientOnSessionConnected;
}

private void ClientOnSessionUpdated(object sender, SessionEvent e)
{
if (DefaultSession.Topic == e.Topic)
{
UpdateDefaultChainAndNamespace();
}
}

private void ClientOnSessionDeleted(object sender, SessionEvent e)
{
if (DefaultSession.Topic == e.Topic)
{
DefaultSession = default;
UpdateDefaultChainAndNamespace();
}
}

private void ClientOnSessionConnected(object sender, SessionStruct e)
{
if (!HasDefaultSession)
{
DefaultSession = e;
UpdateDefaultChainAndNamespace();
}
}

private void UpdateDefaultChainAndNamespace()
{
if (HasDefaultSession)
{
var currentDefault = DefaultNamespace;
if (currentDefault != null && DefaultSession.RequiredNamespaces.ContainsKey(currentDefault))
{
// DefaultNamespace is still valid
var currentChain = DefaultChain;
if (currentChain == null ||
DefaultSession.RequiredNamespaces[DefaultNamespace].Chains.Contains(currentChain))
{
// DefaultChain is still valid
return;
}

DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0];
return;
}

// DefaultNamespace is null or not found in RequiredNamespaces, update it
DefaultNamespace = DefaultSession.RequiredNamespaces.OrderedKeys.FirstOrDefault();
if (DefaultNamespace != null)
{
DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0];
}
else
{
// TODO The Keys property is unordered! Maybe this needs to be updated
DefaultNamespace = DefaultSession.RequiredNamespaces.Keys.FirstOrDefault();
if (DefaultNamespace != null)
{
DefaultChain = DefaultSession.RequiredNamespaces[DefaultNamespace].Chains[0];
}
}
}
else
{
DefaultNamespace = null;
}
}

public Caip25Address CurrentAddress(string @namespace = null, SessionStruct session = default)
{
@namespace ??= DefaultNamespace;
if (string.IsNullOrWhiteSpace(session.Topic)) // default
session = DefaultSession;

return session.CurrentAddress(@namespace);
}

public Caip25Address[] AllAddresses(string @namespace = null, SessionStruct session = default)
{
@namespace ??= DefaultNamespace;
if (string.IsNullOrWhiteSpace(session.Topic)) // default
session = DefaultSession;

return session.AllAddresses(@namespace);
}

public void Dispose()
{
_client.SessionConnected -= ClientOnSessionConnected;
_client.SessionDeleted -= ClientOnSessionDeleted;
_client.SessionUpdated -= ClientOnSessionUpdated;
_client.SessionApproved -= ClientOnSessionConnected;

_client = null;
Sessions = null;
DefaultNamespace = null;
DefaultSession = default;
}
}
39 changes: 38 additions & 1 deletion WalletConnectSharp.Sign/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,43 @@ public void HandleEventMessageType<T>(Func<string, JsonRpcRequest<SessionEvent<T
Client.Core.MessageHandler.HandleMessageType(requestCallback, responseCallback);
}

public Task<IAcknowledgement> UpdateSession(Namespaces namespaces)
{
return UpdateSession(Client.AddressProvider.DefaultSession.Topic, namespaces);
}

public Task<IAcknowledgement> Extend()
{
return Extend(Client.AddressProvider.DefaultSession.Topic);
}

public Task<TR> Request<T, TR>(T data, string chainId = null, long? expiry = null)
{
return Request<T, TR>(Client.AddressProvider.DefaultSession.Topic, data,
chainId ?? Client.AddressProvider.DefaultChain, expiry);
}

public Task Respond<T, TR>(JsonRpcResponse<TR> response)
{
return Respond<T, TR>(Client.AddressProvider.DefaultSession.Topic, response);
}

public Task Emit<T>(EventData<T> eventData, string chainId = null)
{
return Emit<T>(Client.AddressProvider.DefaultSession.Topic, eventData,
chainId ?? Client.AddressProvider.DefaultChain);
}

public Task Ping()
{
return Ping(Client.AddressProvider.DefaultSession.Topic);
}

public Task Disconnect(Error reason = null)
{
return Disconnect(Client.AddressProvider.DefaultSession.Topic, reason);
}

/// <summary>
/// Parse a session proposal URI and return all information in the URI in a
/// new <see cref="UriParameters"/> object
Expand Down Expand Up @@ -581,7 +618,7 @@ public async Task<TR> Request<T, TR>(string topic, T data, string chainId = null
if (string.IsNullOrWhiteSpace(chainId))
{
var sessionData = Client.Session.Get(topic);
var firstRequiredNamespace = sessionData.RequiredNamespaces.Keys.ToArray()[0];
var firstRequiredNamespace = sessionData.RequiredNamespaces.OrderedKeys[0];
defaultChainId = sessionData.RequiredNamespaces[firstRequiredNamespace].Chains[0];
}
else
Expand Down
Loading
Loading