Skip to content
Closed
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
Empty file added .codex
Empty file.
2 changes: 2 additions & 0 deletions .env.walkthrough
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PLATFORM_TUTORIALS_IDENTITY_MNEMONIC=grab blanket exit because snow field benefit divorce course rain finish lady
# 4TQYGira8KhvUNA641qmsvhtxZunpFBdwn2uxiUSPxMx
10 changes: 10 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
env:
browser: true
es2021: true
extends: [
'airbnb-base',
]
parserOptions:
ecmaVersion: latest
sourceType: module
rules: {}
22 changes: 22 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#ebc08c",
"activityBar.background": "#ebc08c",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#188855",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#15202b99",
"sash.hoverBorder": "#ebc08c",
"statusBar.background": "#e4a860",
"statusBar.foreground": "#15202b",
"statusBarItem.hoverBackground": "#dd9034",
"statusBarItem.remoteBackground": "#e4a860",
"statusBarItem.remoteForeground": "#15202b",
"titleBar.activeBackground": "#e4a860",
"titleBar.activeForeground": "#15202b",
"titleBar.inactiveBackground": "#e4a86099",
"titleBar.inactiveForeground": "#15202b99"
},
"peacock.color": "#e4a860"
}
111 changes: 111 additions & 0 deletions 1-Identities-and-Names/identity-and-name-register-mainnet.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Register a new Dash Platform identity and a DPNS name on mainnet.
//
// Required environment variables:
// NETWORK=mainnet
// PLATFORM_MNEMONIC="..."
// NAME_LABEL=your-unique-name
//
// The mnemonic's first available DIP-13 identity index is used. Its primary
// mainnet Platform address must already contain enough funds.
import { randomBytes } from 'node:crypto';
import { Identity, Identifier } from '@dashevo/evo-sdk';
import { clientConfig, setupDashClient } from '../setupDashClient.mjs';

const NAME_LABEL = process.env.NAME_LABEL?.trim().toLowerCase();
const IDENTITY_CREDITS = 5000000n;

if (clientConfig.network !== 'mainnet') {
throw new Error(
'Refusing to run: set NETWORK=mainnet explicitly in .env or the environment.',
);
}

if (!process.env.PLATFORM_MNEMONIC) {
throw new Error('PLATFORM_MNEMONIC is required.');
}

if (!NAME_LABEL) {
throw new Error(
'NAME_LABEL is required (provide the label without ".dash").',
);
}

const { sdk, keyManager, addressKeyManager } = await setupDashClient({
requireIdentity: false,
});

if (!keyManager || !addressKeyManager) {
throw new Error('Could not initialize the mnemonic key managers.');
}

try {
if (!(await sdk.dpns.isValidUsername(NAME_LABEL))) {
throw new Error(`"${NAME_LABEL}" is not a valid DPNS name label.`);
}

if (await sdk.dpns.isContestedUsername(NAME_LABEL)) {
throw new Error(
`"${NAME_LABEL}.dash" is contested and cannot use simple registration.`,
);
}

if (!(await sdk.dpns.isNameAvailable(NAME_LABEL))) {
throw new Error(`"${NAME_LABEL}.dash" is already registered.`);
}

const identity = new Identity(new Identifier(randomBytes(32)));
keyManager.getKeysInCreation().forEach((key) => {
identity.addPublicKey(key.toIdentityPublicKey());
});

let registeredIdentity;

try {
const result = await sdk.addresses.createIdentity({
identity,
inputs: [
{
address: addressKeyManager.primaryAddress.bech32m,
amount: IDENTITY_CREDITS,
},
],
identitySigner: keyManager.getFullSigner(),
addressSigner: addressKeyManager.getSigner(),
});
registeredIdentity = result.identity;
} catch (error) {
// Work around a known SDK proof-verification error that can occur after a
// successful identity registration.
const match = error.message?.match(/proof returned identity (\w+) but/);
if (!match) throw error;

registeredIdentity = await sdk.identities.fetch(match[1]);
if (!registeredIdentity) {
throw new Error(
`Identity ${match[1]} was reported as registered but could not be fetched.`,
);
}
}

const identityId = registeredIdentity.id.toString();
const identityKey = registeredIdentity.getPublicKeyById(2);
if (!identityKey) {
throw new Error(`Identity ${identityId} does not contain auth key 2.`);
}

console.log('Identity registered.');
console.log('Identity ID:', identityId);

const nameResult = await sdk.dpns.registerName({
label: NAME_LABEL,
identity: registeredIdentity,
identityKey,
signer: keyManager.getFullSigner(),
});

console.log(`Name registered: ${NAME_LABEL}.dash`);
console.log(nameResult.toJSON());
} catch (error) {
console.error('Registration failed:', error.message);
process.exitCode = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// See https://docs.dash.org/projects/platform/en/stable/docs/tutorials/contracts-and-documents/register-a-data-contract.html
import { DataContract } from '@dashevo/evo-sdk';
import { setupDashClient } from '../setupDashClient.mjs';

const { sdk, keyManager } = await setupDashClient();
const { identity, identityKey, signer } = await keyManager.getAuth();

// Define the document schemas for the contract
const documentSchemas = {
note: {
type: 'object',
properties: {
message: {
type: 'string',
position: 0,
},
},
keywords: ['foo', 'bar', 'baz'],
additionalProperties: false,
},
};

try {
// Get the next identity nonce for contract creation
const identityNonce = await sdk.identities.nonce(identity.id.toString());

// Create the data contract
const dataContract = new DataContract({
ownerId: identity.id,
identityNonce: (identityNonce || 0n) + 1n,
schemas: documentSchemas,
fullValidation: true,
});

// Publish the contract to the platform
const publishedContract = await sdk.contracts.publish({
dataContract,
identityKey,
signer,
});

console.log('Contract registered:\n', publishedContract.toJSON());
} catch (e) {
console.error('Something went wrong:\n', e.message);
}
152 changes: 152 additions & 0 deletions 2-Contracts-and-Documents/document-retrieve-history.debug.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// Diagnostic copy of document-retrieve-history.mjs
//
// Goal: capture the FULL error + trace logs for sdk.documents.history(),
// which returns gRPC "Operation is not implemented or not supported" even
// though grpcurl/grpcui (native gRPC) and a hand-built grpc-web POST both
// succeed against the same node:port for getDocumentHistory.
//
// Run: DAPI_ADDRESS=68.67.122.7 node 2-Contracts-and-Documents/document-retrieve-history.debug.mjs
//
// Optional overrides:
// SDK_VERSION=11 (platform version override; setupDashClient-core pins 11)
// SDK_VERSION=none (do NOT pin a version — let the SDK negotiate)
// SDK_PROOFS=false (request without proofs)
// LOG=trace (wasm log level: trace|debug|info|warn|error)

import { EvoSDK, wallet } from '@dashevo/evo-sdk';

const DATA_CONTRACT_ID = 'Gec4xb6s8NHqLYQXzaLQgxfDyLEqZ7LpjoijQNG3ExK4';
const DOCUMENT_TYPE = 'note';
const DOCUMENT_ID = 'G8HezLu3wKZkXwvLQ6C1jfvuQ2tCAz2vXzAR9EQWcUhT';

const LOG = process.env.LOG || 'trace';
const SDK_PROOFS = process.env.SDK_PROOFS; // 'true' | 'false' | undefined
const RAW_VERSION = process.env.SDK_VERSION ?? '11';

function normalizeDapiAddress(address) {
if (/^https?:\/\//.test(address)) return address;
return `https://${address}:1443`;
}

const dapiRaw = process.env.DAPI_ADDRESSES || process.env.DAPI_ADDRESS || '';
const addresses = dapiRaw
.split(',')
.map((a) => a.trim())
.filter(Boolean)
.map(normalizeDapiAddress);

console.log('--- config ---');
console.log('addresses :', addresses);
console.log('SDK_VERSION (raw):', RAW_VERSION);
console.log('SDK_PROOFS :', SDK_PROOFS ?? '(default)');
console.log('LOG :', LOG);

// Turn on WASM-side trace logging BEFORE building the SDK.
try {
await EvoSDK.setLogLevel(LOG);
console.log('setLogLevel ok :', LOG);
} catch (e) {
console.log('setLogLevel FAILED:', e?.message ?? e);
}

const TRUSTED = process.env.SDK_TRUSTED !== 'false'; // default true; set false to pin addresses

const options = {
network: 'testnet',
trusted: TRUSTED,
addresses,
logs: LOG,
};

// Version pin is the prime suspect — make it toggleable.
if (RAW_VERSION !== 'none' && RAW_VERSION !== '') {
options.version = Number(RAW_VERSION);
}
if (SDK_PROOFS === 'true') options.proofs = true;
if (SDK_PROOFS === 'false') options.proofs = false;

console.log('\n--- EvoSDK options ---');
console.log(options);

const sdk = new EvoSDK(options);
await sdk.connect();

try {
const negotiated = sdk.version?.();
console.log('\nsdk.version() :', negotiated);
} catch (e) {
console.log('\nsdk.version() err:', e?.message ?? e);
}

function dumpError(label, e) {
console.error(`\n===== ${label} =====`);
console.error('typeof :', typeof e);
console.error('constructor :', e?.constructor?.name);
console.error('message :', e?.message);
console.error('code :', e?.code);
console.error('name :', e?.name);
console.error('cause :', e?.cause);
// Enumerate own props (wasm errors often stash data in non-standard fields)
try {
const own = {};
for (const k of Object.getOwnPropertyNames(e || {})) {
if (k === 'stack') continue;
own[k] = e[k];
}
console.error('ownProps :', own);
} catch (inner) {
console.error('ownProps err :', inner?.message);
}
try {
console.error('JSON :', JSON.stringify(e));
} catch {
/* not serializable */
}
console.error('toString :', String(e));
console.error('stack :\n', e?.stack);
}

// Brute-force: retry history on the SAME sdk instance. Nodes that fail get
// banned by the dapi-client for this sdk's lifetime, so the address pool
// shrinks toward the one upgraded node (.7) on each Unimplemented.
const MAX_ATTEMPTS = Number(process.env.MAX_ATTEMPTS || 60);

let history;
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
try {
history = await sdk.documents.history({
dataContractId: DATA_CONTRACT_ID,
documentTypeName: DOCUMENT_TYPE,
documentId: DOCUMENT_ID,
});
console.log(
`\n✅ SUCCESS on attempt ${attempt}, entries: ${history?.size ?? history}`,
);
break;
} catch (e) {
const msg = e?.message ?? String(e);
const kind = /not implemented or not supported/i.test(msg)
? 'UNIMPLEMENTED'
: /no available addresses/i.test(msg)
? 'POOL_EXHAUSTED'
: 'OTHER';
console.log(`attempt ${attempt}: ${kind} — ${msg.slice(0, 120)}`);
if (kind === 'POOL_EXHAUSTED') {
// All non-banned nodes failed for this sdk; nothing left to try.
console.log('All nodes banned/exhausted for this sdk instance.');
break;
}
}
}

if (history) {
for (const [ts, doc] of history) {
console.log(` Version at ${ts}:`, doc?.toJSON?.() ?? doc);
}
} else {
console.log(
'\n❌ Never hit the upgraded node within',
MAX_ATTEMPTS,
'attempts.',
);
}
Loading
Loading