-
Notifications
You must be signed in to change notification settings - Fork 10
Open
Description
Chain Provider Configuration Architecture (v2)
Problem
The current single chainProviders config doesn't support specialized RPCs (e.g., ethrpc.xyz only works for ENS, blocks other contracts). Need a design that:
- Removes global
chainProvidersentirely - Makes domain resolvers the primary source of RPC URLs
- Allows challenges to fall back to resolver URLs or use their own
- Is fully JSON-serializable for user account storage
Design Principles
- No global
chainProviders- remove from PlebbitOptions - Resolvers own their URLs - each resolver (ENS, SNS) brings its own RPC config
- Challenges have fallback chain - challenge options > resolver URLs > challenge hardcoded defaults
- chainId hardcoded - resolvers/challenges know their chainId internally (eth=1, matic=137, etc.)
Domain Resolver Architecture
Static Registry: PKC.domainResolvers
Similar to existing Plebbit.challenges, domain resolvers are registered in a static object:
// Static registry (like Plebbit.challenges)
PKC.domainResolvers: { [name: string]: DomainResolverInstance }
// Instance interface (registered directly, no cloning needed)
export interface DomainResolverInstance {
name: string;
tlds: string[]; // e.g., ['.eth']
// chainProviders passed as arg to resolve(), not stored on instance
resolve: (args: {
domain: string;
txtRecordName: string;
chainProviders: ChainProviders; // user's config passed at resolve time
plebbit: Plebbit;
}) => Promise<DomainResolverResult>;
}
// ChainProviders type (same as current)
type ChainProviders = {
[chainTicker: string]: {
urls: string[];
chainId: number;
}
};Registration (App Startup)
import PKC from '@pkc/pkc-js';
import { ensResolver } from '@bitsocial/resolver-ens';
import { snsResolver } from '@bitsocial/resolver-sns';
// Register resolver instances directly (once at app startup)
PKC.domainResolvers['ens'] = ensResolver;
PKC.domainResolvers['sns'] = snsResolver;
// Example ensResolver structure:
// {
// name: 'ens',
// tlds: ['.eth'],
// resolve: async ({ domain, txtRecordName, chainProviders, plebbit }) => {
// // chainProviders.eth.urls contains the RPC URLs to use
// // chainProviders.eth.chainId contains the chain ID
// }
// }JSON Config (User Account Storage)
{
"domainResolvers": [
{
"name": "ens",
"chainProviders": {
"eth": { "urls": ["https://custom-rpc.com"], "chainId": 1 }
}
},
{
"name": "sns",
"chainProviders": {
"sol": { "urls": ["https://solana-rpc.com"], "chainId": -1 }
}
}
]
}Initialization Flow
// Load user config from storage
const userConfig = JSON.parse(savedUserAccount);
// Create PKC instance
const plebbit = await PKC({
domainResolvers: userConfig.domainResolvers // [{ name, chainProviders }]
});
// Internally:
// 1. Store configs as-is in plebbit.domainResolvers (no cloning needed)
// 2. At resolve time, lookup PKC.domainResolvers[name] and pass chainProviders to resolve()Resolution Flow (at resolve time)
// When resolving a domain like "mysite.eth":
// 1. Find config where TLD matches (find resolver for '.eth')
const config = plebbit.domainResolvers.find(c => {
const resolver = PKC.domainResolvers[c.name];
return resolver?.tlds.some(tld => domain.endsWith(tld));
});
// 2. Get resolver from static registry
const resolver = PKC.domainResolvers[config.name];
// 3. Call resolve() with chainProviders from user config
const result = await resolver.resolve({
domain,
txtRecordName,
chainProviders: config.chainProviders, // user's config passed here
plebbit
});Priority Resolution Order
For Domain Resolvers:
- Uses its own urls[] directly (configured when resolver is instantiated)
For Challenges/Tipping/etc:
1. User-specified URLs in challenge options (e.g., ethRpcUrl)
2. URLs from registered domainResolvers matching the chainTicker
3. Challenge's own hardcoded fallback URLs
Challenge RPC Resolution
Challenges embed their own helper logic inline (no separate util file needed):
// Inside evm-contract-call challenge
function getChainUrls(
plebbit: Plebbit,
chainTicker: string,
userSpecifiedUrls?: string[],
hardcodedFallbackUrls?: string[]
): string[] {
// 1. User-specified URLs from challenge options (highest priority)
if (userSpecifiedUrls?.length) return userSpecifiedUrls;
// 2. URLs from resolver configs (merge all that have this chainTicker)
const mergedUrls: string[] = [];
for (const config of plebbit.domainResolvers || []) {
const chainProvider = config.chainProviders?.[chainTicker];
if (chainProvider?.urls?.length) {
mergedUrls.push(...chainProvider.urls);
}
}
if (mergedUrls.length) return mergedUrls;
// 3. Challenge's hardcoded fallback URLs
if (hardcodedFallbackUrls?.length) return hardcodedFallbackUrls;
throw new Error(`No RPC URLs found for chain ${chainTicker}`);
}Example Configurations
Simple (ENS only, using resolver defaults)
// App startup: register resolver
PKC.domainResolvers['ens'] = ensResolver;
// User just enables ENS without custom URLs
const plebbit = await PKC({
domainResolvers: [{ name: 'ens' }] // uses resolver's default chainProviders
});Custom RPC URLs
const plebbit = await PKC({
domainResolvers: [{
name: 'ens',
chainProviders: {
eth: { urls: ['https://my-custom-rpc.com'], chainId: 1 }
}
}]
});With Challenge-Specific Override
// Subplebbit challenge settings
{
name: "evm-contract-call",
options: {
chainTicker: "eth",
address: "0x...",
abi: "...",
condition: ">1000",
rpcUrls: "https://eth-mainnet.infura.io/v3/..." // overrides resolver URLs
}
}No Resolvers (Challenge-Only)
const plebbit = await PKC({
// No domainResolvers - can't resolve .eth/.sol addresses
});
// Challenges still work using their hardcoded fallback URLs
// e.g., evm-contract-call has built-in Infura/Alchemy URLsMultiple Resolvers with Same Chain (URLs merged for challenges)
PKC.domainResolvers['ens'] = ensResolver; // has eth chainProvider
PKC.domainResolvers['unstoppable'] = udResolver; // also has eth chainProvider
const plebbit = await PKC({
domainResolvers: [{ name: 'ens' }, { name: 'unstoppable' }]
});
// For challenges: ETH URLs from both resolvers are mergedJSON-Serializable Config (for user account storage)
{
"domainResolvers": [
{
"name": "ens",
"chainProviders": {
"eth": { "urls": ["https://ethrpc.xyz"], "chainId": 1 }
}
},
{
"name": "sns",
"chainProviders": {
"sol": { "urls": ["https://solana-rpc.com"], "chainId": -1 }
}
}
]
}Note: The actual resolver implementation is loaded by name from PKC.domainResolvers,
chainProviders override the resolver's defaults.
Files to Modify
- src/schema.ts - Remove
chainProviders, adddomainResolversconfig schema - src/types.ts - Add DomainResolverInstance, DomainResolverConfig interfaces
- src/plebbit/plebbit.ts - Add static
PKC.domainResolversregistry, storeplebbit.domainResolversconfig - src/domain-resolver.ts - Refactor to use plugin system
- src/clients/base-client-manager.ts - Update resolution to use registered resolvers
- src/runtime/node/subplebbit/challenges/plebbit-js-challenges/evm-contract-call/index.ts - Add
rpcUrlsoption, embed helper inline
Implementation Steps
Phase 1: Define New Types & Schema
- Add
DomainResolverInstanceinterface to src/types.ts (name, tlds, resolve) - Add
DomainResolverConfigtype to src/types.ts ({ name: string, chainProviders?: ChainProviders }) - Add
DomainResolverConfigSchemato src/schema.ts (validates { name, chainProviders? }) - Add optional
domainResolvers: DomainResolverConfig[]to PlebbitUserOptionsSchema - Remove
chainProvidersfrom PlebbitUserOptionsSchema (breaking change) - Remove
defaultChainProvidersconstant
Phase 2: Static Registry
- Add static
PKC.domainResolvers: { [name: string]: DomainResolverInstance }object - Initialize as empty object
{} - Export from src/index.ts
Phase 3: Update Plebbit Class
- Add
plebbit.domainResolvers: DomainResolverConfig[]property (stores user configs as-is) - In constructor: validate each config name exists in
PKC.domainResolvers - Throw if resolver not found for config name
- Remove
chainProvidersproperty - No cloning needed - configs stored directly
Phase 4: Refactor Domain Resolution
- Update base-client-manager.ts to find resolver config by TLD
- Look up resolver in PKC.domainResolvers by config.name
- Call resolver.resolve() with chainProviders from config
- Throw ERR_NO_RESOLVER_FOR_TLD if no matching resolver
Phase 5: Update Challenges
- Add
rpcUrlsoption to evm-contract-call optionInputs - Embed helper function inline in challenge
- Add hardcoded fallback URLs in challenge (e.g., public Infura endpoints)
Phase 6: Tests & Migration
- Add tests for resolver registration
- Add tests for JSON config -> instance creation with URL override
- Test challenge fallback chain
- Document breaking change in CHANGELOG
Breaking Changes
chainProvidersremoved from PlebbitOptions- Must register resolvers via
PKC.domainResolvers['name'] = resolverInstancebefore use - JSON config stores
{ name, chainProviders? }[]- resolvers looked up by name, chainProviders override defaults - Challenges work without resolvers (use hardcoded fallbacks)
- For challenges: URLs from all resolvers with matching chainTicker are merged
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels