Skip to content
Open
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
15 changes: 5 additions & 10 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import {
generateIdentityKey,
} from './crypto/keys.js';
import { publishContract, extractDocumentSchemas } from './platform/contract.js';
import { getIdentityBalanceAndRevision } from './platform/client.js';
import { estimateContractFee, parseContractJson } from 'dash-contract-fee-estimator';
import type { KeyType, KeyPurpose, SecurityLevel, ManageNewKeyConfig } from './types.js';
import type { BridgeState } from './types.js';
Expand Down Expand Up @@ -1104,11 +1105,8 @@ function setupEventListeners(container: HTMLElement) {
const keys = await getIdentityPublicKeys(result.identityId, state.network);
let balance: number | undefined;
try {
const { EvoSDK } = await import('@dashevo/evo-sdk');
const sdk = state.network === 'mainnet' ? EvoSDK.mainnetTrusted() : EvoSDK.testnetTrusted();
await sdk.connect();
const br = await sdk.identities.balanceAndRevision(result.identityId);
balance = Number(br?.balance ?? 0n);
const identityState = await getIdentityBalanceAndRevision(result.identityId, state.network);
balance = identityState.balance;
} catch { /* best-effort */ }
// Build final state: fetched + key WIF + validation — single updateState call
let finalState = setContractIdentityFetched(state, keys, balance);
Expand Down Expand Up @@ -1174,11 +1172,8 @@ function setupEventListeners(container: HTMLElement) {
// Also fetch balance for the existing identity credit check
let balance: number | undefined;
try {
const { EvoSDK } = await import('@dashevo/evo-sdk');
const sdk = state.network === 'mainnet' ? EvoSDK.mainnetTrusted() : EvoSDK.testnetTrusted();
await sdk.connect();
const result = await sdk.identities.balanceAndRevision(identityId);
balance = Number(result?.balance ?? 0n);
const identityState = await getIdentityBalanceAndRevision(identityId, state.network);
balance = identityState.balance;
} catch {
// Balance fetch is best-effort; continue without it
}
Expand Down
168 changes: 168 additions & 0 deletions src/platform/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { EvoSDK } from '@dashevo/evo-sdk';
import { withRetry, type RetryOptions } from '../utils/retry.js';

export type PlatformNetwork = 'testnet' | 'mainnet';

export interface PlatformIdentityKeyRecord {
keyId: number;
keyType?: string;
purpose?: string;
securityLevel?: string;
data?: unknown;
disabledAt?: unknown;
}

const PLATFORM_REQUEST_SETTINGS = {
connectTimeoutMs: 10000,
// wait_for_state_transition_result uses a 30s server-side wait window,
// so client-side request timeout must exceed that on runtimes that honor it.
timeoutMs: 40000,
retries: 2,
banFailedAddress: true,
} as const;

export const PLATFORM_PUT_SETTINGS = {
...PLATFORM_REQUEST_SETTINGS,
} as const;

const PLATFORM_OPERATION_TIMEOUT_MS = 45000;

function createPlatformSdk(network: PlatformNetwork): EvoSDK {
const options = { settings: PLATFORM_REQUEST_SETTINGS };

if (network === 'mainnet') {
return EvoSDK.mainnetTrusted(options);
}

return EvoSDK.testnetTrusted(options);
}

export async function connectPlatformSdk(
network: PlatformNetwork,
retryOptions?: RetryOptions
): Promise<EvoSDK> {
const sdk = createPlatformSdk(network);

console.log(`Connecting to ${network}...`);
await withRetry(() => sdk.connect(), retryOptions);
console.log('Connected to Platform');

return sdk;
}

export async function withConnectedPlatformSdk<T>(
network: PlatformNetwork,
callback: (sdk: EvoSDK) => Promise<T>,
retryOptions?: RetryOptions
): Promise<T> {
const sdk = await connectPlatformSdk(network, retryOptions);
return callback(sdk);
}

export async function withPlatformOperationTimeout<T>(
promise: Promise<T>,
action: string,
timeoutMs: number = PLATFORM_OPERATION_TIMEOUT_MS
): Promise<T> {
let timeoutId: number | undefined;

try {
return await Promise.race([
promise,
new Promise<never>((_, reject) => {
timeoutId = window.setTimeout(() => {
reject(new Error(`Timed out while ${action}`));
}, timeoutMs);
}),
]);
} finally {
if (timeoutId !== undefined) {
window.clearTimeout(timeoutId);
}
}
}

export async function fetchIdentityWithSdk(
sdk: EvoSDK,
identityId: string,
retryOptions?: RetryOptions
) {
return withRetry(() => sdk.identities.fetch(identityId), retryOptions);
}

export async function fetchIdentity(
identityId: string,
network: PlatformNetwork,
retryOptions?: RetryOptions
) {
return withConnectedPlatformSdk(
network,
(sdk) => fetchIdentityWithSdk(sdk, identityId, retryOptions),
retryOptions
);
}

export async function getIdentityBalanceAndRevisionWithSdk(
sdk: EvoSDK,
identityId: string,
retryOptions?: RetryOptions
): Promise<{ balance: number; revision: number }> {
const result = await withRetry(
() => sdk.identities.balanceAndRevision(identityId),
retryOptions
);

return {
balance: Number(result?.balance ?? 0n),
revision: Number(result?.revision ?? 0n),
};
}

export async function getIdentityBalanceAndRevision(
identityId: string,
network: PlatformNetwork,
retryOptions?: RetryOptions
): Promise<{ balance: number; revision: number }> {
return withConnectedPlatformSdk(
network,
(sdk) => getIdentityBalanceAndRevisionWithSdk(sdk, identityId, retryOptions),
retryOptions
);
}

export async function fetchIdentityPublicKeyRecordsWithSdk(
sdk: EvoSDK,
identityId: string,
retryOptions?: RetryOptions
): Promise<PlatformIdentityKeyRecord[]> {
const keysResponse = await withRetry(
() => sdk.identities.getKeys({
identityId,
request: { type: 'all' },
}),
retryOptions
);

if (!keysResponse) {
throw new Error('Identity not found');
}

const keysArray = Array.isArray(keysResponse) ? keysResponse : [keysResponse];
if (keysArray.length === 0) {
throw new Error('Identity has no keys');
}

return keysArray as PlatformIdentityKeyRecord[];
}

export async function fetchIdentityPublicKeyRecords(
identityId: string,
network: PlatformNetwork,
retryOptions?: RetryOptions
): Promise<PlatformIdentityKeyRecord[]> {
return withConnectedPlatformSdk(
network,
(sdk) => fetchIdentityPublicKeyRecordsWithSdk(sdk, identityId, retryOptions),
retryOptions
);
}
79 changes: 36 additions & 43 deletions src/platform/contract.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EvoSDK, IdentitySigner, DataContract } from '@dashevo/evo-sdk';
import { IdentitySigner, DataContract } from '@dashevo/evo-sdk';
import { withRetry, type RetryOptions } from '../utils/retry.js';
import { fetchIdentityWithSdk, withConnectedPlatformSdk } from './client.js';

/**
* Publish a data contract on Dash Platform.
Expand All @@ -20,55 +21,47 @@ export async function publishContract(
network: 'testnet' | 'mainnet',
retryOptions?: RetryOptions,
): Promise<{ contractId: string }> {
const sdk = network === 'mainnet'
? EvoSDK.mainnetTrusted()
: EvoSDK.testnetTrusted();

console.log(`Connecting to ${network} for contract publishing...`);
await withRetry(() => sdk.connect(), retryOptions);

const identity = await withRetry(
() => sdk.identities.fetch(identityId),
retryOptions,
);
if (!identity) {
throw new Error('Identity not found');
}
return withConnectedPlatformSdk(network, async (sdk) => {
const identity = await fetchIdentityWithSdk(sdk, identityId, retryOptions);
if (!identity) {
throw new Error('Identity not found');
}

const identityKey = identity.getPublicKeyById(publicKeyId);
if (!identityKey) {
throw new Error(`Identity key ${publicKeyId} not found`);
}
const identityKey = identity.getPublicKeyById(publicKeyId);
if (!identityKey) {
throw new Error(`Identity key ${publicKeyId} not found`);
}

const signer = new IdentitySigner();
signer.addKeyFromWif(privateKeyWif);
const signer = new IdentitySigner();
signer.addKeyFromWif(privateKeyWif);

console.log('Creating data contract...');
const contractOptions = {
ownerId: identityId,
identityNonce: 0n, // Placeholder: SDK's put_to_platform_and_wait_for_response fetches the real nonce
schemas: documentSchemas as Record<string, object>,
fullValidation: true,
...(tokens && Object.keys(tokens).length > 0 ? { tokens } : {}),
};
console.log('Creating data contract...');
const contractOptions = {
ownerId: identityId,
identityNonce: 0n, // Placeholder: SDK's put_to_platform_and_wait_for_response fetches the real nonce
schemas: documentSchemas as Record<string, object>,
fullValidation: true,
...(tokens && Object.keys(tokens).length > 0 ? { tokens } : {}),
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const dataContract = new DataContract(contractOptions as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const dataContract = new DataContract(contractOptions as any);

console.log('Publishing contract...');
const published = await withRetry(
() => sdk.contracts.publish({
dataContract,
identityKey,
signer,
}),
retryOptions,
);
console.log('Publishing contract...');
const published = await withRetry(
() => sdk.contracts.publish({
dataContract,
identityKey,
signer,
}),
retryOptions,
);

const contractId = published.id.toString();
console.log('Contract published:', contractId);
const contractId = published.id.toString();
console.log('Contract published:', contractId);

return { contractId };
return { contractId };
}, retryOptions);
}

/**
Expand Down
Loading
Loading