Skip to content

Commit

Permalink
feat: created address api to set defaults and fetch connected address
Browse files Browse the repository at this point in the history
  • Loading branch information
gigajuwels committed Nov 1, 2023
1 parent 883f0b7 commit 9dbd3fb
Show file tree
Hide file tree
Showing 11 changed files with 553 additions and 4 deletions.
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);
}
}
}
185 changes: 185 additions & 0 deletions WalletConnectSharp.Sign/Controllers/AddressProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
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;

// double check
if (@namespace == null)
throw new ArgumentException("CurrentAddress: @namespace is null");
if (string.IsNullOrWhiteSpace(session.Topic))
throw new ArgumentException("CurrentAddress: Session is undefined");

var defaultNamespace = session.Namespaces[@namespace];

if (defaultNamespace.Accounts.Length == 0)
return null; //The namespace {@namespace} has no addresses connected")

var fullAddress = defaultNamespace.Accounts[0];
var addressParts = fullAddress.Split(":");

var address = addressParts[2];
var chainId = string.Join(':', addressParts.Take(2));

return new Caip25Address()
{
Address = address,
ChainId = chainId,
};
}

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

// double check
if (@namespace == null)
throw new ArgumentException("CurrentAddress: @namespace is null");
if (string.IsNullOrWhiteSpace(session.Topic))
throw new ArgumentException("CurrentAddress: Session is undefined");

var defaultNamespace = session.Namespaces[@namespace];

if (defaultNamespace.Accounts.Length == 0)
return null; //The namespace {@namespace} has no addresses connected")

return defaultNamespace.Accounts.Select(addr => new Caip25Address()
{
Address = addr.Split(":")[2], ChainId = string.Join(":", addr.Split(":").Take(2))
}).ToArray();
}

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

_client = null;
Sessions = null;
DefaultNamespace = null;
DefaultSession = default;
}
}
Loading

0 comments on commit 9dbd3fb

Please sign in to comment.