Skip to content

Commit 718cb6a

Browse files
authored
Allow overriding RPC (#118)
1 parent 1ff3a70 commit 718cb6a

File tree

4 files changed

+117
-25
lines changed

4 files changed

+117
-25
lines changed

Thirdweb.Tests/Thirdweb.RPC/Thirdweb.RPC.Tests.cs

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,90 @@ public class RpcTests : BaseTests
77
public RpcTests(ITestOutputHelper output)
88
: base(output) { }
99

10+
[Fact]
11+
public void RpcOverride_None()
12+
{
13+
var client = ThirdwebClient.Create(secretKey: this.SecretKey);
14+
var thirdwebRpc = $"https://1.rpc.thirdweb.com/{client.ClientId}";
15+
var rpc = ThirdwebRPC.GetRpcInstance(client, 1);
16+
Assert.Equal(thirdwebRpc, rpc.RpcUrl.AbsoluteUri);
17+
}
18+
19+
[Fact]
20+
public void RpcOverride_Single()
21+
{
22+
var customRpc = "https://eth.llamarpc.com/";
23+
var client = ThirdwebClient.Create(secretKey: this.SecretKey, rpcOverrides: new Dictionary<BigInteger, string> { { 1, customRpc } });
24+
var rpc = ThirdwebRPC.GetRpcInstance(client, 1);
25+
Assert.Equal(customRpc, client.RpcOverrides[1]);
26+
Assert.Equal(customRpc, rpc.RpcUrl.AbsoluteUri);
27+
}
28+
29+
[Fact]
30+
public void RpcOverride_Multiple()
31+
{
32+
var customRpc1 = "https://eth.llamarpc.com/";
33+
var customRpc42161 = "https://arbitrum.llamarpc.com/";
34+
var client = ThirdwebClient.Create(secretKey: this.SecretKey, rpcOverrides: new Dictionary<BigInteger, string> { { 1, customRpc1 }, { 42161, customRpc42161 } });
35+
var rpc1 = ThirdwebRPC.GetRpcInstance(client, 1);
36+
var rpc42161 = ThirdwebRPC.GetRpcInstance(client, 42161);
37+
Assert.Equal(customRpc1, client.RpcOverrides[1]);
38+
Assert.Equal(customRpc1, rpc1.RpcUrl.AbsoluteUri);
39+
Assert.Equal(customRpc42161, client.RpcOverrides[42161]);
40+
Assert.Equal(customRpc42161, rpc42161.RpcUrl.AbsoluteUri);
41+
}
42+
43+
[Fact]
44+
public void RpcOverride_Single_Default()
45+
{
46+
var customRpc = "https://eth.llamarpc.com/";
47+
var client = ThirdwebClient.Create(secretKey: this.SecretKey, rpcOverrides: new Dictionary<BigInteger, string> { { 1, customRpc } });
48+
49+
var thirdwebRpc = $"https://42161.rpc.thirdweb.com/{client.ClientId}";
50+
51+
var rpc1 = ThirdwebRPC.GetRpcInstance(client, 1);
52+
Assert.Equal(customRpc, rpc1.RpcUrl.AbsoluteUri);
53+
54+
var rpc42161 = ThirdwebRPC.GetRpcInstance(client, 42161);
55+
Assert.Equal(thirdwebRpc, rpc42161.RpcUrl.AbsoluteUri);
56+
}
57+
58+
[Fact]
59+
public void RpcOverride_Multiple_Default()
60+
{
61+
var customRpc1 = "https://eth.llamarpc.com/";
62+
var customRpc42161 = "https://arbitrum.llamarpc.com/";
63+
var client = ThirdwebClient.Create(secretKey: this.SecretKey, rpcOverrides: new Dictionary<BigInteger, string> { { 1, customRpc1 }, { 42161, customRpc42161 } });
64+
65+
var thirdwebRpc = $"https://421614.rpc.thirdweb.com/{client.ClientId}";
66+
67+
var rpc1 = ThirdwebRPC.GetRpcInstance(client, 1);
68+
Assert.Equal(customRpc1, rpc1.RpcUrl.AbsoluteUri);
69+
70+
var rpc42161 = ThirdwebRPC.GetRpcInstance(client, 42161);
71+
Assert.Equal(customRpc42161, rpc42161.RpcUrl.AbsoluteUri);
72+
73+
var rpc421614 = ThirdwebRPC.GetRpcInstance(client, 421614);
74+
Assert.Equal(thirdwebRpc, rpc421614.RpcUrl.AbsoluteUri);
75+
}
76+
77+
[Fact(Timeout = 120000)]
78+
public async Task Request_WithRpcOverride()
79+
{
80+
var customRpc = "https://eth.llamarpc.com/";
81+
var client = ThirdwebClient.Create(secretKey: this.SecretKey, rpcOverrides: new Dictionary<BigInteger, string> { { 1, customRpc } });
82+
83+
var rpc = ThirdwebRPC.GetRpcInstance(client, 1);
84+
var blockNumber = await rpc.SendRequestAsync<string>("eth_blockNumber");
85+
Assert.NotNull(blockNumber);
86+
Assert.StartsWith("0x", blockNumber);
87+
88+
var rpc2 = ThirdwebRPC.GetRpcInstance(client, 42161);
89+
var blockNumber2 = await rpc2.SendRequestAsync<string>("eth_blockNumber");
90+
Assert.NotNull(blockNumber2);
91+
Assert.StartsWith("0x", blockNumber2);
92+
}
93+
1094
[Fact(Timeout = 120000)]
1195
public async Task GetBlockNumber()
1296
{
@@ -69,18 +153,4 @@ public async Task TestRpcError()
69153
var exception = await Assert.ThrowsAsync<Exception>(async () => await rpc.SendRequestAsync<string>("eth_invalidMethod"));
70154
Assert.Contains("RPC Error for request", exception.Message);
71155
}
72-
73-
// [Fact(Timeout = 120000)]
74-
// public async Task TestCache()
75-
// {
76-
// var client = ThirdwebClient.Create(secretKey: this.SecretKey);
77-
// var rpc = ThirdwebRPC.GetRpcInstance(client, 421614);
78-
// var blockNumber1 = await rpc.SendRequestAsync<string>("eth_blockNumber");
79-
// await ThirdwebTask.Delay(1);
80-
// var blockNumber2 = await rpc.SendRequestAsync<string>("eth_blockNumber");
81-
// Assert.Equal(blockNumber1, blockNumber2);
82-
// await ThirdwebTask.Delay(1000);
83-
// var blockNumber3 = await rpc.SendRequestAsync<string>("eth_blockNumber");
84-
// Assert.NotEqual(blockNumber1, blockNumber3);
85-
// }
86156
}

Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ public async Task GetAllActiveSigners()
239239
reqValidityEndTimestamp: Utils.GetUnixTimeStampIn10Years().ToString()
240240
);
241241

242+
await ThirdwebTask.Delay(1000);
243+
242244
signers = await account.GetAllActiveSigners();
243245

244246
Assert.Equal(count + 1, signers.Count);

Thirdweb/Thirdweb.Client/ThirdwebClient.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Thirdweb.Tests")]
1+
using System.Numerics;
2+
3+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Thirdweb.Tests")]
24

35
namespace Thirdweb;
46

@@ -20,6 +22,7 @@ public class ThirdwebClient
2022
internal string SecretKey { get; }
2123
internal string BundleId { get; }
2224
internal ITimeoutOptions FetchTimeoutOptions { get; }
25+
internal Dictionary<BigInteger, string> RpcOverrides { get; }
2326

2427
private ThirdwebClient(
2528
string clientId = null,
@@ -30,7 +33,8 @@ private ThirdwebClient(
3033
string sdkName = null,
3134
string sdkOs = null,
3235
string sdkPlatform = null,
33-
string sdkVersion = null
36+
string sdkVersion = null,
37+
Dictionary<BigInteger, string> rpcOverrides = null
3438
)
3539
{
3640
if (string.IsNullOrEmpty(clientId) && string.IsNullOrEmpty(secretKey))
@@ -71,6 +75,8 @@ private ThirdwebClient(
7175

7276
this.HttpClient = httpClient ?? new ThirdwebHttpClient();
7377
this.HttpClient.SetHeaders(defaultHeaders);
78+
79+
this.RpcOverrides = rpcOverrides;
7480
}
7581

7682
/// <summary>
@@ -85,6 +91,7 @@ private ThirdwebClient(
8591
/// <param name="sdkOs">The SDK OS (optional).</param>
8692
/// <param name="sdkPlatform">The SDK platform (optional).</param>
8793
/// <param name="sdkVersion">The SDK version (optional).</param>
94+
/// <param name="rpcOverrides">Mapping of chain id to your custom rpc for that chain id (optional, defaults to thirdweb RPC).</param>
8895
/// <returns>A new instance of <see cref="ThirdwebClient"/>.</returns>
8996
public static ThirdwebClient Create(
9097
string clientId = null,
@@ -95,9 +102,10 @@ public static ThirdwebClient Create(
95102
string sdkName = null,
96103
string sdkOs = null,
97104
string sdkPlatform = null,
98-
string sdkVersion = null
105+
string sdkVersion = null,
106+
Dictionary<BigInteger, string> rpcOverrides = null
99107
)
100108
{
101-
return new ThirdwebClient(clientId, secretKey, bundleId, fetchTimeoutOptions, httpClient, sdkName, sdkOs, sdkPlatform, sdkVersion);
109+
return new ThirdwebClient(clientId, secretKey, bundleId, fetchTimeoutOptions, httpClient, sdkName, sdkOs, sdkPlatform, sdkVersion, rpcOverrides);
102110
}
103111
}

Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
using System.Text;
44
using Newtonsoft.Json;
55

6+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Thirdweb.Tests")]
7+
68
namespace Thirdweb;
79

810
/// <summary>
911
/// Represents the Thirdweb RPC client for sending requests and handling responses.
1012
/// </summary>
1113
public class ThirdwebRPC : IDisposable
1214
{
15+
internal Uri RpcUrl { get; }
16+
1317
private const int BatchSizeLimit = 100;
1418
private readonly TimeSpan _batchInterval = TimeSpan.FromMilliseconds(50);
1519

16-
private readonly Uri _rpcUrl;
1720
private readonly TimeSpan _rpcTimeout;
1821
private readonly Dictionary<string, (object Response, DateTime Timestamp)> _cache = new();
1922
private readonly TimeSpan _cacheDuration = TimeSpan.FromMilliseconds(25);
@@ -49,7 +52,15 @@ public static ThirdwebRPC GetRpcInstance(ThirdwebClient client, BigInteger chain
4952
throw new ArgumentException("Invalid Chain ID");
5053
}
5154

52-
var key = $"{client.ClientId}_{chainId}_{client.FetchTimeoutOptions.GetTimeout(TimeoutType.Rpc)}";
55+
string key;
56+
if (client.RpcOverrides != null && client.RpcOverrides.TryGetValue(chainId, out var rpcOverride))
57+
{
58+
key = $"{client.ClientId}_{chainId}_{rpcOverride}_{client.FetchTimeoutOptions.GetTimeout(TimeoutType.Rpc)}";
59+
}
60+
else
61+
{
62+
key = $"{client.ClientId}_{chainId}_{client.FetchTimeoutOptions.GetTimeout(TimeoutType.Rpc)}";
63+
}
5364

5465
if (!_rpcs.ContainsKey(key))
5566
{
@@ -77,7 +88,7 @@ public async Task<TResponse> SendRequestAsync<TResponse>(string method, params o
7788
{
7889
lock (this._cacheLock)
7990
{
80-
var cacheKey = GetCacheKey(this._rpcUrl.ToString(), method, parameters);
91+
var cacheKey = GetCacheKey(this.RpcUrl.ToString(), method, parameters);
8192
if (this._cache.TryGetValue(cacheKey, out var cachedItem) && (DateTime.Now - cachedItem.Timestamp) < this._cacheDuration)
8293
{
8394
if (cachedItem.Response is TResponse cachedResponse)
@@ -121,7 +132,7 @@ public async Task<TResponse> SendRequestAsync<TResponse>(string method, params o
121132
{
122133
lock (this._cacheLock)
123134
{
124-
var cacheKey = GetCacheKey(this._rpcUrl.ToString(), method, parameters);
135+
var cacheKey = GetCacheKey(this.RpcUrl.ToString(), method, parameters);
125136
this._cache[cacheKey] = (response, DateTime.Now);
126137
}
127138
return response;
@@ -133,7 +144,7 @@ public async Task<TResponse> SendRequestAsync<TResponse>(string method, params o
133144
var deserializedResponse = JsonConvert.DeserializeObject<TResponse>(JsonConvert.SerializeObject(result));
134145
lock (this._cacheLock)
135146
{
136-
var cacheKey = GetCacheKey(this._rpcUrl.ToString(), method, parameters);
147+
var cacheKey = GetCacheKey(this.RpcUrl.ToString(), method, parameters);
137148
this._cache[cacheKey] = (deserializedResponse, DateTime.Now);
138149
}
139150
return deserializedResponse;
@@ -148,7 +159,8 @@ public async Task<TResponse> SendRequestAsync<TResponse>(string method, params o
148159
private ThirdwebRPC(ThirdwebClient client, BigInteger chainId)
149160
{
150161
this._httpClient = client.HttpClient;
151-
this._rpcUrl = new Uri($"https://{chainId}.rpc.thirdweb.com/{client.ClientId}");
162+
var rpcOverride = client.RpcOverrides?.FirstOrDefault(r => r.Key == chainId);
163+
this.RpcUrl = new Uri(rpcOverride?.Value ?? $"https://{chainId}.rpc.thirdweb.com/{client.ClientId}");
152164
this._rpcTimeout = TimeSpan.FromMilliseconds(client.FetchTimeoutOptions.GetTimeout(TimeoutType.Rpc));
153165
_ = this.StartBackgroundFlushAsync();
154166
}
@@ -161,7 +173,7 @@ private async Task SendBatchAsync(List<RpcRequest> batch)
161173
try
162174
{
163175
using var cts = new CancellationTokenSource(this._rpcTimeout);
164-
var response = await this._httpClient.PostAsync(this._rpcUrl.ToString(), content, cts.Token).ConfigureAwait(false);
176+
var response = await this._httpClient.PostAsync(this.RpcUrl.ToString(), content, cts.Token).ConfigureAwait(false);
165177

166178
if (!response.IsSuccessStatusCode)
167179
{

0 commit comments

Comments
 (0)