Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
88 changes: 80 additions & 8 deletions packages/js-evo-sdk/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,28 @@ export interface ConnectionOptions {
}

export interface EvoSDKOptions extends ConnectionOptions {
network?: 'testnet' | 'mainnet' | 'local';
network?: 'testnet' | 'mainnet' | 'local' | 'devnet';
trusted?: boolean;
// Custom masternode addresses. When provided, network and trusted options are ignored.
// Custom masternode addresses to seed the SDK with. `network` still
// controls which Network enum the underlying builder uses (and, for
// trusted mode, which quorums endpoint is prefetched); the addresses
// here replace the network's built-in defaults at seed time.
// Example: ['https://127.0.0.1:1443', 'https://192.168.1.100:1443']
addresses?: string[];
// Short name of the devnet (e.g. 'paloma'). Required when network === 'devnet'
// AND trusted === true (used to derive the quorum URL). When trusted === false,
// explicit `addresses` are mandatory and `devnetName` alone is not sufficient
// — no masternode addresses can be discovered without a trusted context.
devnetName?: string;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// Optional override for the trusted devnet quorum base URL. When omitted,
// the URL is derived as `https://quorums.<devnetName>.networks.dash.org`.
// Only consulted when trusted === true && network === 'devnet'.
quorumUrl?: string;
}

export class EvoSDK {
private wasmSdk?: wasm.WasmSdk;
private options: Required<Pick<EvoSDKOptions, 'network' | 'trusted'>> & ConnectionOptions & { addresses?: string[] };
private options: Required<Pick<EvoSDKOptions, 'network' | 'trusted'>> & ConnectionOptions & { addresses?: string[]; devnetName?: string; quorumUrl?: string };

public addresses!: AddressesFacade;
public documents!: DocumentsFacade;
Expand All @@ -56,8 +68,27 @@ export class EvoSDK {
public shielded!: ShieldedFacade;
constructor(options: EvoSDKOptions = {}) {
// Apply defaults while preserving any future connection options
const { network = 'testnet', trusted = false, addresses, ...connection } = options;
this.options = { network, trusted, addresses, ...connection };
const { network = 'testnet', trusted = false, addresses, devnetName, quorumUrl, ...connection } = options;

if (network === 'devnet') {
const hasAddresses = !!(addresses && addresses.length > 0);
if (!devnetName && !hasAddresses) {
throw new Error("EvoSDK: network 'devnet' requires either devnetName or explicit addresses");
}
if (trusted && !devnetName) {
throw new Error("EvoSDK: trusted devnet requires devnetName (used to derive the quorum URL)");
}
if (!trusted && !hasAddresses) {
throw new Error("EvoSDK: non-trusted devnet requires explicit addresses (no addresses can be discovered without a trusted context)");
}
if (quorumUrl && !trusted) {
throw new Error("EvoSDK: quorumUrl is only meaningful when trusted === true");
}
} else if (quorumUrl) {
throw new Error("EvoSDK: quorumUrl is only valid when network === 'devnet'");
}
Comment thread
PastaPastaPasta marked this conversation as resolved.

this.options = { network, trusted, addresses, devnetName, quorumUrl, ...connection };

this.addresses = new AddressesFacade(this);
this.documents = new DocumentsFacade(this);
Expand Down Expand Up @@ -96,7 +127,7 @@ export class EvoSDK {
}
await initWasm();

const { network, trusted, version, proofs, settings, logs, addresses } = this.options;
const { network, trusted, version, proofs, settings, logs, addresses, devnetName, quorumUrl } = this.options;

// Prefetch trusted context only when trusted mode is requested
let context: wasm.WasmTrustedContext | undefined;
Expand All @@ -107,6 +138,13 @@ export class EvoSDK {
context = await wasm.WasmTrustedContext.prefetchTestnet();
} else if (network === 'local') {
context = await wasm.WasmTrustedContext.prefetchLocal();
} else if (network === 'devnet') {
if (!devnetName) {
throw new Error("EvoSDK: trusted devnet requires devnetName");
}
context = quorumUrl
? await wasm.WasmTrustedContext.prefetchDevnetWithUrl(quorumUrl)
: await wasm.WasmTrustedContext.prefetchDevnet(devnetName);
} else {
throw new Error(`Unknown network: ${network}`);
}
Expand All @@ -122,6 +160,8 @@ export class EvoSDK {
builder = wasm.WasmSdkBuilder.testnet();
} else if (network === 'local') {
builder = wasm.WasmSdkBuilder.local();
} else if (network === 'devnet') {
builder = wasm.WasmSdkBuilder.newDevnet();
} else {
throw new Error(`Unknown network: ${network}`);
}
Expand Down Expand Up @@ -181,11 +221,43 @@ export class EvoSDK {
static local(options: ConnectionOptions = {}): EvoSDK { return new EvoSDK({ network: 'local', ...options }); }
static localTrusted(options: ConnectionOptions = {}): EvoSDK { return new EvoSDK({ network: 'local', trusted: true, ...options }); }

/**
* Create an EvoSDK instance configured for a devnet, without trusted-context
* proof verification. Requires explicit `addresses` in `options` —
* `devnetName` alone is not sufficient in non-trusted mode, since no
* masternode addresses can be discovered without a trusted context.
* Proof-bearing queries will fail; for proof verification on devnet, use
* `EvoSDK.devnetTrusted` instead.
*/
static devnet(devnetName: string, options: ConnectionOptions & { addresses?: string[] } = {}): EvoSDK {
return new EvoSDK({ network: 'devnet', devnetName, ...options });
}
Comment thread
PastaPastaPasta marked this conversation as resolved.

/**
* Create an EvoSDK instance configured for a devnet with a trusted context.
*
* The trusted context is prefetched from
* `https://quorums.<devnetName>.networks.dash.org` by default. Pass
* `quorumUrl` to override (useful when the public DNS is not yet deployed).
*
* @example
* ```typescript
* const sdk = EvoSDK.devnetTrusted('paloma');
* await sdk.connect();
* ```
*/
static devnetTrusted(
devnetName: string,
options: ConnectionOptions & { quorumUrl?: string } = {},
): EvoSDK {
return new EvoSDK({ network: 'devnet', devnetName, trusted: true, ...options });
}

/**
* Create an EvoSDK instance configured with specific masternode addresses.
*
* @param addresses - Array of HTTPS URLs to masternodes (e.g., ['https://127.0.0.1:1443'])
* @param network - Network identifier: 'mainnet', 'testnet' (default: 'testnet')
* @param network - Network identifier: 'mainnet', 'testnet', 'devnet', or 'local' (default: 'testnet')
* @param options - Additional connection options
* @returns A configured EvoSDK instance (not yet connected - call .connect() to establish connection)
*
Expand All @@ -195,7 +267,7 @@ export class EvoSDK {
* await sdk.connect();
* ```
*/
static withAddresses(addresses: string[], network: 'mainnet' | 'testnet' | 'local' = 'testnet', options: ConnectionOptions = {}): EvoSDK {
static withAddresses(addresses: string[], network: 'mainnet' | 'testnet' | 'local' | 'devnet' = 'testnet', options: ConnectionOptions & { devnetName?: string } = {}): EvoSDK {
return new EvoSDK({ addresses, network, ...options });
}
}
Expand Down
63 changes: 63 additions & 0 deletions packages/js-evo-sdk/tests/unit/sdk.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,67 @@ describe('EvoSDK', () => {
expect(sdk.isConnected).to.equal(false);
});
});

describe('devnet()', () => {
it('should create non-trusted devnet instance with addresses + devnetName', () => {
const sdk = EvoSDK.devnet('paloma', { addresses: [TEST_ADDRESS_1] });
expect(sdk).to.be.instanceof(EvoSDK);
expect(sdk.options.network).to.equal('devnet');
expect(sdk.options.devnetName).to.equal('paloma');
expect(sdk.options.addresses).to.deep.equal([TEST_ADDRESS_1]);
expect(sdk.options.trusted).to.be.false();
expect(sdk.isConnected).to.equal(false);
});

it('should accept devnet with only addresses (no devnetName)', () => {
const sdk = new EvoSDK({ network: 'devnet', addresses: [TEST_ADDRESS_1] });
expect(sdk.options.network).to.equal('devnet');
expect(sdk.options.addresses).to.deep.equal([TEST_ADDRESS_1]);
expect(sdk.options.devnetName).to.be.undefined();
});

it('should reject non-trusted devnet without addresses', () => {
// devnetName alone is not enough — without trusted context, no addresses can be discovered.
expect(() => EvoSDK.devnet('paloma')).to.throw(/addresses/);
});

it('should reject network=devnet without devnetName and without addresses', () => {
expect(() => new EvoSDK({ network: 'devnet' })).to.throw(/devnet/);
});
});

describe('devnetTrusted()', () => {
it('should create trusted devnet instance', () => {
const sdk = EvoSDK.devnetTrusted('paloma');
expect(sdk).to.be.instanceof(EvoSDK);
expect(sdk.options.network).to.equal('devnet');
expect(sdk.options.devnetName).to.equal('paloma');
expect(sdk.options.trusted).to.be.true();
expect(sdk.isConnected).to.equal(false);
});

it('should preserve quorumUrl override', () => {
const sdk = EvoSDK.devnetTrusted('paloma', { quorumUrl: 'https://custom.example' });
expect(sdk.options.quorumUrl).to.equal('https://custom.example');
expect(sdk.options.trusted).to.be.true();
});

it('should reject trusted devnet without devnetName', () => {
expect(() => new EvoSDK({ network: 'devnet', trusted: true })).to.throw(/devnetName/);
});

it('should reject quorumUrl when trusted is false', () => {
expect(() => new EvoSDK({
network: 'devnet',
devnetName: 'paloma',
addresses: [TEST_ADDRESS_1],
quorumUrl: 'https://custom',
})).to.throw(/quorumUrl/);
});

it('should reject quorumUrl on non-devnet networks', () => {
expect(() => new EvoSDK({ network: 'testnet', trusted: true, quorumUrl: 'https://x' } as any))
.to.throw(/quorumUrl/);
});
});
});
2 changes: 1 addition & 1 deletion packages/rs-sdk-trusted-context-provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This crate provides a trusted HTTP-based context provider for the Dash SDK that

- **Mainnet**: Uses `https://quorums.mainnet.networks.dash.org/`
- **Testnet**: Uses `https://quorums.testnet.networks.dash.org/`
- **Devnet**: Uses `https://quorums.devnet.<devnet_name>.networks.dash.org/`
- **Devnet**: Uses `https://quorums.<devnet_name>.networks.dash.org/`

## Usage

Expand Down
4 changes: 2 additions & 2 deletions packages/rs-sdk-trusted-context-provider/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! ## Networks Supported
//! - **Mainnet**: Uses `https://quorums.mainnet.networks.dash.org/`
//! - **Testnet**: Uses `https://quorums.testnet.networks.dash.org/`
//! - **Devnet**: Uses `https://quorums.devnet.<devnet_name>.networks.dash.org/`
//! - **Devnet**: Uses `https://quorums.<devnet_name>.networks.dash.org/`

pub mod error;
pub mod provider;
Expand Down Expand Up @@ -44,7 +44,7 @@ pub fn get_quorum_base_url(
"Devnet name cannot start or end with a hyphen".to_string(),
));
}
Ok(format!("https://quorums.devnet.{}.networks.dash.org", name))
Ok(format!("https://quorums.{}.networks.dash.org", name))
} else {
Err(TrustedContextProviderError::InvalidDevnetName(
"Devnet name must be provided for devnet network".to_string(),
Expand Down
2 changes: 1 addition & 1 deletion packages/rs-sdk-trusted-context-provider/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ mod tests {

assert_eq!(
get_quorum_base_url(Network::Devnet, Some("example")).unwrap(),
"https://quorums.devnet.example.networks.dash.org"
"https://quorums.example.networks.dash.org"
);
}

Expand Down
73 changes: 70 additions & 3 deletions packages/wasm-sdk/src/context_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ pub struct WasmContext {}
///
/// Holds pre-fetched quorum keys and discovered masternode addresses for
/// proof verification and network connectivity. Create one via the async
/// `prefetchMainnet()`, `prefetchTestnet()`, or `prefetchLocal()` factory
/// methods, then pass it to a builder via `withTrustedContext()`.
/// `prefetchMainnet()`, `prefetchTestnet()`, `prefetchDevnet()`, or
/// `prefetchLocal()` factory methods, then pass it to a builder via
/// `withTrustedContext()`.
#[wasm_bindgen]
#[derive(Clone)]
pub struct WasmTrustedContext {
Expand All @@ -35,7 +36,7 @@ impl ContextProvider for WasmContext {
_core_chain_locked_height: u32,
) -> Result<[u8; 48], ContextProviderError> {
Err(ContextProviderError::Generic(
"Non-trusted mode is not supported in WASM. Please use the trusted SDK builders (new_mainnet_trusted or new_testnet_trusted) instead.".to_string()
"Non-trusted mode is not supported in WASM. Please construct a WasmTrustedContext via prefetchMainnet/prefetchTestnet/prefetchDevnet/prefetchLocal and attach it with WasmSdkBuilder.withTrustedContext().".to_string()
))
}

Expand Down Expand Up @@ -166,6 +167,72 @@ impl WasmTrustedContext {
})
}

/// Pre-fetch quorum keys and masternode addresses for a devnet.
///
/// `devnet_name` is the short name of the devnet (e.g. `"paloma"`). The
/// quorum base URL is derived as `https://quorums.<devnet_name>.networks.dash.org`.
///
/// Returns a ready-to-use `WasmTrustedContext` that can be passed to
/// `WasmSdkBuilder.newDevnet().withTrustedContext(context)`.
#[wasm_bindgen(js_name = "prefetchDevnet")]
pub async fn prefetch_devnet(devnet_name: String) -> Result<WasmTrustedContext, WasmSdkError> {
let inner = rs_sdk_trusted_context_provider::TrustedHttpContextProvider::new(
dash_sdk::dpp::dashcore::Network::Devnet,
Some(devnet_name),
std::num::NonZeroUsize::new(100).unwrap(),
)
.map_err(|e| WasmSdkError::generic(format!("Failed to create context provider: {}", e)))?
.with_refetch_if_not_found(false);

let inner = Arc::new(inner);

inner
.update_quorum_caches()
.await
.map_err(|e| WasmSdkError::generic(format!("Failed to prefetch quorums: {}", e)))?;

let discovered_addresses = Self::fetch_addresses_from(&inner).await?;

Ok(WasmTrustedContext {
inner,
discovered_addresses,
})
}

/// Pre-fetch quorum keys and masternode addresses for a devnet using a
/// fully-specified quorum base URL.
///
/// Use this when the default
/// `https://quorums.<devnet_name>.networks.dash.org` URL produced by
/// `prefetchDevnet` is not yet deployed for a devnet, or when pointing
/// at a non-standard quorums endpoint.
#[wasm_bindgen(js_name = "prefetchDevnetWithUrl")]
pub async fn prefetch_devnet_with_url(
base_url: String,
) -> Result<WasmTrustedContext, WasmSdkError> {
let inner = rs_sdk_trusted_context_provider::TrustedHttpContextProvider::new_with_url(
dash_sdk::dpp::dashcore::Network::Devnet,
base_url,
std::num::NonZeroUsize::new(100).unwrap(),
)
.map_err(|e| WasmSdkError::generic(format!("Failed to create context provider: {}", e)))?
.with_refetch_if_not_found(false);

let inner = Arc::new(inner);

inner
.update_quorum_caches()
.await
.map_err(|e| WasmSdkError::generic(format!("Failed to prefetch quorums: {}", e)))?;

let discovered_addresses = Self::fetch_addresses_from(&inner).await?;

Ok(WasmTrustedContext {
inner,
discovered_addresses,
})
}
Comment thread
PastaPastaPasta marked this conversation as resolved.
Comment thread
PastaPastaPasta marked this conversation as resolved.

/// Pre-fetch quorum keys and masternode addresses for a local network.
///
/// Uses the default local quorum sidecar URL (`http://127.0.0.1:22444`).
Expand Down
24 changes: 22 additions & 2 deletions packages/wasm-sdk/src/sdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ impl WasmSdkBuilder {
///
/// # Arguments
/// * `addresses` - Array of HTTPS URLs (e.g., ["https://127.0.0.1:1443"])
/// * `network` - Network identifier: "mainnet", "testnet" or "local"
/// * `network` - Network identifier: "mainnet", "testnet", "devnet", or "local"
#[wasm_bindgen(js_name = "withAddresses")]
pub fn new_with_addresses(
addresses: Vec<String>,
Expand Down Expand Up @@ -205,10 +205,11 @@ impl WasmSdkBuilder {
let network = match network.to_lowercase().as_str() {
"mainnet" => Network::Mainnet,
"testnet" => Network::Testnet,
"devnet" => Network::Devnet,
"local" => Network::Regtest,
_ => {
return Err(WasmSdkError::invalid_argument(format!(
"Invalid network '{}'. Expected: mainnet, testnet or local",
"Invalid network '{}'. Expected: mainnet, testnet, devnet, or local",
network
)));
}
Expand Down Expand Up @@ -245,6 +246,25 @@ impl WasmSdkBuilder {
}
}

/// Create a new SdkBuilder preconfigured for a devnet.
///
/// Devnets have no built-in default address list. The returned builder
/// is expected to be paired with either explicit addresses (via the
/// `withAddresses` variant) or a `WasmTrustedContext` from
/// `WasmTrustedContext.prefetchDevnet(name)`, whose discovered addresses
/// will be substituted via `withTrustedContext`.
#[wasm_bindgen(js_name = "newDevnet")]
pub fn new_devnet() -> Self {
let sdk_builder = SdkBuilder::new(dash_sdk::sdk::AddressList::default())
.with_network(dash_sdk::dpp::dashcore::Network::Devnet)
.with_context_provider(WasmContext {});

Self {
inner: sdk_builder,
trusted_context: None,
}
}

/// Create a new SdkBuilder preconfigured for a local network using default dashmate gateway.
#[wasm_bindgen(js_name = "local")]
pub fn new_local() -> Self {
Expand Down
Loading
Loading