From 61c749d8881df8c4224ed1104607e303c063eb72 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 14:37:42 -0500 Subject: [PATCH 01/22] fix: update state transition docs examples to v4 option shapes Replace pre-v4 privateKeyWif-in-call snippets with payload + identityKey/signer (and asset-lock PrivateKey) examples sourced from shipped Options interfaces. Stop multi-line example formatting from prefixing return on the first line and drop stale sdk_example overrides for identity create/top-up. Co-Authored-By: Claude --- public/api-definitions.json | 227 ++++++++-- scripts/generate_docs.py | 876 +++++++++++++++++++++++++++++++++--- 2 files changed, 994 insertions(+), 109 deletions(-) diff --git a/public/api-definitions.json b/public/api-definitions.json index b9ecd52..466f184 100644 --- a/public/api-definitions.json +++ b/public/api-definitions.json @@ -575,7 +575,7 @@ "type": "text", "label": "Label", "required": true, - "placeholder": "ąlice" + "placeholder": "\u0105lice" } ], "sdk_method": "dpns.convertToHomographSafe" @@ -1549,29 +1549,35 @@ } ], "sdk_params": [ + { + "name": "identity", + "type": "Identity", + "label": "Identity", + "required": true, + "description": "Identity object with public keys attached (new Identity(...) + addPublicKey / IdentityPublicKeyInCreation)." + }, { "name": "assetLockProof", - "type": "string", + "type": "AssetLockProof", "label": "Asset Lock Proof", "required": true, - "description": "Hex-encoded JSON asset lock proof" + "description": "AssetLockProof from Core (AssetLockProof.fromHex / createInstantAssetLockProof / createChainAssetLockProof)." }, { - "name": "assetLockPrivateKeyWif", - "type": "string", - "label": "Asset Lock Private Key (WIF)", + "name": "assetLockPrivateKey", + "type": "PrivateKey", + "label": "Asset Lock Private Key", "required": true, - "description": "WIF format private key" + "description": "PrivateKey controlling the asset-lock output (PrivateKey.fromWIF). Separate from the IdentitySigner." }, { - "name": "publicKeys", - "type": "string", - "label": "Public Keys", + "name": "signer", + "type": "IdentitySigner", + "label": "Identity Signer", "required": true, - "description": "JSON array of public keys. Key requirements: ECDSA_SECP256K1 requires privateKeyHex or privateKeyWif for signing, BLS12_381 requires privateKeyHex for signing, ECDSA_HASH160 requires either the data field (base64-encoded 20-byte public key hash) or privateKeyHex (produces empty signatures)." + "description": "IdentitySigner holding private keys that prove ownership of the identity public keys being registered." } ], - "sdk_example": "// Asset lock proof is a hex-encoded JSON object\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockPrivateKeyWif = \"XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\n// Public keys array with proper key types\nconst publicKeys = JSON.stringify([\n {\n id: 0,\n type: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2, BIP13_SCRIPT_HASH = 3\n purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, SYSTEM = 4, VOTING = 5, OWNER = 6\n securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3\n data: \"A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ\", // Base64-encoded public key\n readOnly: false,\n privateKeyWif: \"XBrZJKcW4ajWVNAU6yP87WQog6CjFnpbqyAKgNTZRqmhYvPgMNV2\" // Required for ECDSA_SECP256K1 signing\n },\n {\n id: 1,\n type: 0, // ECDSA_SECP256K1\n purpose: 0, // AUTHENTICATION\n securityLevel: 2, // HIGH\n data: \"AnotherBase64EncodedPublicKeyHere\", // Base64-encoded public key\n readOnly: false,\n privateKeyWif: \"XAnotherPrivateKeyInWIFFormat\" // Required for signing\n },\n {\n id: 2,\n type: 2, // ECDSA_HASH160\n purpose: 0, // AUTHENTICATION\n securityLevel: 2, // HIGH\n data: \"ripemd160hash20bytes1234\", // Base64-encoded 20-byte RIPEMD160 hash\n readOnly: false\n // ECDSA_HASH160 keys produce empty signatures (privateKey not required/used for signing)\n }\n]);\n\nconst result = await client.identities.create({ assetLockProof, assetLockPrivateKeyWif, publicKeys });", "sdk_method": "identities.create" }, "identityTopUp": { @@ -1589,28 +1595,27 @@ ], "sdk_params": [ { - "name": "identityId", - "type": "string", - "label": "Identity ID", + "name": "identity", + "type": "Identity", + "label": "Identity", "required": true, - "description": "Base58 format identity ID" + "description": "Fetched Identity object to top up (sdk.identities.fetch)." }, { "name": "assetLockProof", - "type": "string", + "type": "AssetLockProof", "label": "Asset Lock Proof", "required": true, - "description": "Hex-encoded JSON asset lock proof" + "description": "AssetLockProof from Core funding the top-up." }, { - "name": "assetLockPrivateKeyWif", - "type": "string", - "label": "Asset Lock Private Key (WIF)", + "name": "assetLockPrivateKey", + "type": "PrivateKey", + "label": "Asset Lock Private Key", "required": true, - "description": "WIF format private key" + "description": "PrivateKey for the asset-lock output. Top-up does not use IdentitySigner." } ], - "sdk_example": "const identityId = \"5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk\"; // base58\nconst assetLockProof = \"a9147d3b... (hex-encoded)\";\nconst assetLockPrivateKeyWif = \"XFfpaSbZq52HPy3WWve1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1\"; // WIF format\n\nconst result = await sdk.identities.topUp({ identityId, assetLockProof, assetLockPrivateKeyWif });", "sdk_method": "identities.topUp" }, "identityUpdate": { @@ -2520,9 +2525,27 @@ } ], "sdk_params": [ - {"name": "inputs", "type": "array", "label": "Inputs", "required": true, "description": "Array of {address, nonce, amount (credits)} objects for sender addresses"}, - {"name": "outputs", "type": "array", "label": "Outputs", "required": true, "description": "Array of {address, amount (credits)} objects for recipient addresses"}, - {"name": "signer", "type": "object", "label": "Signer", "required": true, "description": "PlatformAddressSigner instance"} + { + "name": "inputs", + "type": "array", + "label": "Inputs", + "required": true, + "description": "Array of {address, nonce, amount (credits)} objects for sender addresses" + }, + { + "name": "outputs", + "type": "array", + "label": "Outputs", + "required": true, + "description": "Array of {address, amount (credits)} objects for recipient addresses" + }, + { + "name": "signer", + "type": "object", + "label": "Signer", + "required": true, + "description": "PlatformAddressSigner instance" + } ], "sdk_method": "addresses.transfer" }, @@ -2560,9 +2583,27 @@ } ], "sdk_params": [ - {"name": "identityId", "type": "string", "label": "Identity ID", "required": true, "description": "Base58 identity ID to top up"}, - {"name": "inputs", "type": "array", "label": "Inputs", "required": true, "description": "Array of {address, nonce, amount (credits)} objects"}, - {"name": "signer", "type": "object", "label": "Signer", "required": true, "description": "PlatformAddressSigner instance"} + { + "name": "identityId", + "type": "string", + "label": "Identity ID", + "required": true, + "description": "Base58 identity ID to top up" + }, + { + "name": "inputs", + "type": "array", + "label": "Inputs", + "required": true, + "description": "Array of {address, nonce, amount (credits)} objects" + }, + { + "name": "signer", + "type": "object", + "label": "Signer", + "required": true, + "description": "PlatformAddressSigner instance" + } ], "sdk_method": "addresses.topUpIdentity" }, @@ -2607,11 +2648,41 @@ } ], "sdk_params": [ - {"name": "inputs", "type": "array", "label": "Inputs", "required": true, "description": "Array of {address, nonce, amount (credits)} objects"}, - {"name": "coreFeePerByte", "type": "number", "label": "Core Fee Per Byte", "required": false, "description": "Fee per byte for Core transaction"}, - {"name": "pooling", "type": "string", "label": "Pooling", "required": false, "description": "Pooling strategy"}, - {"name": "outputScript", "type": "string", "label": "Output Script", "required": true, "description": "Dash Core output script"}, - {"name": "signer", "type": "object", "label": "Signer", "required": true, "description": "PlatformAddressSigner instance"} + { + "name": "inputs", + "type": "array", + "label": "Inputs", + "required": true, + "description": "Array of {address, nonce, amount (credits)} objects" + }, + { + "name": "coreFeePerByte", + "type": "number", + "label": "Core Fee Per Byte", + "required": false, + "description": "Fee per byte for Core transaction" + }, + { + "name": "pooling", + "type": "string", + "label": "Pooling", + "required": false, + "description": "Pooling strategy" + }, + { + "name": "outputScript", + "type": "string", + "label": "Output Script", + "required": true, + "description": "Dash Core output script" + }, + { + "name": "signer", + "type": "object", + "label": "Signer", + "required": true, + "description": "PlatformAddressSigner instance" + } ], "sdk_method": "addresses.withdraw" }, @@ -2635,9 +2706,27 @@ } ], "sdk_params": [ - {"name": "identityId", "type": "string", "label": "Identity ID", "required": true, "description": "Base58 identity ID to transfer from"}, - {"name": "outputs", "type": "array", "label": "Outputs", "required": true, "description": "Array of {address, amount (credits)} objects for recipient addresses"}, - {"name": "signer", "type": "object", "label": "Signer", "required": true, "description": "IdentitySigner instance"} + { + "name": "identityId", + "type": "string", + "label": "Identity ID", + "required": true, + "description": "Base58 identity ID to transfer from" + }, + { + "name": "outputs", + "type": "array", + "label": "Outputs", + "required": true, + "description": "Array of {address, amount (credits)} objects for recipient addresses" + }, + { + "name": "signer", + "type": "object", + "label": "Signer", + "required": true, + "description": "IdentitySigner instance" + } ], "sdk_method": "addresses.transferFromIdentity" }, @@ -2661,10 +2750,34 @@ } ], "sdk_params": [ - {"name": "assetLockProof", "type": "string", "label": "Asset Lock Proof", "required": true, "description": "Hex-encoded asset lock proof"}, - {"name": "assetLockPrivateKey", "type": "string", "label": "Asset Lock Private Key", "required": true, "description": "WIF private key for asset lock"}, - {"name": "outputs", "type": "array", "label": "Outputs", "required": true, "description": "Array of {address, amount (credits)} objects"}, - {"name": "signer", "type": "object", "label": "Signer", "required": false, "description": "Optional PlatformAddressSigner instance"} + { + "name": "assetLockProof", + "type": "string", + "label": "Asset Lock Proof", + "required": true, + "description": "Hex-encoded asset lock proof" + }, + { + "name": "assetLockPrivateKey", + "type": "string", + "label": "Asset Lock Private Key", + "required": true, + "description": "WIF private key for asset lock" + }, + { + "name": "outputs", + "type": "array", + "label": "Outputs", + "required": true, + "description": "Array of {address, amount (credits)} objects" + }, + { + "name": "signer", + "type": "object", + "label": "Signer", + "required": false, + "description": "Optional PlatformAddressSigner instance" + } ], "sdk_method": "addresses.fundFromAssetLock" }, @@ -2702,10 +2815,34 @@ } ], "sdk_params": [ - {"name": "identity", "type": "object", "label": "Identity", "required": true, "description": "Identity object with public keys"}, - {"name": "inputs", "type": "array", "label": "Inputs", "required": true, "description": "Array of {address, nonce, amount (credits)} objects"}, - {"name": "identitySigner", "type": "object", "label": "Identity Signer", "required": true, "description": "IdentitySigner for signing identity keys"}, - {"name": "addressSigner", "type": "object", "label": "Address Signer", "required": true, "description": "PlatformAddressSigner instance"} + { + "name": "identity", + "type": "object", + "label": "Identity", + "required": true, + "description": "Identity object with public keys" + }, + { + "name": "inputs", + "type": "array", + "label": "Inputs", + "required": true, + "description": "Array of {address, nonce, amount (credits)} objects" + }, + { + "name": "identitySigner", + "type": "object", + "label": "Identity Signer", + "required": true, + "description": "IdentitySigner for signing identity keys" + }, + { + "name": "addressSigner", + "type": "object", + "label": "Address Signer", + "required": true, + "description": "PlatformAddressSigner instance" + } ], "sdk_method": "addresses.createIdentity" } diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index 89b1cf8..c1ffe7e 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -482,44 +482,764 @@ def evo_example_for_query(key: str, inputs: List[dict]): def evo_example_for_transition(key: str): + """Return practical v4 state-transition examples matching shipped Options interfaces. + + Examples build typed payload objects, select identity keys, and construct + IdentitySigner / asset-lock PrivateKey instances rather than the pre-v4 + privateKeyWif-in-call shape. + """ + identity = TESTNET_TEST_DATA['identity_id'] + contract = TESTNET_TEST_DATA['data_contract_id'] + document_id = TESTNET_TEST_DATA['document_id'] + document_type = TESTNET_TEST_DATA['document_type'] + pro_tx = TESTNET_TEST_DATA['pro_tx_hash'] + platform_address = TESTNET_TEST_DATA['platform_address'] + m = { # Identities - # 'identityCreate' - example in api-definitions.json - # 'identityTopUp' - example in api-definitions.json - 'identityCreditTransfer': "await client.identities.creditTransfer({ senderId, recipientId, amount, privateKeyWif, keyId })", - 'identityCreditWithdrawal': "await client.identities.creditWithdrawal({ identityId, toAddress, amount, coreFeePerByte, privateKeyWif, keyId })", - 'identityUpdate': "await client.identities.update({ identityId, addPublicKeys, disablePublicKeyIds, privateKeyWif })", - 'dataContractCreate': "await client.contracts.publish({ ownerId, definition, privateKeyWif, keyId })", - 'dataContractUpdate': "await client.contracts.update({ contractId, ownerId, updates, privateKeyWif, keyId })", + 'identityCreate': example(f""" + import {{ + AssetLockProof, + Identity, + IdentityPublicKeyInCreation, + IdentitySigner, + KeyType, + PrivateKey, + Purpose, + SecurityLevel, + }} from '@dashevo/evo-sdk'; + + // Asset lock from Core (hex) + the private key that controls that output. + const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); + const assetLockPrivateKey = PrivateKey.fromWIF('cVExampleAssetLockKeyForIdentityFunding'); + + // Build the identity shell and attach public keys that will be registered. + const identity = new Identity('random-or-derived-identity-id'); + const masterKey = new IdentityPublicKeyInCreation({{ + keyId: 0, + purpose: Purpose.AUTHENTICATION, + securityLevel: SecurityLevel.MASTER, + keyType: KeyType.ECDSA_SECP256K1, + data: Uint8Array.from(atob('A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ'), c => c.charCodeAt(0)), + }}).toIdentityPublicKey(); + identity.addPublicKey(masterKey); + + // IdentitySigner holds private keys for proving ownership of identity keys. + // Keep this separate from the asset-lock key. + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + + await sdk.identities.create({{ + identity, + assetLockProof, + assetLockPrivateKey, + signer, + }}); + """), + 'identityTopUp': example(f""" + import {{ AssetLockProof, PrivateKey }} from '@dashevo/evo-sdk'; + + const identity = await sdk.identities.fetch('{identity}'); + const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); + // Asset-lock signing only — top-up does not use IdentitySigner. + const assetLockPrivateKey = PrivateKey.fromWIF('cVExampleAssetLockKeyForIdentityFunding'); + + const newBalance = await sdk.identities.topUp({{ + identity, + assetLockProof, + assetLockPrivateKey, + }}); + """), + 'identityCreditTransfer': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const identity = await sdk.identities.fetch('{identity}'); + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + // Optional: pass signingKey when you need a specific transfer/auth key. + const signingKey = identity.getPublicKeyById(3) // TRANSFER key when present + || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); + + await sdk.identities.creditTransfer({{ + identity, + recipientId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + amount: 1000000n, + signer, + signingKey, + }}); + """), + 'identityCreditWithdrawal': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const identity = await sdk.identities.fetch('{identity}'); + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const signingKey = identity.getPublicKeyById(3) + || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); + + const remainingBalance = await sdk.identities.creditWithdrawal({{ + identity, + amount: 1000000n, + toAddress: 'yT8DDY5NkX4Zt44Fy8QjmCekheJQH4EMkv', + coreFeePerByte: 1, + signer, + signingKey, + }}); + """), + 'identityUpdate': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const identity = await sdk.identities.fetch('{identity}'); + const signer = new IdentitySigner(); + // Master key is required to add/disable identity keys. + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + + await sdk.identities.update({{ + identity, + addPublicKeys: undefined, // optional IdentityPublicKeyInCreation[] + disablePublicKeys: [2], // optional key ids to disable + signer, + }}); + """), + + # Data contracts + 'dataContractCreate': example(f""" + import {{ DataContract, IdentitySigner }} from '@dashevo/evo-sdk'; + + const ownerId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + + const keys = await sdk.identities.getKeys({{ + identityId: ownerId, + request: {{ type: 'all' }}, + }}); + // Contract publish requires a CRITICAL authentication key. + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'CRITICAL', + ); + + const identityNonce = (await sdk.identities.nonce(ownerId)) ?? 0n; + const dataContract = new DataContract({{ + ownerId, + identityNonce: identityNonce + 1n, + schemas: {{ + note: {{ + type: 'object', + properties: {{ + message: {{ type: 'string', maxLength: 200, position: 0 }}, + }}, + required: ['message'], + additionalProperties: false, + }}, + }}, + }}); + + const published = await sdk.contracts.publish({{ + dataContract, + identityKey, + signer, + }}); + """), + 'dataContractUpdate': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const ownerId = '{identity}'; + const contractId = '{contract}'; + const dataContract = await sdk.contracts.fetch(contractId); + dataContract.version = (dataContract.version || 1) + 1; + // Optionally merge additional document type schemas before update: + // dataContract.setSchemas(mergedSchemas, undefined, false, sdk.version()); + + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ + identityId: ownerId, + request: {{ type: 'all' }}, + }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'CRITICAL', + ); + + await sdk.contracts.update({{ dataContract, identityKey, signer }}); + """), + # Documents - 'documentCreate': "await client.documents.create({ contractId, type: documentType, ownerId, data, entropyHex, privateKeyWif })", - 'documentReplace': "await client.documents.replace({ contractId, type: documentType, documentId, ownerId, data, revision, privateKeyWif })", - 'documentDelete': "await client.documents.delete({ contractId, type: documentType, documentId, ownerId, privateKeyWif })", - 'documentTransfer': "await client.documents.transfer({ contractId, type: documentType, documentId, ownerId, recipientId, privateKeyWif })", - 'documentPurchase': "await client.documents.purchase({ contractId, type: documentType, documentId, buyerId, price, privateKeyWif })", - 'documentSetPrice': "await client.documents.setPrice({ contractId, type: documentType, documentId, ownerId, price, privateKeyWif })", + 'documentCreate': example(f""" + import {{ Document, IdentitySigner }} from '@dashevo/evo-sdk'; + + const ownerId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + + const keys = await sdk.identities.getKeys({{ + identityId: ownerId, + request: {{ type: 'all' }}, + }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + const document = new Document({{ + dataContractId: '{contract}', + documentTypeName: '{document_type}', + ownerId, + properties: {{ /* fields required by the document type schema */ }}, + }}); + + await sdk.documents.create({{ document, identityKey, signer }}); + """), + 'documentReplace': example(f""" + import {{ Document, IdentitySigner }} from '@dashevo/evo-sdk'; + + const ownerId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ + identityId: ownerId, + request: {{ type: 'all' }}, + }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + // Fetch current document, then rebuild/replace with revision + 1. + const current = await sdk.documents.get('{contract}', '{document_type}', '{document_id}'); + const document = new Document({{ + dataContractId: '{contract}', + documentTypeName: '{document_type}', + ownerId, + id: '{document_id}', + revision: BigInt(current.revision) + 1n, + properties: {{ /* updated properties */ }}, + }}); + + await sdk.documents.replace({{ document, identityKey, signer }}); + """), + 'documentDelete': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const ownerId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ + identityId: ownerId, + request: {{ type: 'all' }}, + }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + await sdk.documents.delete({{ + document: {{ + id: '{document_id}', + ownerId, + dataContractId: '{contract}', + documentTypeName: '{document_type}', + }}, + identityKey, + signer, + }}); + """), + 'documentTransfer': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const ownerId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ + identityId: ownerId, + request: {{ type: 'all' }}, + }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + const document = await sdk.documents.get('{contract}', '{document_type}', '{document_id}'); + document.revision = BigInt(document.revision) + 1n; + + await sdk.documents.transfer({{ + document, + recipientId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + identityKey, + signer, + }}); + """), + 'documentPurchase': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const buyerId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ + identityId: buyerId, + request: {{ type: 'all' }}, + }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + const document = await sdk.documents.get('{contract}', '{document_type}', '{document_id}'); + document.revision = BigInt(document.revision) + 1n; + + await sdk.documents.purchase({{ + document, + buyerId, + price: 1000n, + identityKey, + signer, + }}); + """), + 'documentSetPrice': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const ownerId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ + identityId: ownerId, + request: {{ type: 'all' }}, + }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + const document = await sdk.documents.get('{contract}', '{document_type}', '{document_id}'); + document.revision = BigInt(document.revision) + 1n; + + await sdk.documents.setPrice({{ + document, + price: 1000n, + identityKey, + signer, + }}); + """), + # Tokens - 'tokenMint': "await client.tokens.mint({ contractId, tokenPosition, amount, identityId, privateKeyWif, recipientId, publicNote })", - 'tokenBurn': "await client.tokens.burn({ contractId, tokenPosition, amount, identityId, privateKeyWif, publicNote })", - 'tokenTransfer': "await client.tokens.transfer({ contractId, tokenPosition, amount, senderId, recipientId, privateKeyWif, publicNote })", - 'tokenFreeze': "await client.tokens.freeze({ contractId, tokenPosition, identityToFreeze, identityId, privateKeyWif, publicNote })", - 'tokenUnfreeze': "await client.tokens.unfreeze({ contractId, tokenPosition, identityToUnfreeze, identityId, privateKeyWif, publicNote })", - 'tokenDestroyFrozen': "await client.tokens.destroyFrozen({ contractId, tokenPosition, frozenIdentityId, identityId, privateKeyWif, publicNote })", - 'tokenSetPriceForDirectPurchase': "await client.tokens.setPrice({ contractId, tokenPosition, identityId, priceType, priceData, privateKeyWif, publicNote })", - 'tokenDirectPurchase': "await client.tokens.directPurchase({ contractId, tokenPosition, amount, identityId, totalAgreedPrice, privateKeyWif })", - 'tokenClaim': "await client.tokens.claim({ contractId, tokenPosition, distributionType, identityId, privateKeyWif, publicNote })", - 'tokenEmergencyAction': "await client.tokens.emergencyAction({ contractId, tokenPosition, actionType, identityId, privateKeyWif, publicNote })", - # Voting - 'dpnsUsername': "await client.voting.masternodeVote({ masternodeProTxHash, contractId, documentTypeName, indexName, indexValues, voteChoice, votingKeyWif })", - 'masternodeVote': "await client.voting.masternodeVote({ masternodeProTxHash, contractId, documentTypeName, indexName, indexValues, voteChoice, votingKeyWif })", - 'dpnsRegister': "await client.dpns.registerName({ label, identityId, publicKeyId, privateKeyWif, onPreorder })", - # Platform Addresses - 'addressTransfer': "await client.addresses.transfer({ inputs, outputs, signer })", - 'addressTopUpIdentity': "await client.addresses.topUpIdentity({ identityId, inputs, signer })", - 'addressWithdraw': "await client.addresses.withdraw({ inputs, coreFeePerByte, pooling, outputScript, signer })", - 'addressTransferFromIdentity': "await client.addresses.transferFromIdentity({ identityId, outputs, signer })", - 'addressFundFromAssetLock': "await client.addresses.fundFromAssetLock({ assetLockProof, assetLockPrivateKey, outputs, signer })", - 'addressCreateIdentity': "await client.addresses.createIdentity({ identity, inputs, identitySigner, addressSigner })", + 'tokenMint': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const identityId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ identityId, request: {{ type: 'all' }} }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + const result = await sdk.tokens.mint({{ + dataContractId: '{contract}', + tokenPosition: 0, + amount: 100n, + identityId, + recipientId: identityId, + publicNote: 'mint', + identityKey, + signer, + }}); + """), + 'tokenBurn': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const identityId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ identityId, request: {{ type: 'all' }} }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + const result = await sdk.tokens.burn({{ + dataContractId: '{contract}', + tokenPosition: 0, + amount: 10n, + identityId, + publicNote: 'burn', + identityKey, + signer, + }}); + """), + 'tokenTransfer': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const senderId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ identityId: senderId, request: {{ type: 'all' }} }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + const result = await sdk.tokens.transfer({{ + dataContractId: '{contract}', + tokenPosition: 0, + amount: 5n, + senderId, + recipientId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + publicNote: 'transfer', + identityKey, + signer, + }}); + """), + 'tokenFreeze': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const authorityId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ identityId: authorityId, request: {{ type: 'all' }} }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + await sdk.tokens.freeze({{ + dataContractId: '{contract}', + tokenPosition: 0, + authorityId, + frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + publicNote: 'freeze', + identityKey, + signer, + }}); + """), + 'tokenUnfreeze': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const authorityId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ identityId: authorityId, request: {{ type: 'all' }} }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + await sdk.tokens.unfreeze({{ + dataContractId: '{contract}', + tokenPosition: 0, + authorityId, + frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + publicNote: 'unfreeze', + identityKey, + signer, + }}); + """), + 'tokenDestroyFrozen': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const authorityId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ identityId: authorityId, request: {{ type: 'all' }} }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + await sdk.tokens.destroyFrozen({{ + dataContractId: '{contract}', + tokenPosition: 0, + authorityId, + frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + publicNote: 'destroy', + identityKey, + signer, + }}); + """), + 'tokenSetPriceForDirectPurchase': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const authorityId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ identityId: authorityId, request: {{ type: 'all' }} }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + await sdk.tokens.setPrice({{ + dataContractId: '{contract}', + tokenPosition: 0, + authorityId, + price: 1000n, // or null to clear + publicNote: 'set price', + identityKey, + signer, + }}); + """), + 'tokenDirectPurchase': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const buyerId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ identityId: buyerId, request: {{ type: 'all' }} }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + const result = await sdk.tokens.directPurchase({{ + dataContractId: '{contract}', + tokenPosition: 0, + buyerId, + amount: 10n, + maxTotalCost: 10000n, + identityKey, + signer, + }}); + """), + 'tokenClaim': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const identityId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ identityId, request: {{ type: 'all' }} }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + const result = await sdk.tokens.claim({{ + dataContractId: '{contract}', + tokenPosition: 0, + identityId, + distributionType: 'perpetual', // or 'preProgrammed' + publicNote: 'claim', + identityKey, + signer, + }}); + """), + 'tokenEmergencyAction': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const authorityId = '{identity}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({{ identityId: authorityId, request: {{ type: 'all' }} }}); + const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), + ); + + await sdk.tokens.emergencyAction({{ + dataContractId: '{contract}', + tokenPosition: 0, + authorityId, + action: 'pause', // or 'resume' + publicNote: 'pause trading', + identityKey, + signer, + }}); + """), + + # DPNS / voting + 'dpnsRegister': example(f""" + import {{ IdentitySigner }} from '@dashevo/evo-sdk'; + + const identityId = '{identity}'; + const identity = await sdk.identities.fetch(identityId); + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + // HIGH authentication key is typically used for DPNS registration. + const identityKey = identity.getPublicKeyById(1) + || identity.publicKeys.find( + k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'HIGH', + ); + + const result = await sdk.dpns.registerName({{ + label: 'alice', + identity, + identityKey, + signer, + preorderCallback: (preorderDocument) => {{ + console.log('preorder submitted', preorderDocument.id?.toString?.()); + }}, + }}); + """), + 'dpnsUsername': example(f""" + import {{ IdentitySigner, ResourceVoteChoice, VotePoll }} from '@dashevo/evo-sdk'; + + const masternodeProTxHash = '{pro_tx}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExampleVotingKeyWifGoesHere'); + // Voting key must match the masternode voting public key on the identity. + const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); + const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING') + || votingIdentity.getPublicKeyById(0); + + const votePoll = new VotePoll({{ + contractId: '{contract}', + documentTypeName: 'domain', + indexName: 'parentNameAndLabel', + indexValues: ['dash', 'alice'], + }}); + + await sdk.voting.masternodeVote({{ + masternodeProTxHash, + votePoll, + voteChoice: ResourceVoteChoice.TowardsIdentity('{identity}'), + votingKey, + signer, + }}); + """), + 'masternodeVote': example(f""" + import {{ IdentitySigner, ResourceVoteChoice, VotePoll }} from '@dashevo/evo-sdk'; + + const masternodeProTxHash = '{pro_tx}'; + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExampleVotingKeyWifGoesHere'); + const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); + const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING') + || votingIdentity.getPublicKeyById(0); + + const votePoll = new VotePoll({{ + contractId: '{contract}', + documentTypeName: 'domain', + indexName: 'parentNameAndLabel', + indexValues: ['dash', 'alice'], + }}); + + await sdk.voting.masternodeVote({{ + masternodeProTxHash, + votePoll, + voteChoice: ResourceVoteChoice.TowardsIdentity('{identity}'), + votingKey, + signer, + }}); + """), + + # Platform Addresses — keep address-signer vs identity-signer distinction + 'addressTransfer': example(f""" + import {{ + PlatformAddressInput, + PlatformAddressOutput, + PlatformAddressSigner, + PrivateKey, + }} from '@dashevo/evo-sdk'; + + const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); + const signer = new PlatformAddressSigner(); + const senderAddr = signer.addKey(privateKey); // derives P2PKH platform address + + const input = new PlatformAddressInput(senderAddr, 0n, 100000n); + const output = new PlatformAddressOutput( + /* recipient PlatformAddress or bech32m */ '{platform_address}', + 90000n, + ); + + const result = await sdk.addresses.transfer({{ + inputs: [input], + outputs: [output], + signer, + }}); + """), + 'addressTopUpIdentity': example(f""" + import {{ PlatformAddressInput, PlatformAddressSigner, PrivateKey }} from '@dashevo/evo-sdk'; + + const identity = await sdk.identities.fetch('{identity}'); + const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); + const signer = new PlatformAddressSigner(); + const sourceAddr = signer.addKey(privateKey); + const input = new PlatformAddressInput(sourceAddr, 0n, 50000n); + + const result = await sdk.addresses.topUpIdentity({{ + identityId: identity.id, + inputs: [input], + signer, + }}); + """), + 'addressWithdraw': example(f""" + import {{ PlatformAddressInput, PlatformAddressSigner, PrivateKey }} from '@dashevo/evo-sdk'; + + const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); + const signer = new PlatformAddressSigner(); + const platformAddr = signer.addKey(privateKey); + const input = new PlatformAddressInput(platformAddr, 0n, 100000n); + + // Provide a Core L1 output script (e.g. CoreScript.newP2PKH(...)). + const result = await sdk.addresses.withdraw({{ + inputs: [input], + coreFeePerByte: 1, + pooling: undefined, + outputScript: /* CoreScript */ undefined, + signer, + }}); + """), + 'addressTransferFromIdentity': example(f""" + import {{ IdentitySigner, PlatformAddressOutput }} from '@dashevo/evo-sdk'; + + const identityId = '{identity}'; + // Uses IdentitySigner (identity transfer key), not PlatformAddressSigner. + const signer = new IdentitySigner(); + signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + + const output = new PlatformAddressOutput('{platform_address}', 100000n); + const result = await sdk.addresses.transferFromIdentity({{ + identityId, + outputs: [output], + signer, + }}); + """), + 'addressFundFromAssetLock': example(f""" + import {{ + AssetLockProof, + PlatformAddressOutput, + PlatformAddressSigner, + PrivateKey, + }} from '@dashevo/evo-sdk'; + + // Asset-lock key signs the L1 funding proof; address signer controls outputs. + const assetLockPrivateKey = PrivateKey.fromWIF('cAssetLockPrivateKeyWif...'); + const addressPrivateKey = PrivateKey.fromWIF('cAddressPrivateKeyWif...'); + const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); + + const signer = new PlatformAddressSigner(); + const platformAddr = signer.addKey(addressPrivateKey); + const output = new PlatformAddressOutput(platformAddr, 100000n); + + const result = await sdk.addresses.fundFromAssetLock({{ + assetLockProof, + assetLockPrivateKey, + outputs: [output], + signer, + }}); + """), + 'addressCreateIdentity': example(f""" + import {{ + Identity, + IdentityPublicKeyInCreation, + IdentitySigner, + Identifier, + KeyType, + PlatformAddressSigner, + PrivateKey, + Purpose, + SecurityLevel, + }} from '@dashevo/evo-sdk'; + + const addressPrivateKey = PrivateKey.fromWIF('cAddressPrivateKeyWif...'); + const identityPrivateKey = PrivateKey.fromWIF('cIdentityKeyWif...'); + + const identity = new Identity(Identifier.random()); + identity.addPublicKey( + new IdentityPublicKeyInCreation({{ + keyId: 0, + purpose: Purpose.AUTHENTICATION, + securityLevel: SecurityLevel.MASTER, + keyType: KeyType.ECDSA_SECP256K1, + data: identityPrivateKey.getPublicKey().toBuffer(), + }}).toIdentityPublicKey(), + ); + + const addressSigner = new PlatformAddressSigner(); + const sourceAddr = addressSigner.addKey(addressPrivateKey); + const identitySigner = new IdentitySigner(); + identitySigner.addKey(identityPrivateKey); + + const result = await sdk.addresses.createIdentity({{ + identity, + inputs: [{{ address: sourceAddr, amount: 50_000_000_000n }}], + identitySigner, + addressSigner, + }}); + """), } return m.get(key) @@ -584,21 +1304,26 @@ def format_example(code: str, header: str) -> str: if not formatted: return header - body_lines = formatted.split('\n') - if body_lines: - first_line = body_lines[0].lstrip() - if first_line and not first_line.startswith('return'): - body_lines[0] = f'return {first_line}' + body_lines = [re.sub(r'\bclient\b', 'sdk', line) for line in formatted.split('\n')] - def replace_client(line: str) -> str: - return line.replace('client', 'sdk') + # Only auto-prefix `return` for single-expression snippets. Multi-line + # examples already contain setup + an explicit final call/return. + code_line_indexes = [ + index + for index, line in enumerate(body_lines) + if line.strip() and not line.strip().startswith('//') + ] + if len(code_line_indexes) == 1: + index = code_line_indexes[0] + stripped = body_lines[index].lstrip() + indent = body_lines[index][: len(body_lines[index]) - len(stripped)] + if not stripped.startswith('return '): + body_lines[index] = f'{indent}return {stripped}' - processed = [replace_client(line) for line in body_lines] lines = [header] - lines.extend(processed) + lines.extend(body_lines) return '\n'.join(lines).rstrip() - def render_operation( prefix: str, item_key: str, @@ -1240,28 +1965,29 @@ def format_ai_example_block(code: str | None, item_key: str) -> str: return f"// Example currently unavailable for `{item_key}`" raw_lines = snippet.split('\n') - processed = [line.replace('client', 'sdk') for line in raw_lines] + processed = [re.sub(r'\bclient\b', 'sdk', line) for line in raw_lines] - first_index = 0 - while first_index < len(processed) and processed[first_index].strip().startswith('//'): - first_index += 1 - - if first_index >= len(processed): - result_only = '\n'.join(processed).rstrip() - if not result_only.endswith(';'): - result_only += ';' - return result_only + # Multi-line examples already declare imports/setup and a final call — leave intact. + code_line_indexes = [ + index + for index, line in enumerate(processed) + if line.strip() and not line.strip().startswith('//') + ] + if len(code_line_indexes) != 1: + return '\n'.join(processed).rstrip() + first_index = code_line_indexes[0] first_line = processed[first_index] stripped = first_line.lstrip() indent = first_line[: len(first_line) - len(stripped)] - # Check if the example already contains variable declarations or const result already_has_declaration = ( - stripped.startswith('const ') or - stripped.startswith('let ') or - stripped.startswith('var ') or - 'const result' in snippet + stripped.startswith('const ') + or stripped.startswith('let ') + or stripped.startswith('var ') + or stripped.startswith('import ') + or stripped.startswith('await ') + or 'const result' in snippet ) if not already_has_declaration: @@ -1270,7 +1996,7 @@ def format_ai_example_block(code: str | None, item_key: str) -> str: processed[first_index] = f"{indent}const result = {stripped}" joined = '\n'.join(processed).rstrip() - if not joined.endswith(';'): + if joined and not joined.endswith(';'): joined += ';' return joined @@ -1302,12 +2028,23 @@ def generate_ai_reference_md(query_defs: dict, transition_defs: dict, type_metad '```', '', '## Authentication', - 'Most state transitions require an identity identifier and a signing key in Wallet Import ' - 'Format (WIF). Keep credentials secure and never embed production keys in source control:', + 'State transitions authenticate with typed objects, not a `privateKeyWif` field on the call:', + '- Identity writes: fetch/build the payload, select an `IdentityPublicKey`, and sign with `IdentitySigner`.', + '- Asset-lock writes (identity create/top-up, fund-from-asset-lock): use `AssetLockProof` + `PrivateKey` ' + 'for the L1 lock; identity create also needs a separate `IdentitySigner` for key proofs.', + '- Platform address writes: use `PlatformAddressSigner` for address inputs/outputs; ' + 'identity-funded address ops use `IdentitySigner`.', + 'Keep credentials secure and never embed production keys in source control:', '```javascript', + "import { IdentitySigner, PrivateKey } from '@dashevo/evo-sdk';", + '', f"const identityId = '{identity_sample}';", "const privateKeyWif = 'L1ExamplePrivateKeyWifGoesHere';", "const assetLockPrivateKeyWif = 'cVExampleAssetLockKeyForIdentityFunding';", + '', + 'const signer = new IdentitySigner();', + 'signer.addKeyFromWif(privateKeyWif);', + 'const assetLockPrivateKey = PrivateKey.fromWIF(assetLockPrivateKeyWif);', '```', '', '## Query Operations', @@ -1402,10 +2139,21 @@ def append_query_sections() -> None: '## State Transition Operations', '', '### Pattern', - 'All state transitions require authentication and are invoked with namespace methods:', + 'State transitions take a typed options object. Typical identity-signed writes look like:', '```javascript', - 'const result = await sdk..({ ...params, privateKeyWif });', + 'const identity = await sdk.identities.fetch(identityId);', + 'const signer = new IdentitySigner();', + 'signer.addKeyFromWif(privateKeyWif);', + 'const identityKey = identity.getPublicKeyById(keyId);', + '', + 'const result = await sdk..({', + ' /* payload fields: identity / document / dataContract / ... */', + ' identityKey, // when required by the method', + ' signer,', + '});', '```', + 'Asset-lock methods take `assetLockProof` + `assetLockPrivateKey` instead of (or in addition to) ' + 'an `IdentitySigner`. See each operation example below.', '', '### Available State Transitions', ]) From 15403cd683d80742976071fb87b0958d3904f1b6 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 14:38:51 -0500 Subject: [PATCH 02/22] docs: regenerate AI_REFERENCE with v4 state transition examples Apply the generator fixes for invented address/identifier helpers and regenerate the committed AI_REFERENCE examples so they match the v4 payload + signer option shapes. Co-Authored-By: Claude --- public/AI_REFERENCE.md | 864 +++++++++++++++++++++++++++++++++------ scripts/generate_docs.py | 21 +- 2 files changed, 754 insertions(+), 131 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index 6c0cb49..66a5b01 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -18,11 +18,21 @@ await sdk.connect(); ``` ## Authentication -Most state transitions require an identity identifier and a signing key in Wallet Import Format (WIF). Keep credentials secure and never embed production keys in source control: +State transitions authenticate with typed objects, not a `privateKeyWif` field on the call: +- Identity writes: fetch/build the payload, select an `IdentityPublicKey`, and sign with `IdentitySigner`. +- Asset-lock writes (identity create/top-up, fund-from-asset-lock): use `AssetLockProof` + `PrivateKey` for the L1 lock; identity create also needs a separate `IdentitySigner` for key proofs. +- Platform address writes: use `PlatformAddressSigner` for address inputs/outputs; identity-funded address ops use `IdentitySigner`. +Keep credentials secure and never embed production keys in source control: ```javascript +import { IdentitySigner, PrivateKey } from '@dashevo/evo-sdk'; + const identityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; const privateKeyWif = 'L1ExamplePrivateKeyWifGoesHere'; const assetLockPrivateKeyWif = 'cVExampleAssetLockKeyForIdentityFunding'; + +const signer = new IdentitySigner(); +signer.addKeyFromWif(privateKeyWif); +const assetLockPrivateKey = PrivateKey.fromWIF(assetLockPrivateKeyWif); ``` ## Query Operations @@ -97,12 +107,12 @@ Returns: Example: ```javascript -const result = await sdk.identities.getKeys({ +return await sdk.identities.getKeys({ identityId: '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', request: { type: 'all' }, limit: 10, offset: 0 -}); +}) ``` **Get Contract Keys for Identities** - `identities.contractKeys` @@ -125,10 +135,10 @@ Returns: Example: ```javascript -const result = await sdk.identities.contractKeys({ +return await sdk.identities.contractKeys({ identityIds: ['5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'], contractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec' -}); +}) ``` **Get Identity Nonce** - `identities.nonce` @@ -366,11 +376,11 @@ Returns: Example: ```javascript -const result = await sdk.contracts.getHistory({ +return await sdk.contracts.getHistory({ dataContractId: 'HLY575cNazmc5824FxqaEMEBuzFeE4a98GDRNKbyJqCM', limit: 10, startAtMs: 0 -}); +}) ``` **Get Data Contracts** - `contracts.getMany` @@ -387,10 +397,10 @@ Returns: Example: ```javascript -const result = await sdk.contracts.getMany([ +return await sdk.contracts.getMany([ 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A' -]); +]) ``` #### Document Queries @@ -424,13 +434,13 @@ Returns: Example: ```javascript -const result = await sdk.documents.query({ +return await sdk.documents.query({ dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', documentTypeName: 'domain', where: [["normalizedParentDomainName", "==", "dash"]], orderBy: [["normalizedLabel", "asc"]], limit: 10 -}); +}) ``` **Get Document** - `documents.get` @@ -453,11 +463,11 @@ Returns: Example: ```javascript -const result = await sdk.documents.get( +return await sdk.documents.get( 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', 'domain', '7NYmEKQsYtniQRUmxwdPGeVcirMoPh5ZPyAKz8BWFy3r' -); +) ``` #### DPNS Queries @@ -624,14 +634,14 @@ Returns: Example: ```javascript -const result = await sdk.group.contestedResources({ +return await sdk.group.contestedResources({ dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', documentTypeName: 'domain', indexName: 'parentNameAndLabel', startAtValue: null, limit: 10, orderAscending: true -}); +}) ``` **Get Contested Resource Vote State** - `voting.contestedResourceVoteState` @@ -669,7 +679,7 @@ Returns: Example: ```javascript -const result = await sdk.voting.contestedResourceVoteState({ +return await sdk.voting.contestedResourceVoteState({ dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', documentTypeName: 'domain', indexName: 'parentNameAndLabel', @@ -677,7 +687,7 @@ const result = await sdk.voting.contestedResourceVoteState({ resultType: 'documents', limit: 10, orderAscending: true -}); +}) ``` **Get Voters for Identity** - `group.contestedResourceVotersForIdentity` @@ -713,7 +723,7 @@ Returns: Example: ```javascript -const result = await sdk.group.contestedResourceVotersForIdentity({ +return await sdk.group.contestedResourceVotersForIdentity({ dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', documentTypeName: 'domain', indexName: 'parentNameAndLabel', @@ -721,7 +731,7 @@ const result = await sdk.group.contestedResourceVotersForIdentity({ contestantId: '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', limit: 10, orderAscending: true -}); +}) ``` **Get Identity Votes** - `voting.contestedResourceIdentityVotes` @@ -746,11 +756,11 @@ Returns: Example: ```javascript -const result = await sdk.voting.contestedResourceIdentityVotes({ +return await sdk.voting.contestedResourceIdentityVotes({ identityId: '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', limit: 10, orderAscending: true -}); +}) ``` **Get Vote Polls by End Date** - `voting.votePollsByEndDate` @@ -778,12 +788,12 @@ Returns: Example: ```javascript -const result = await sdk.voting.votePollsByEndDate({ +return await sdk.voting.votePollsByEndDate({ startTimeMs: null, endTimeMs: null, limit: 10, orderAscending: true, -}); +}) ``` #### Protocol & Version @@ -841,11 +851,11 @@ Returns: Example: ```javascript -const result = await sdk.epoch.epochsInfo({ +return await sdk.epoch.epochsInfo({ startEpoch: 8635, count: 5, ascending: true -}); +}) ``` **Get Current Epoch** - `epoch.current` @@ -880,11 +890,11 @@ Returns: Example: ```javascript -const result = await sdk.epoch.finalizedInfos({ +return await sdk.epoch.finalizedInfos({ startEpoch: 8635, count: 5, ascending: true -}); +}) ``` **Get Epoch Blocks by Evonode IDs** - `epoch.evonodesProposedBlocksByIds` @@ -902,10 +912,10 @@ Returns: Example: ```javascript -const result = await sdk.epoch.evonodesProposedBlocksByIds( +return await sdk.epoch.evonodesProposedBlocksByIds( 8635, ['143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'] -); +) ``` **Get Epoch Blocks by Range** - `epoch.evonodesProposedBlocksByRange` @@ -927,11 +937,11 @@ Returns: Example: ```javascript -const result = await sdk.epoch.evonodesProposedBlocksByRange({ +return await sdk.epoch.evonodesProposedBlocksByRange({ epoch: 8635, limit: 5, orderAscending: true -}); +}) ``` #### Token Queries @@ -969,10 +979,10 @@ Returns: Example: ```javascript -const result = await sdk.tokens.statuses([ +return await sdk.tokens.statuses([ 'Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv', 'H7FRpZJqZK933r9CzZMsCuf1BM34NT5P2wSJyjDkprqy' -]); +]) ``` **Get Direct Purchase Prices** - `tokens.directPurchasePrices` @@ -989,9 +999,9 @@ Returns: Example: ```javascript -const result = await sdk.tokens.directPurchasePrices([ +return await sdk.tokens.directPurchasePrices([ 'Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv' -]); +]) ``` **Get Token Contract Info** - `tokens.contractInfo` @@ -1028,10 +1038,10 @@ Returns: Example: ```javascript -const result = await sdk.tokens.perpetualDistributionLastClaim( +return await sdk.tokens.perpetualDistributionLastClaim( '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', 'Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv' -); +) ``` **Get Token Total Supply** - `tokens.totalSupply` @@ -1369,10 +1379,20 @@ const result = await sdk.addresses.getMany(['tdash1krt0z5hrcaphyuraxmk2h2ff8nyv5 ## State Transition Operations ### Pattern -All state transitions require authentication and are invoked with namespace methods: +State transitions take a typed options object. Typical identity-signed writes look like: ```javascript -const result = await sdk..({ ...params, privateKeyWif }); +const identity = await sdk.identities.fetch(identityId); +const signer = new IdentitySigner(); +signer.addKeyFromWif(privateKeyWif); +const identityKey = identity.getPublicKeyById(keyId); + +const result = await sdk..({ + /* payload fields: identity / document / dataContract / ... */ + identityKey, // when required by the method + signer, +}); ``` +Asset-lock methods take `assetLockProof` + `assetLockPrivateKey` instead of (or in addition to) an `IdentitySigner`. See each operation example below. ### Available State Transitions #### Identity Transitions @@ -1381,14 +1401,17 @@ const result = await sdk..({ ...params, privateKeyWif }); *Create a new identity with initial credits* Parameters: -- `Asset Lock Proof` (string, required) - - Hex-encoded JSON asset lock proof +- `Identity` (Identity, required) + - Identity object with public keys attached (new Identity(...) + addPublicKey / IdentityPublicKeyInCreation). + +- `Asset Lock Proof` (AssetLockProof, required) + - AssetLockProof from Core (AssetLockProof.fromHex / createInstantAssetLockProof / createChainAssetLockProof). -- `Asset Lock Private Key (WIF)` (string, required) - - WIF format private key +- `Asset Lock Private Key` (PrivateKey, required) + - PrivateKey controlling the asset-lock output (PrivateKey.fromWIF). Separate from the IdentitySigner. -- `Public Keys` (string, required) - - JSON array of public keys. Key requirements: ECDSA_SECP256K1 requires privateKeyHex or privateKeyWif for signing, BLS12_381 requires privateKeyHex for signing, ECDSA_HASH160 requires either the data field (base64-encoded 20-byte public key hash) or privateKeyHex (produces empty signatures). +- `Identity Signer` (IdentitySigner, required) + - IdentitySigner holding private keys that prove ownership of the identity public keys being registered. Returns: @@ -1396,56 +1419,57 @@ Returns: Example: ```javascript -// Asset lock proof is a hex-encoded JSON object -const assetLockProof = "a9147d3b... (hex-encoded)"; -const assetLockPrivateKeyWif = "XFfpaSbZq52HPy3WWwe1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1"; // WIF format - -// Public keys array with proper key types -const publicKeys = JSON.stringify([ - { - id: 0, - type: 0, // ECDSA_SECP256K1 = 0, BLS12_381 = 1, ECDSA_HASH160 = 2, BIP13_SCRIPT_HASH = 3 - purpose: 0, // AUTHENTICATION = 0, ENCRYPTION = 1, DECRYPTION = 2, TRANSFER = 3, SYSTEM = 4, VOTING = 5, OWNER = 6 - securityLevel: 0, // MASTER = 0, CRITICAL = 1, HIGH = 2, MEDIUM = 3 - data: "A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ", // Base64-encoded public key - readOnly: false, - privateKeyWif: "XBrZJKcW4ajWVNAU6yP87WQog6CjFnpbqyAKgNTZRqmhYvPgMNV2" // Required for ECDSA_SECP256K1 signing - }, - { - id: 1, - type: 0, // ECDSA_SECP256K1 - purpose: 0, // AUTHENTICATION - securityLevel: 2, // HIGH - data: "AnotherBase64EncodedPublicKeyHere", // Base64-encoded public key - readOnly: false, - privateKeyWif: "XAnotherPrivateKeyInWIFFormat" // Required for signing - }, - { - id: 2, - type: 2, // ECDSA_HASH160 - purpose: 0, // AUTHENTICATION - securityLevel: 2, // HIGH - data: "ripemd160hash20bytes1234", // Base64-encoded 20-byte RIPEMD160 hash - readOnly: false - // ECDSA_HASH160 keys produce empty signatures (privateKey not required/used for signing) - } -]); - -const result = await sdk.identities.create({ assetLockProof, assetLockPrivateKeyWif, publicKeys }); +import { + AssetLockProof, + Identity, + IdentityPublicKeyInCreation, + IdentitySigner, + KeyType, + PrivateKey, + Purpose, + SecurityLevel, +} from '@dashevo/evo-sdk'; + +// Asset lock from Core (hex) + the private key that controls that output. +const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); +const assetLockPrivateKey = PrivateKey.fromWIF('cVExampleAssetLockKeyForIdentityFunding'); + +// Build the identity shell and attach public keys that will be registered. +const identity = new Identity('random-or-derived-identity-id'); +const masterKey = new IdentityPublicKeyInCreation({ + keyId: 0, + purpose: Purpose.AUTHENTICATION, + securityLevel: SecurityLevel.MASTER, + keyType: KeyType.ECDSA_SECP256K1, + data: Uint8Array.from(atob('A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ'), c => c.charCodeAt(0)), +}).toIdentityPublicKey(); +identity.addPublicKey(masterKey); + +// IdentitySigner holds private keys for proving ownership of identity keys. +// Keep this separate from the asset-lock key. +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + +await sdk.identities.create({ + identity, + assetLockProof, + assetLockPrivateKey, + signer, +}); ``` **Identity Top Up** - `identities.topUp` *Add credits to an existing identity* Parameters: -- `Identity ID` (string, required) - - Base58 format identity ID +- `Identity` (Identity, required) + - Fetched Identity object to top up (sdk.identities.fetch). -- `Asset Lock Proof` (string, required) - - Hex-encoded JSON asset lock proof +- `Asset Lock Proof` (AssetLockProof, required) + - AssetLockProof from Core funding the top-up. -- `Asset Lock Private Key (WIF)` (string, required) - - WIF format private key +- `Asset Lock Private Key` (PrivateKey, required) + - PrivateKey for the asset-lock output. Top-up does not use IdentitySigner. Returns: @@ -1453,11 +1477,18 @@ Returns: Example: ```javascript -const identityId = "5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk"; // base58 -const assetLockProof = "a9147d3b... (hex-encoded)"; -const assetLockPrivateKeyWif = "XFfpaSbZq52HPy3WWve1dXsZMiU1bQn8vQd34HNXkSZThevBWRn1"; // WIF format +import { AssetLockProof, PrivateKey } from '@dashevo/evo-sdk'; -const result = await sdk.identities.topUp({ identityId, assetLockProof, assetLockPrivateKeyWif }); +const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); +const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); +// Asset-lock signing only — top-up does not use IdentitySigner. +const assetLockPrivateKey = PrivateKey.fromWIF('cVExampleAssetLockKeyForIdentityFunding'); + +const newBalance = await sdk.identities.topUp({ + identity, + assetLockProof, + assetLockPrivateKey, +}); ``` **Identity Update** - `identities.update` @@ -1476,7 +1507,19 @@ Returns: Example: ```javascript -const result = await sdk.identities.update({ identityId, addPublicKeys, disablePublicKeyIds, privateKeyWif }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); +const signer = new IdentitySigner(); +// Master key is required to add/disable identity keys. +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + +await sdk.identities.update({ + identity, + addPublicKeys: undefined, // optional IdentityPublicKeyInCreation[] + disablePublicKeys: [2], // optional key ids to disable + signer, +}); ``` **Identity Credit Transfer** - `identities.creditTransfer` @@ -1494,7 +1537,22 @@ Returns: Example: ```javascript -const result = await sdk.identities.creditTransfer({ senderId, recipientId, amount, privateKeyWif, keyId }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +// Optional: pass signingKey when you need a specific transfer/auth key. +const signingKey = identity.getPublicKeyById(3) // TRANSFER key when present + || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); + +await sdk.identities.creditTransfer({ + identity, + recipientId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + amount: 1000000n, + signer, + signingKey, +}); ``` **Identity Credit Withdrawal** - `identities.creditWithdrawal` @@ -1513,7 +1571,22 @@ Returns: Example: ```javascript -const result = await sdk.identities.creditWithdrawal({ identityId, toAddress, amount, coreFeePerByte, privateKeyWif, keyId }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const signingKey = identity.getPublicKeyById(3) + || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); + +const remainingBalance = await sdk.identities.creditWithdrawal({ + identity, + amount: 1000000n, + toAddress: 'yT8DDY5NkX4Zt44Fy8QjmCekheJQH4EMkv', + coreFeePerByte: 1, + signer, + signingKey, +}); ``` #### Data Contract Transitions @@ -1571,7 +1644,42 @@ Returns: Example: ```javascript -const result = await sdk.contracts.publish({ ownerId, definition, privateKeyWif, keyId }); +import { DataContract, IdentitySigner } from '@dashevo/evo-sdk'; + +const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + +const keys = await sdk.identities.getKeys({ + identityId: ownerId, + request: { type: 'all' }, +}); +// Contract publish requires a CRITICAL authentication key. +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'CRITICAL', +); + +const identityNonce = (await sdk.identities.nonce(ownerId)) ?? 0n; +const dataContract = new DataContract({ + ownerId, + identityNonce: identityNonce + 1n, + schemas: { + note: { + type: 'object', + properties: { + message: { type: 'string', maxLength: 200, position: 0 }, + }, + required: ['message'], + additionalProperties: false, + }, + }, +}); + +const published = await sdk.contracts.publish({ + dataContract, + identityKey, + signer, +}); ``` **Data Contract Update** - `contracts.update` @@ -1608,7 +1716,26 @@ Returns: Example: ```javascript -const result = await sdk.contracts.update({ contractId, ownerId, updates, privateKeyWif, keyId }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const contractId = 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'; +const dataContract = await sdk.contracts.fetch(contractId); +dataContract.version = (dataContract.version || 1) + 1; +// Optionally merge additional document type schemas before update: +// dataContract.setSchemas(mergedSchemas, undefined, false, sdk.version()); + +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ + identityId: ownerId, + request: { type: 'all' }, +}); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'CRITICAL', +); + +await sdk.contracts.update({ dataContract, identityKey, signer }); ``` #### Document Transitions @@ -1631,7 +1758,28 @@ Returns: Example: ```javascript -const result = await sdk.documents.create({ contractId, type: documentType, ownerId, data, entropyHex, privateKeyWif }); +import { Document, IdentitySigner } from '@dashevo/evo-sdk'; + +const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + +const keys = await sdk.identities.getKeys({ + identityId: ownerId, + request: { type: 'all' }, +}); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +const document = new Document({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + documentTypeName: 'domain', + ownerId, + properties: { /* fields required by the document type schema */ }, +}); + +await sdk.documents.create({ document, identityKey, signer }); ``` **Document Replace** - `documents.replace` @@ -1654,7 +1802,31 @@ Returns: Example: ```javascript -const result = await sdk.documents.replace({ contractId, type: documentType, documentId, ownerId, data, revision, privateKeyWif }); +import { Document, IdentitySigner } from '@dashevo/evo-sdk'; + +const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ + identityId: ownerId, + request: { type: 'all' }, +}); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +// Fetch current document, then rebuild/replace with revision + 1. +const current = await sdk.documents.get('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', 'domain', '7NYmEKQsYtniQRUmxwdPGeVcirMoPh5ZPyAKz8BWFy3r'); +const document = new Document({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + documentTypeName: 'domain', + ownerId, + id: '7NYmEKQsYtniQRUmxwdPGeVcirMoPh5ZPyAKz8BWFy3r', + revision: BigInt(current.revision) + 1n, + properties: { /* updated properties */ }, +}); + +await sdk.documents.replace({ document, identityKey, signer }); ``` **Document Delete** - `documents.delete` @@ -1673,7 +1845,29 @@ Returns: Example: ```javascript -const result = await sdk.documents.delete({ contractId, type: documentType, documentId, ownerId, privateKeyWif }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ + identityId: ownerId, + request: { type: 'all' }, +}); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +await sdk.documents.delete({ + document: { + id: '7NYmEKQsYtniQRUmxwdPGeVcirMoPh5ZPyAKz8BWFy3r', + ownerId, + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + documentTypeName: 'domain', + }, + identityKey, + signer, +}); ``` **Document Transfer** - `documents.transfer` @@ -1694,7 +1888,28 @@ Returns: Example: ```javascript -const result = await sdk.documents.transfer({ contractId, type: documentType, documentId, ownerId, recipientId, privateKeyWif }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ + identityId: ownerId, + request: { type: 'all' }, +}); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +const document = await sdk.documents.get('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', 'domain', '7NYmEKQsYtniQRUmxwdPGeVcirMoPh5ZPyAKz8BWFy3r'); +document.revision = BigInt(document.revision) + 1n; + +await sdk.documents.transfer({ + document, + recipientId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + identityKey, + signer, +}); ``` **Document Purchase** - `documents.purchase` @@ -1715,7 +1930,29 @@ Returns: Example: ```javascript -const result = await sdk.documents.purchase({ contractId, type: documentType, documentId, buyerId, price, privateKeyWif }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const buyerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ + identityId: buyerId, + request: { type: 'all' }, +}); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +const document = await sdk.documents.get('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', 'domain', '7NYmEKQsYtniQRUmxwdPGeVcirMoPh5ZPyAKz8BWFy3r'); +document.revision = BigInt(document.revision) + 1n; + +await sdk.documents.purchase({ + document, + buyerId, + price: 1000n, + identityKey, + signer, +}); ``` **Document Set Price** - `documents.setPrice` @@ -1736,7 +1973,28 @@ Returns: Example: ```javascript -const result = await sdk.documents.setPrice({ contractId, type: documentType, documentId, ownerId, price, privateKeyWif }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ + identityId: ownerId, + request: { type: 'all' }, +}); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +const document = await sdk.documents.get('GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', 'domain', '7NYmEKQsYtniQRUmxwdPGeVcirMoPh5ZPyAKz8BWFy3r'); +document.revision = BigInt(document.revision) + 1n; + +await sdk.documents.setPrice({ + document, + price: 1000n, + identityKey, + signer, +}); ``` **DPNS Register Name** - `dpns.registerName` @@ -1753,7 +2011,27 @@ Returns: Example: ```javascript -const result = await sdk.dpns.registerName({ label, identityId, publicKeyId, privateKeyWif, onPreorder }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const identityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const identity = await sdk.identities.fetch(identityId); +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +// HIGH authentication key is typically used for DPNS registration. +const identityKey = identity.getPublicKeyById(1) + || identity.publicKeys.find( + k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'HIGH', + ); + +const result = await sdk.dpns.registerName({ + label: 'alice', + identity, + identityKey, + signer, + preorderCallback: (preorderDocument) => { + console.log('preorder submitted', preorderDocument.id?.toString?.()); + }, +}); ``` #### Token Transitions @@ -1777,7 +2055,25 @@ Returns: Example: ```javascript -const result = await sdk.tokens.burn({ contractId, tokenPosition, amount, identityId, privateKeyWif, publicNote }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const identityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ identityId, request: { type: 'all' } }); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +const result = await sdk.tokens.burn({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + tokenPosition: 0, + amount: 10n, + identityId, + publicNote: 'burn', + identityKey, + signer, +}); ``` **Token Mint** - `tokens.mint` @@ -1801,7 +2097,26 @@ Returns: Example: ```javascript -const result = await sdk.tokens.mint({ contractId, tokenPosition, amount, identityId, privateKeyWif, recipientId, publicNote }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const identityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ identityId, request: { type: 'all' } }); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +const result = await sdk.tokens.mint({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + tokenPosition: 0, + amount: 100n, + identityId, + recipientId: identityId, + publicNote: 'mint', + identityKey, + signer, +}); ``` **Token Claim** - `tokens.claim` @@ -1824,7 +2139,25 @@ Returns: Example: ```javascript -const result = await sdk.tokens.claim({ contractId, tokenPosition, distributionType, identityId, privateKeyWif, publicNote }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const identityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ identityId, request: { type: 'all' } }); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +const result = await sdk.tokens.claim({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + tokenPosition: 0, + identityId, + distributionType: 'perpetual', // or 'preProgrammed' + publicNote: 'claim', + identityKey, + signer, +}); ``` **Token Set Price** - `tokens.setPrice` @@ -1850,7 +2183,25 @@ Returns: Example: ```javascript -const result = await sdk.tokens.setPrice({ contractId, tokenPosition, identityId, priceType, priceData, privateKeyWif, publicNote }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const authorityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ identityId: authorityId, request: { type: 'all' } }); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +await sdk.tokens.setPrice({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + tokenPosition: 0, + authorityId, + price: 1000n, // or null to clear + publicNote: 'set price', + identityKey, + signer, +}); ``` **Token Direct Purchase** - `tokens.directPurchase` @@ -1872,7 +2223,25 @@ Returns: Example: ```javascript -const result = await sdk.tokens.directPurchase({ contractId, tokenPosition, amount, identityId, totalAgreedPrice, privateKeyWif }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const buyerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ identityId: buyerId, request: { type: 'all' } }); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +const result = await sdk.tokens.directPurchase({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + tokenPosition: 0, + buyerId, + amount: 10n, + maxTotalCost: 10000n, + identityKey, + signer, +}); ``` **Token Emergency Action** - `tokens.emergencyAction` @@ -1895,7 +2264,25 @@ Returns: Example: ```javascript -const result = await sdk.tokens.emergencyAction({ contractId, tokenPosition, actionType, identityId, privateKeyWif, publicNote }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const authorityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ identityId: authorityId, request: { type: 'all' } }); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +await sdk.tokens.emergencyAction({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + tokenPosition: 0, + authorityId, + action: 'pause', // or 'resume' + publicNote: 'pause trading', + identityKey, + signer, +}); ``` **Token Transfer** - `tokens.transfer` @@ -1919,7 +2306,26 @@ Returns: Example: ```javascript -const result = await sdk.tokens.transfer({ contractId, tokenPosition, amount, senderId, recipientId, privateKeyWif, publicNote }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const senderId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ identityId: senderId, request: { type: 'all' } }); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +const result = await sdk.tokens.transfer({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + tokenPosition: 0, + amount: 5n, + senderId, + recipientId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + publicNote: 'transfer', + identityKey, + signer, +}); ``` **Token Freeze** - `tokens.freeze` @@ -1941,7 +2347,25 @@ Returns: Example: ```javascript -const result = await sdk.tokens.freeze({ contractId, tokenPosition, identityToFreeze, identityId, privateKeyWif, publicNote }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const authorityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ identityId: authorityId, request: { type: 'all' } }); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +await sdk.tokens.freeze({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + tokenPosition: 0, + authorityId, + frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + publicNote: 'freeze', + identityKey, + signer, +}); ``` **Token Unfreeze** - `tokens.unfreeze` @@ -1963,7 +2387,25 @@ Returns: Example: ```javascript -const result = await sdk.tokens.unfreeze({ contractId, tokenPosition, identityToUnfreeze, identityId, privateKeyWif, publicNote }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const authorityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ identityId: authorityId, request: { type: 'all' } }); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +await sdk.tokens.unfreeze({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + tokenPosition: 0, + authorityId, + frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + publicNote: 'unfreeze', + identityKey, + signer, +}); ``` **Token Destroy Frozen** - `tokens.destroyFrozen` @@ -1985,7 +2427,25 @@ Returns: Example: ```javascript -const result = await sdk.tokens.destroyFrozen({ contractId, tokenPosition, frozenIdentityId, identityId, privateKeyWif, publicNote }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const authorityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +const keys = await sdk.identities.getKeys({ identityId: authorityId, request: { type: 'all' } }); +const identityKey = keys.find( + k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), +); + +await sdk.tokens.destroyFrozen({ + dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + tokenPosition: 0, + authorityId, + frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + publicNote: 'destroy', + identityKey, + signer, +}); ``` #### Voting Transitions @@ -2009,7 +2469,30 @@ Returns: Example: ```javascript -const result = await sdk.voting.masternodeVote({ masternodeProTxHash, contractId, documentTypeName, indexName, indexValues, voteChoice, votingKeyWif }); +import { IdentitySigner, ResourceVoteChoice, VotePoll } from '@dashevo/evo-sdk'; + +const masternodeProTxHash = '143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExampleVotingKeyWifGoesHere'); +// Voting key must match the masternode voting public key on the identity. +const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); +const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING') + || votingIdentity.getPublicKeyById(0); + +const votePoll = new VotePoll({ + contractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + documentTypeName: 'domain', + indexName: 'parentNameAndLabel', + indexValues: ['dash', 'alice'], +}); + +await sdk.voting.masternodeVote({ + masternodeProTxHash, + votePoll, + voteChoice: ResourceVoteChoice.TowardsIdentity('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'), + votingKey, + signer, +}); ``` **Contested Resource** - `voting.masternodeVote` @@ -2035,7 +2518,29 @@ Returns: Example: ```javascript -const result = await sdk.voting.masternodeVote({ masternodeProTxHash, contractId, documentTypeName, indexName, indexValues, voteChoice, votingKeyWif }); +import { IdentitySigner, ResourceVoteChoice, VotePoll } from '@dashevo/evo-sdk'; + +const masternodeProTxHash = '143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'; +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExampleVotingKeyWifGoesHere'); +const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); +const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING') + || votingIdentity.getPublicKeyById(0); + +const votePoll = new VotePoll({ + contractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + documentTypeName: 'domain', + indexName: 'parentNameAndLabel', + indexValues: ['dash', 'alice'], +}); + +await sdk.voting.masternodeVote({ + masternodeProTxHash, + votePoll, + voteChoice: ResourceVoteChoice.TowardsIdentity('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'), + votingKey, + signer, +}); ``` #### Platform Address Transitions @@ -2060,7 +2565,28 @@ Returns: Example: ```javascript -const result = await sdk.addresses.transfer({ inputs, outputs, signer }); +import { + PlatformAddressInput, + PlatformAddressOutput, + PlatformAddressSigner, + PrivateKey, +} from '@dashevo/evo-sdk'; + +const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); +const signer = new PlatformAddressSigner(); +const senderAddr = signer.addKey(privateKey); // derives P2PKH platform address + +const input = new PlatformAddressInput(senderAddr, 0, 100000n); +const output = new PlatformAddressOutput( + /* recipient PlatformAddress or bech32m */ 'tdash1krt0z5hrcaphyuraxmk2h2ff8nyv5fmncsgf7evf', + 90000n, +); + +const result = await sdk.addresses.transfer({ + inputs: [input], + outputs: [output], + signer, +}); ``` **Top Up Identity from Address** - `addresses.topUpIdentity` @@ -2083,7 +2609,19 @@ Returns: Example: ```javascript -const result = await sdk.addresses.topUpIdentity({ identityId, inputs, signer }); +import { PlatformAddressInput, PlatformAddressSigner, PrivateKey } from '@dashevo/evo-sdk'; + +const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); +const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); +const signer = new PlatformAddressSigner(); +const sourceAddr = signer.addKey(privateKey); +const input = new PlatformAddressInput(sourceAddr, 0, 50000n); + +const result = await sdk.addresses.topUpIdentity({ + identity, + inputs: [input], + signer, +}); ``` **Withdraw to Core** - `addresses.withdraw` @@ -2112,7 +2650,21 @@ Returns: Example: ```javascript -const result = await sdk.addresses.withdraw({ inputs, coreFeePerByte, pooling, outputScript, signer }); +import { PlatformAddressInput, PlatformAddressSigner, PrivateKey } from '@dashevo/evo-sdk'; + +const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); +const signer = new PlatformAddressSigner(); +const platformAddr = signer.addKey(privateKey); +const input = new PlatformAddressInput(platformAddr, 0, 100000n); + +// Provide a Core L1 output script (e.g. CoreScript.newP2PKH(...)). +const result = await sdk.addresses.withdraw({ + inputs: [input], + coreFeePerByte: 1, + pooling: undefined, + outputScript: /* CoreScript */ undefined, + signer, +}); ``` **Transfer from Identity to Address** - `addresses.transferFromIdentity` @@ -2135,7 +2687,19 @@ Returns: Example: ```javascript -const result = await sdk.addresses.transferFromIdentity({ identityId, outputs, signer }); +import { IdentitySigner, PlatformAddressOutput } from '@dashevo/evo-sdk'; + +// Uses IdentitySigner (identity transfer key), not PlatformAddressSigner. +const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + +const output = new PlatformAddressOutput('tdash1krt0z5hrcaphyuraxmk2h2ff8nyv5fmncsgf7evf', 100000n); +const result = await sdk.addresses.transferFromIdentity({ + identity, + outputs: [output], + signer, +}); ``` **Fund Address from Asset Lock** - `addresses.fundFromAssetLock` @@ -2161,7 +2725,28 @@ Returns: Example: ```javascript -const result = await sdk.addresses.fundFromAssetLock({ assetLockProof, assetLockPrivateKey, outputs, signer }); +import { + AssetLockProof, + PlatformAddressOutput, + PlatformAddressSigner, + PrivateKey, +} from '@dashevo/evo-sdk'; + +// Asset-lock key signs the L1 funding proof; address signer controls outputs. +const assetLockPrivateKey = PrivateKey.fromWIF('cAssetLockPrivateKeyWif...'); +const addressPrivateKey = PrivateKey.fromWIF('cAddressPrivateKeyWif...'); +const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); + +const signer = new PlatformAddressSigner(); +const platformAddr = signer.addKey(addressPrivateKey); +const output = new PlatformAddressOutput(platformAddr, 100000n); + +const result = await sdk.addresses.fundFromAssetLock({ + assetLockProof, + assetLockPrivateKey, + outputs: [output], + signer, +}); ``` **Create Identity from Address** - `addresses.createIdentity` @@ -2187,7 +2772,44 @@ Returns: Example: ```javascript -const result = await sdk.addresses.createIdentity({ identity, inputs, identitySigner, addressSigner }); +import { + Identity, + IdentityPublicKeyInCreation, + IdentitySigner, + KeyType, + PlatformAddressInput, + PlatformAddressSigner, + PrivateKey, + Purpose, + SecurityLevel, +} from '@dashevo/evo-sdk'; + +const addressPrivateKey = PrivateKey.fromWIF('cAddressPrivateKeyWif...'); +const identityPrivateKey = PrivateKey.fromWIF('cIdentityKeyWif...'); + +const identity = new Identity(/* 32-byte id */ new Uint8Array(32)); +identity.addPublicKey( + new IdentityPublicKeyInCreation({ + keyId: 0, + purpose: Purpose.AUTHENTICATION, + securityLevel: SecurityLevel.MASTER, + keyType: KeyType.ECDSA_SECP256K1, + data: identityPrivateKey.getPublicKey().toBytes(), + }).toIdentityPublicKey(), +); + +const addressSigner = new PlatformAddressSigner(); +const sourceAddr = addressSigner.addKey(addressPrivateKey); +const identitySigner = new IdentitySigner(); +identitySigner.addKey(identityPrivateKey); +const input = new PlatformAddressInput(sourceAddr, 0, 50_000_000_000n); + +const result = await sdk.addresses.createIdentity({ + identity, + inputs: [input], + identitySigner, + addressSigner, +}); ``` ## Common Patterns diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index c1ffe7e..a3f1f9e 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -1118,7 +1118,7 @@ def evo_example_for_transition(key: str): const signer = new PlatformAddressSigner(); const senderAddr = signer.addKey(privateKey); // derives P2PKH platform address - const input = new PlatformAddressInput(senderAddr, 0n, 100000n); + const input = new PlatformAddressInput(senderAddr, 0, 100000n); const output = new PlatformAddressOutput( /* recipient PlatformAddress or bech32m */ '{platform_address}', 90000n, @@ -1137,10 +1137,10 @@ def evo_example_for_transition(key: str): const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); const signer = new PlatformAddressSigner(); const sourceAddr = signer.addKey(privateKey); - const input = new PlatformAddressInput(sourceAddr, 0n, 50000n); + const input = new PlatformAddressInput(sourceAddr, 0, 50000n); const result = await sdk.addresses.topUpIdentity({{ - identityId: identity.id, + identity, inputs: [input], signer, }}); @@ -1151,7 +1151,7 @@ def evo_example_for_transition(key: str): const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); const signer = new PlatformAddressSigner(); const platformAddr = signer.addKey(privateKey); - const input = new PlatformAddressInput(platformAddr, 0n, 100000n); + const input = new PlatformAddressInput(platformAddr, 0, 100000n); // Provide a Core L1 output script (e.g. CoreScript.newP2PKH(...)). const result = await sdk.addresses.withdraw({{ @@ -1165,14 +1165,14 @@ def evo_example_for_transition(key: str): 'addressTransferFromIdentity': example(f""" import {{ IdentitySigner, PlatformAddressOutput }} from '@dashevo/evo-sdk'; - const identityId = '{identity}'; // Uses IdentitySigner (identity transfer key), not PlatformAddressSigner. + const identity = await sdk.identities.fetch('{identity}'); const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const output = new PlatformAddressOutput('{platform_address}', 100000n); const result = await sdk.addresses.transferFromIdentity({{ - identityId, + identity, outputs: [output], signer, }}); @@ -1206,8 +1206,8 @@ def evo_example_for_transition(key: str): Identity, IdentityPublicKeyInCreation, IdentitySigner, - Identifier, KeyType, + PlatformAddressInput, PlatformAddressSigner, PrivateKey, Purpose, @@ -1217,14 +1217,14 @@ def evo_example_for_transition(key: str): const addressPrivateKey = PrivateKey.fromWIF('cAddressPrivateKeyWif...'); const identityPrivateKey = PrivateKey.fromWIF('cIdentityKeyWif...'); - const identity = new Identity(Identifier.random()); + const identity = new Identity(/* 32-byte id */ new Uint8Array(32)); identity.addPublicKey( new IdentityPublicKeyInCreation({{ keyId: 0, purpose: Purpose.AUTHENTICATION, securityLevel: SecurityLevel.MASTER, keyType: KeyType.ECDSA_SECP256K1, - data: identityPrivateKey.getPublicKey().toBuffer(), + data: identityPrivateKey.getPublicKey().toBytes(), }}).toIdentityPublicKey(), ); @@ -1232,10 +1232,11 @@ def evo_example_for_transition(key: str): const sourceAddr = addressSigner.addKey(addressPrivateKey); const identitySigner = new IdentitySigner(); identitySigner.addKey(identityPrivateKey); + const input = new PlatformAddressInput(sourceAddr, 0, 50_000_000_000n); const result = await sdk.addresses.createIdentity({{ identity, - inputs: [{{ address: sourceAddr, amount: 50_000_000_000n }}], + inputs: [input], identitySigner, addressSigner, }}); From 3f071715edefb4222e7b793979a693dfb744c1eb Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 14:40:08 -0500 Subject: [PATCH 03/22] test: guard generated docs against pre-v4 transition call shapes Add unit coverage that loads generator transition examples, forbids privateKeyWif-in-call option keys, and checks final sdk write options against shipped wasm-sdk Options interfaces. Extend documentation check with the classic issue #63 example patterns. Co-Authored-By: Claude --- scripts/check_documentation.py | 29 +++ tests/unit/transition-examples.test.js | 286 +++++++++++++++++++++++++ 2 files changed, 315 insertions(+) create mode 100644 tests/unit/transition-examples.test.js diff --git a/scripts/check_documentation.py b/scripts/check_documentation.py index 04f4fbb..42fe975 100644 --- a/scripts/check_documentation.py +++ b/scripts/check_documentation.py @@ -102,7 +102,36 @@ def main(): if missing_anchors: errors.append(f"ERROR: Missing HTML return type anchors: {', '.join(missing_anchors)}") + # Guard against pre-v4 privateKeyWif-in-call examples (issue #63). + if not errors: + classic_patterns = [ + (r'documents\.create\(\{\s*contractId,\s*type:\s*documentType,\s*ownerId,\s*data,\s*entropyHex,\s*privateKeyWif\s*\}', + 'pre-v4 documents.create(... privateKeyWif) example'), + (r'identities\.topUp\(\{\s*identityId,\s*assetLockProof,\s*assetLockPrivateKeyWif\s*\}', + 'pre-v4 identities.topUp(... assetLockPrivateKeyWif) example'), + (r'identities\.create\(\{\s*assetLockProof,\s*assetLockPrivateKeyWif,\s*publicKeys\s*\}', + 'pre-v4 identities.create(... assetLockPrivateKeyWif, publicKeys) example'), + (r'sdk\.\.\(\{\s*\.\.\.params,\s*privateKeyWif\s*\}', + 'pre-v4 state-transition pattern with privateKeyWif in call'), + ] + for label, file_path in (('docs.html', docs_file), ('AI_REFERENCE.md', ai_file)): + if not file_path.exists(): + continue + content = file_path.read_text(encoding='utf-8') + for pattern, description in classic_patterns: + if re.search(pattern, content): + errors.append(f'ERROR: {label} still contains {description}') + # Broad guard: privateKeyWif used as an object property in generated examples. + if re.search(r'\bprivateKeyWif\s*:', content): + errors.append( + f'ERROR: {label} still documents privateKeyWif as an object property; ' + 'v4 examples must use IdentitySigner.addKeyFromWif / PrivateKey.fromWIF instead' + ) + if 'IdentitySigner' not in content: + errors.append(f'ERROR: {label} is missing IdentitySigner usage expected for v4 write examples') + # Compose report + lines = [ '=' * 80, 'Evo SDK Documentation Check', diff --git a/tests/unit/transition-examples.test.js b/tests/unit/transition-examples.test.js new file mode 100644 index 0000000..9c12df2 --- /dev/null +++ b/tests/unit/transition-examples.test.js @@ -0,0 +1,286 @@ +import { describe, expect, it } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import ts from 'typescript'; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const docs = fs.readFileSync(path.join(ROOT, 'public/docs.html'), 'utf8'); +const aiReference = fs.readFileSync(path.join(ROOT, 'public/AI_REFERENCE.md'), 'utf8'); +const apiDefinitions = JSON.parse(fs.readFileSync(path.join(ROOT, 'public/api-definitions.json'), 'utf8')); +const wasmSdkDts = fs.readFileSync(path.join(ROOT, 'node_modules/@dashevo/wasm-sdk/dist/sdk.d.ts'), 'utf8'); + +const TRANSITION_KEYS = Object.values(apiDefinitions.transitions).flatMap((category) => + Object.keys(category.transitions || {}), +); + +const METHOD_TO_OPTIONS = { + 'identities.create': 'IdentityCreateOptions', + 'identities.topUp': 'IdentityTopUpOptions', + 'identities.creditTransfer': 'IdentityCreditTransferOptions', + 'identities.creditWithdrawal': 'IdentityCreditWithdrawalOptions', + 'identities.update': 'IdentityUpdateOptions', + 'contracts.publish': 'ContractPublishOptions', + 'contracts.update': 'ContractUpdateOptions', + 'documents.create': 'DocumentCreateOptions', + 'documents.replace': 'DocumentReplaceOptions', + 'documents.delete': 'DocumentDeleteOptions', + 'documents.transfer': 'DocumentTransferOptions', + 'documents.purchase': 'DocumentPurchaseOptions', + 'documents.setPrice': 'DocumentSetPriceOptions', + 'tokens.mint': 'TokenMintOptions', + 'tokens.burn': 'TokenBurnOptions', + 'tokens.transfer': 'TokenTransferOptions', + 'tokens.freeze': 'TokenFreezeOptions', + 'tokens.unfreeze': 'TokenUnfreezeOptions', + 'tokens.destroyFrozen': 'TokenDestroyFrozenOptions', + 'tokens.emergencyAction': 'TokenEmergencyActionOptions', + 'tokens.setPrice': 'TokenSetPriceOptions', + 'tokens.directPurchase': 'TokenDirectPurchaseOptions', + 'tokens.claim': 'TokenClaimOptions', + 'dpns.registerName': 'DpnsRegisterNameOptions', + 'voting.masternodeVote': 'MasternodeVoteOptions', + 'addresses.transfer': 'AddressFundsTransferOptions', + 'addresses.topUpIdentity': 'IdentityTopUpFromAddressesOptions', + 'addresses.withdraw': 'AddressFundsWithdrawOptions', + 'addresses.transferFromIdentity': 'IdentityTransferToAddressesOptions', + 'addresses.fundFromAssetLock': 'AddressFundingFromAssetLockOptions', + 'addresses.createIdentity': 'IdentityCreateFromAddressesOptions', +}; + +/** Pre-v4 call-shape markers that must not appear as options on SDK write calls. */ +const FORBIDDEN_CALL_OPTION_NAMES = [ + 'privateKeyWif', + 'assetLockPrivateKeyWif', + 'votingKeyWif', + 'publicKeyId', + 'onPreorder', + 'entropyHex', + 'priceType', + 'priceData', + 'totalAgreedPrice', + 'actionType', + 'identityToFreeze', + 'identityToUnfreeze', + 'freezerId', + 'unfreezerId', + 'destroyerId', + 'definition', + 'updates', +]; + +function loadTransitionExamplesFromGenerator() { + const script = ` +import importlib.util +from pathlib import Path +spec = importlib.util.spec_from_file_location('generate_docs', Path(${JSON.stringify(path.join(ROOT, 'scripts/generate_docs.py'))})) +mod = importlib.util.module_from_spec(spec) +spec.loader.exec_module(mod) +import json +keys = ${JSON.stringify(TRANSITION_KEYS)} +out = {key: mod.evo_example_for_transition(key) for key in keys} +print(json.dumps(out)) +`; + const result = spawnSync('python3', ['-c', script], { + cwd: ROOT, + encoding: 'utf8', + maxBuffer: 10 * 1024 * 1024, + }); + if (result.status !== 0) { + throw new Error(`Failed to load transition examples: ${result.stderr || result.stdout}`); + } + return JSON.parse(result.stdout); +} + +function interfaceProperties(sourceText, interfaceName) { + const source = ts.createSourceFile('sdk.d.ts', sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + const props = new Set(); + const visit = (node) => { + if ( + ts.isInterfaceDeclaration(node) + && node.name.text === interfaceName + && node.members + ) { + for (const member of node.members) { + if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) { + props.add(member.name.text); + } + } + } + ts.forEachChild(node, visit); + }; + visit(source); + return props; +} + +function extractSdkCallSites(example) { + const calls = []; + const callRe = /await\s+sdk\.([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\s*\(/g; + let match; + while ((match = callRe.exec(example)) !== null) { + const namespace = match[1]; + const method = match[2]; + const openIdx = match.index + match[0].length - 1; + let depth = 0; + let end = -1; + for (let i = openIdx; i < example.length; i += 1) { + const ch = example[i]; + if (ch === '(') depth += 1; + else if (ch === ')') { + depth -= 1; + if (depth === 0) { + end = i; + break; + } + } + } + if (end === -1) continue; + const argsText = example.slice(openIdx + 1, end).trim(); + calls.push({ method: `${namespace}.${method}`, argsText }); + } + return calls; +} + +function topLevelObjectKeys(argsText) { + // Expect a single object literal argument: { ... } + const trimmed = argsText.trim(); + if (!trimmed.startsWith('{')) return []; + const source = ts.createSourceFile('example.ts', `const __opts = ${trimmed};`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + const keys = []; + const visit = (node) => { + if (ts.isObjectLiteralExpression(node) && node.parent && ts.isVariableDeclaration(node.parent)) { + for (const prop of node.properties) { + if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) { + if (ts.isIdentifier(prop.name)) keys.push(prop.name.text); + } + } + } + ts.forEachChild(node, visit); + }; + visit(source); + return keys; +} + +function exampleMentionsForbiddenCallOption(example) { + const hits = []; + for (const call of extractSdkCallSites(example)) { + const keys = topLevelObjectKeys(call.argsText); + for (const key of keys) { + if (FORBIDDEN_CALL_OPTION_NAMES.includes(key)) { + hits.push(`${call.method}({ ${key} })`); + } + } + } + // Also catch single-line pre-v4 shapes that put privateKeyWif in the same object as contractId/type. + if (/\{\s*[^}]*\bprivateKeyWif\b[^}]*\}/.test(example)) { + // Only flag when it is not purely a key-construction object for IdentityPublicKeyInCreation etc. + // Those objects use privateKeyWif only in the removed pre-v4 publicKeys JSON; current examples + // should not include privateKeyWif as an object property at all. + if (/\bprivateKeyWif\s*:/.test(example)) { + hits.push('object property privateKeyWif'); + } + } + return hits; +} + +const generatedExamples = loadTransitionExamplesFromGenerator(); + +describe('v4 state transition documentation examples', () => { + it('covers every transition operation with a generator example', () => { + for (const key of TRANSITION_KEYS) { + expect(generatedExamples[key], key).toBeTruthy(); + } + }); + + it('does not use pre-v4 privateKeyWif-in-call option shapes', () => { + const failures = []; + for (const [key, example] of Object.entries(generatedExamples)) { + const hits = exampleMentionsForbiddenCallOption(example); + if (hits.length) failures.push(`${key}: ${hits.join(', ')}`); + } + // Generated artifacts must also be free of the classic one-liners from issue #63. + for (const [label, text] of [['docs.html', docs], ['AI_REFERENCE.md', aiReference]]) { + const classic = [ + /documents\.create\(\{\s*contractId,\s*type:\s*documentType,\s*ownerId,\s*data,\s*entropyHex,\s*privateKeyWif\s*\}\)/, + /identities\.topUp\(\{\s*identityId,\s*assetLockProof,\s*assetLockPrivateKeyWif\s*\}\)/, + /identities\.create\(\{\s*assetLockProof,\s*assetLockPrivateKeyWif,\s*publicKeys\s*\}\)/, + /sdk\.\.\(\{\s*\.\.\.params,\s*privateKeyWif\s*\}\)/, + ]; + for (const re of classic) { + if (re.test(text)) failures.push(`${label} still contains ${re}`); + } + } + expect(failures).toEqual([]); + }); + + it('uses IdentitySigner / asset-lock PrivateKey / payload constructors where appropriate', () => { + expect(generatedExamples.documentCreate).toMatch(/new Document\s*\(/); + expect(generatedExamples.documentCreate).toMatch(/IdentitySigner/); + expect(generatedExamples.documentCreate).toMatch(/identityKey/); + expect(generatedExamples.dataContractCreate).toMatch(/new DataContract\s*\(/); + expect(generatedExamples.identityCreate).toMatch(/AssetLockProof/); + expect(generatedExamples.identityCreate).toMatch(/assetLockPrivateKey/); + expect(generatedExamples.identityCreate).toMatch(/IdentitySigner/); + expect(generatedExamples.identityTopUp).toMatch(/assetLockPrivateKey/); + // Top-up is asset-lock only — no IdentitySigner construction/import. + expect(generatedExamples.identityTopUp).not.toMatch(/new IdentitySigner|import \{[^}]*IdentitySigner/); + expect(generatedExamples.addressFundFromAssetLock).toMatch(/assetLockPrivateKey/); + expect(generatedExamples.addressFundFromAssetLock).toMatch(/PlatformAddressSigner/); + expect(generatedExamples.addressTransferFromIdentity).toMatch(/IdentitySigner/); + expect(generatedExamples.dpnsRegister).toMatch(/identityKey/); + expect(generatedExamples.dpnsRegister).toMatch(/IdentitySigner/); + }); + + it('passes only declared option properties on the final sdk write call', () => { + const failures = []; + for (const [key, item] of Object.entries( + Object.fromEntries( + Object.values(apiDefinitions.transitions).flatMap((category) => + Object.entries(category.transitions || {}), + ), + ), + )) { + const example = generatedExamples[key]; + const sdkMethod = item.sdk_method; + const optionsName = METHOD_TO_OPTIONS[sdkMethod]; + if (!optionsName) { + failures.push(`${key}: no Options mapping for ${sdkMethod}`); + continue; + } + const allowed = interfaceProperties(wasmSdkDts, optionsName); + // TypeScript may declare the same interface name more than once in the giant d.ts + // (transition object variants). Prefer the richest write-options set when ambiguous. + if (!allowed.size) { + failures.push(`${key}: Options interface ${optionsName} not found`); + continue; + } + + const writeCalls = extractSdkCallSites(example).filter((call) => call.method === sdkMethod); + if (!writeCalls.length) { + failures.push(`${key}: no await sdk.${sdkMethod}(...) call in example`); + continue; + } + const call = writeCalls[writeCalls.length - 1]; + const keys = topLevelObjectKeys(call.argsText); + if (!keys.length) { + failures.push(`${key}: could not parse options object for ${sdkMethod}`); + continue; + } + const unknown = keys.filter((k) => !allowed.has(k)); + if (unknown.length) { + failures.push(`${key}: unknown option(s) on ${sdkMethod}: ${unknown.join(', ')} (allowed: ${[...allowed].sort().join(', ')})`); + } + } + expect(failures).toEqual([]); + }); + + it('embeds v4 document create / identity top-up examples in generated docs', () => { + expect(docs).toContain('new Document({'); + expect(docs).toContain('identityKey'); + expect(docs).toContain('assetLockPrivateKey'); + expect(aiReference).toContain('new Document({'); + expect(aiReference).toContain('typed options object'); + expect(aiReference).not.toContain('{ ...params, privateKeyWif }'); + }); +}); From 5da6ab7fc82fc52691dd74370309d6ff2a50233a Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 14:41:49 -0500 Subject: [PATCH 04/22] refactor: drop obsolete identity create/top-up param name overrides sdk_params for those transitions already use the v4 names, so the assetLockPrivateKeyWif rename map is unused. Co-Authored-By: Claude --- scripts/generate_docs.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index a3f1f9e..e29db7b 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -2159,11 +2159,6 @@ def append_query_sections() -> None: '### Available State Transitions', ]) - sdk_param_overrides = { - 'identityCreate': {'assetLockProofPrivateKey': 'assetLockPrivateKeyWif'}, - 'identityTopUp': {'assetLockProofPrivateKey': 'assetLockPrivateKeyWif'}, - } - def append_transition_params( item_key: str, target: List[str], @@ -2178,9 +2173,7 @@ def append_transition_params( for param in params: name = param.get('name', 'unknown') label = param.get('label') - override_name = sdk_param_overrides.get(item_key, {}).get(name) - effective_name = override_name or name - display_name = label if label and label != name else effective_name + display_name = label if label and label != name else name param_type = param.get('type', 'text') required = 'required' if param.get('required') else 'optional' target.append(f"- `{display_name}` ({param_type}, {required})") From 7d1cdf361a1af452f0b1951c73c2a253754a3654 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 14:48:17 -0500 Subject: [PATCH 05/22] refactor: share v4 transition example signer/key helpers Reduce duplicated IdentitySigner and auth-key setup in generated state transition docs, consolidate example formatting helpers, and slim the issue #63 regression guards without changing example semantics. --- scripts/check_documentation.py | 34 +- scripts/generate_docs.py | 716 ++++++++++++++----------- tests/unit/transition-examples.test.js | 104 ++-- 3 files changed, 478 insertions(+), 376 deletions(-) diff --git a/scripts/check_documentation.py b/scripts/check_documentation.py index 42fe975..cc5fb07 100644 --- a/scripts/check_documentation.py +++ b/scripts/check_documentation.py @@ -105,14 +105,26 @@ def main(): # Guard against pre-v4 privateKeyWif-in-call examples (issue #63). if not errors: classic_patterns = [ - (r'documents\.create\(\{\s*contractId,\s*type:\s*documentType,\s*ownerId,\s*data,\s*entropyHex,\s*privateKeyWif\s*\}', - 'pre-v4 documents.create(... privateKeyWif) example'), - (r'identities\.topUp\(\{\s*identityId,\s*assetLockProof,\s*assetLockPrivateKeyWif\s*\}', - 'pre-v4 identities.topUp(... assetLockPrivateKeyWif) example'), - (r'identities\.create\(\{\s*assetLockProof,\s*assetLockPrivateKeyWif,\s*publicKeys\s*\}', - 'pre-v4 identities.create(... assetLockPrivateKeyWif, publicKeys) example'), - (r'sdk\.\.\(\{\s*\.\.\.params,\s*privateKeyWif\s*\}', - 'pre-v4 state-transition pattern with privateKeyWif in call'), + ( + r'documents\.create\(\{\s*contractId,\s*type:\s*documentType,\s*ownerId,\s*data,\s*entropyHex,\s*privateKeyWif\s*\}', + 'pre-v4 documents.create(... privateKeyWif) example', + ), + ( + r'identities\.topUp\(\{\s*identityId,\s*assetLockProof,\s*assetLockPrivateKeyWif\s*\}', + 'pre-v4 identities.topUp(... assetLockPrivateKeyWif) example', + ), + ( + r'identities\.create\(\{\s*assetLockProof,\s*assetLockPrivateKeyWif,\s*publicKeys\s*\}', + 'pre-v4 identities.create(... assetLockPrivateKeyWif, publicKeys) example', + ), + ( + r'sdk\.\.\(\{\s*\.\.\.params,\s*privateKeyWif\s*\}', + 'pre-v4 state-transition pattern with privateKeyWif in call', + ), + ( + r'\bprivateKeyWif\s*:', + 'privateKeyWif object property; use IdentitySigner.addKeyFromWif / PrivateKey.fromWIF', + ), ] for label, file_path in (('docs.html', docs_file), ('AI_REFERENCE.md', ai_file)): if not file_path.exists(): @@ -121,12 +133,6 @@ def main(): for pattern, description in classic_patterns: if re.search(pattern, content): errors.append(f'ERROR: {label} still contains {description}') - # Broad guard: privateKeyWif used as an object property in generated examples. - if re.search(r'\bprivateKeyWif\s*:', content): - errors.append( - f'ERROR: {label} still documents privateKeyWif as an object property; ' - 'v4 examples must use IdentitySigner.addKeyFromWif / PrivateKey.fromWIF instead' - ) if 'IdentitySigner' not in content: errors.append(f'ERROR: {label} is missing IdentitySigner usage expected for v4 write examples') diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index e29db7b..b8189ed 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -73,6 +73,107 @@ def example(text: str) -> str: return textwrap.dedent(text).strip() +EXAMPLE_IDENTITY_SIGNER_WIF = 'L1ExamplePrivateKeyWifGoesHere' +EXAMPLE_VOTING_KEY_WIF = 'L1ExampleVotingKeyWifGoesHere' +EXAMPLE_ASSET_LOCK_WIF = 'cVExampleAssetLockKeyForIdentityFunding' +EXAMPLE_RECIPIENT_ID = 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL' +AUTH_SECURITY_LEVELS_DOC = "['CRITICAL', 'HIGH', 'MEDIUM']" + + +def identity_signer_setup( + wif: str = EXAMPLE_IDENTITY_SIGNER_WIF, + *, + comment: str | None = None, + comment_before_add_key: bool = False, + signer_name: str = 'signer', +) -> str: + """Common IdentitySigner construction used by most write examples.""" + lines: List[str] = [] + if comment and not comment_before_add_key: + for part in comment.split('\n'): + lines.append(part if part.startswith('//') else f'// {part}') + lines.append(f'const {signer_name} = new IdentitySigner();') + if comment and comment_before_add_key: + for part in comment.split('\n'): + lines.append(part if part.startswith('//') else f'// {part}') + lines.append(f"{signer_name}.addKeyFromWif('{wif}');") + return '\n'.join(lines) + + +def auth_identity_key_setup( + identity_var: str, + *, + security_levels: str = AUTH_SECURITY_LEVELS_DOC, + compact: bool = False, + critical_only: bool = False, +) -> str: + """Fetch identity keys and select an AUTHENTICATION key for write options.""" + if critical_only: + predicate = "k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'CRITICAL'" + else: + predicate = ( + "k => k.purpose === 'AUTHENTICATION' && " + f"{security_levels}.includes(k.securityLevel)" + ) + + if compact: + if identity_var == 'identityId': + get_keys = "const keys = await sdk.identities.getKeys({ identityId, request: { type: 'all' } });" + else: + get_keys = ( + f'const keys = await sdk.identities.getKeys(' + f"{{ identityId: {identity_var}, request: {{ type: 'all' }} }});" + ) + return f'{get_keys}\nconst identityKey = keys.find(\n {predicate},\n);' + + return ( + 'const keys = await sdk.identities.getKeys({\n' + f' identityId: {identity_var},\n' + " request: { type: 'all' },\n" + '});\n' + 'const identityKey = keys.find(\n' + f' {predicate},\n' + ');' + ) + + +def signer_and_auth_key_setup( + identity_var: str, + *, + compact: bool = False, + critical_only: bool = False, + blank_before_keys: bool = True, + wif: str = EXAMPLE_IDENTITY_SIGNER_WIF, + comment: str | None = None, +) -> str: + parts = [identity_signer_setup(wif, comment=comment)] + if blank_before_keys: + parts.append('') + parts.append( + auth_identity_key_setup( + identity_var, + compact=compact, + critical_only=critical_only, + ) + ) + return '\n'.join(parts) + + +def normalize_example_lines(code: str | None) -> List[str]: + formatted = (code or '').replace('\\n', '\n').strip() + if not formatted: + return [] + return [re.sub(r'\bclient\b', 'sdk', line) for line in formatted.split('\n')] + + +def code_line_indexes(lines: Iterable[str]) -> List[int]: + return [ + index + for index, line in enumerate(lines) + if line.strip() and not line.strip().startswith('//') + ] + + def load_api_definitions(api_definitions_file: Path) -> tuple[dict, dict]: with open(api_definitions_file, 'r', encoding='utf-8') as f: api_data = json.load(f) @@ -481,6 +582,16 @@ def evo_example_for_query(key: str, inputs: List[dict]): return examples.get(key) +def compose_example(*sections: str) -> str: + """Join example fragments that may mix f-strings and shared helper snippets.""" + chunks = [ + textwrap.dedent(section).strip('\n') + for section in sections + if section and section.strip() + ] + return '\n\n'.join(chunks).strip() + + def evo_example_for_transition(key: str): """Return practical v4 state-transition examples matching shipped Options interfaces. @@ -494,11 +605,33 @@ def evo_example_for_transition(key: str): document_type = TESTNET_TEST_DATA['document_type'] pro_tx = TESTNET_TEST_DATA['pro_tx_hash'] platform_address = TESTNET_TEST_DATA['platform_address'] + recipient = EXAMPLE_RECIPIENT_ID + + signer_only = identity_signer_setup() + signer_master = identity_signer_setup( + comment='Master key is required to add/disable identity keys.', + comment_before_add_key=True, + ) + signer_identity_create = identity_signer_setup( + comment=( + 'IdentitySigner holds private keys for proving ownership of identity keys.\n' + '// Keep this separate from the asset-lock key.' + ), + ) + signer_voting = identity_signer_setup(EXAMPLE_VOTING_KEY_WIF) + signer_and_doc_key = signer_and_auth_key_setup('ownerId') + signer_and_buyer_key = signer_and_auth_key_setup('buyerId') + # Token examples keep signer + getKeys tightly packed (no blank line). + signer_and_identity_key = signer_and_auth_key_setup('identityId', compact=True, blank_before_keys=False) + signer_and_sender_key = signer_and_auth_key_setup('senderId', compact=True, blank_before_keys=False) + signer_and_authority_key = signer_and_auth_key_setup('authorityId', compact=True, blank_before_keys=False) + signer_and_contract_key = signer_and_auth_key_setup('ownerId', critical_only=True) m = { # Identities - 'identityCreate': example(f""" - import {{ + 'identityCreate': compose_example( + """ + import { AssetLockProof, Identity, IdentityPublicKeyInCreation, @@ -507,141 +640,153 @@ def evo_example_for_transition(key: str): PrivateKey, Purpose, SecurityLevel, - }} from '@dashevo/evo-sdk'; + } from '@dashevo/evo-sdk'; // Asset lock from Core (hex) + the private key that controls that output. const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); - const assetLockPrivateKey = PrivateKey.fromWIF('cVExampleAssetLockKeyForIdentityFunding'); - + """, + f"const assetLockPrivateKey = PrivateKey.fromWIF('{EXAMPLE_ASSET_LOCK_WIF}');", + """ // Build the identity shell and attach public keys that will be registered. const identity = new Identity('random-or-derived-identity-id'); - const masterKey = new IdentityPublicKeyInCreation({{ + const masterKey = new IdentityPublicKeyInCreation({ keyId: 0, purpose: Purpose.AUTHENTICATION, securityLevel: SecurityLevel.MASTER, keyType: KeyType.ECDSA_SECP256K1, data: Uint8Array.from(atob('A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ'), c => c.charCodeAt(0)), - }}).toIdentityPublicKey(); + }).toIdentityPublicKey(); identity.addPublicKey(masterKey); - - // IdentitySigner holds private keys for proving ownership of identity keys. - // Keep this separate from the asset-lock key. - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - - await sdk.identities.create({{ + """, + signer_identity_create, + """ + await sdk.identities.create({ identity, assetLockProof, assetLockPrivateKey, signer, - }}); - """), - 'identityTopUp': example(f""" + }); + """, + ), + 'identityTopUp': compose_example( + f""" import {{ AssetLockProof, PrivateKey }} from '@dashevo/evo-sdk'; const identity = await sdk.identities.fetch('{identity}'); const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); // Asset-lock signing only — top-up does not use IdentitySigner. - const assetLockPrivateKey = PrivateKey.fromWIF('cVExampleAssetLockKeyForIdentityFunding'); + const assetLockPrivateKey = PrivateKey.fromWIF('{EXAMPLE_ASSET_LOCK_WIF}'); const newBalance = await sdk.identities.topUp({{ identity, assetLockProof, assetLockPrivateKey, }}); - """), - 'identityCreditTransfer': example(f""" + """, + ), + 'identityCreditTransfer': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const identity = await sdk.identities.fetch('{identity}'); - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + """, + signer_only, + f""" // Optional: pass signingKey when you need a specific transfer/auth key. const signingKey = identity.getPublicKeyById(3) // TRANSFER key when present || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); await sdk.identities.creditTransfer({{ identity, - recipientId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + recipientId: '{recipient}', amount: 1000000n, signer, signingKey, }}); - """), - 'identityCreditWithdrawal': example(f""" + """, + ), + 'identityCreditWithdrawal': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const identity = await sdk.identities.fetch('{identity}'); - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + """, + signer_only, + """ const signingKey = identity.getPublicKeyById(3) || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); - const remainingBalance = await sdk.identities.creditWithdrawal({{ + const remainingBalance = await sdk.identities.creditWithdrawal({ identity, amount: 1000000n, toAddress: 'yT8DDY5NkX4Zt44Fy8QjmCekheJQH4EMkv', coreFeePerByte: 1, signer, signingKey, - }}); - """), - 'identityUpdate': example(f""" + }); + """, + ), + 'identityUpdate': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const identity = await sdk.identities.fetch('{identity}'); - const signer = new IdentitySigner(); - // Master key is required to add/disable identity keys. - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - - await sdk.identities.update({{ + """, + signer_master, + """ + await sdk.identities.update({ identity, addPublicKeys: undefined, // optional IdentityPublicKeyInCreation[] disablePublicKeys: [2], // optional key ids to disable signer, - }}); - """), + }); + """, + ), # Data contracts - 'dataContractCreate': example(f""" + 'dataContractCreate': compose_example( + f""" import {{ DataContract, IdentitySigner }} from '@dashevo/evo-sdk'; const ownerId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - - const keys = await sdk.identities.getKeys({{ + """, + identity_signer_setup(), + """ + const keys = await sdk.identities.getKeys({ identityId: ownerId, - request: {{ type: 'all' }}, - }}); + request: { type: 'all' }, + }); // Contract publish requires a CRITICAL authentication key. const identityKey = keys.find( k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'CRITICAL', ); - + """, + """ const identityNonce = (await sdk.identities.nonce(ownerId)) ?? 0n; - const dataContract = new DataContract({{ + const dataContract = new DataContract({ ownerId, identityNonce: identityNonce + 1n, - schemas: {{ - note: {{ + schemas: { + note: { type: 'object', - properties: {{ - message: {{ type: 'string', maxLength: 200, position: 0 }}, - }}, + properties: { + message: { type: 'string', maxLength: 200, position: 0 }, + }, required: ['message'], additionalProperties: false, - }}, - }}, - }}); + }, + }, + }); - const published = await sdk.contracts.publish({{ + const published = await sdk.contracts.publish({ dataContract, identityKey, signer, - }}); - """), - 'dataContractUpdate': example(f""" + }); + """, + ), + 'dataContractUpdate': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const ownerId = '{identity}'; @@ -650,36 +795,22 @@ def evo_example_for_transition(key: str): dataContract.version = (dataContract.version || 1) + 1; // Optionally merge additional document type schemas before update: // dataContract.setSchemas(mergedSchemas, undefined, false, sdk.version()); - - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ - identityId: ownerId, - request: {{ type: 'all' }}, - }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'CRITICAL', - ); - - await sdk.contracts.update({{ dataContract, identityKey, signer }}); - """), + """, + signer_and_contract_key, + """ + await sdk.contracts.update({ dataContract, identityKey, signer }); + """, + ), # Documents - 'documentCreate': example(f""" + 'documentCreate': compose_example( + f""" import {{ Document, IdentitySigner }} from '@dashevo/evo-sdk'; const ownerId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - - const keys = await sdk.identities.getKeys({{ - identityId: ownerId, - request: {{ type: 'all' }}, - }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_doc_key, + f""" const document = new Document({{ dataContractId: '{contract}', documentTypeName: '{document_type}', @@ -688,21 +819,16 @@ def evo_example_for_transition(key: str): }}); await sdk.documents.create({{ document, identityKey, signer }}); - """), - 'documentReplace': example(f""" + """, + ), + 'documentReplace': compose_example( + f""" import {{ Document, IdentitySigner }} from '@dashevo/evo-sdk'; const ownerId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ - identityId: ownerId, - request: {{ type: 'all' }}, - }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_doc_key, + f""" // Fetch current document, then rebuild/replace with revision + 1. const current = await sdk.documents.get('{contract}', '{document_type}', '{document_id}'); const document = new Document({{ @@ -715,21 +841,16 @@ def evo_example_for_transition(key: str): }}); await sdk.documents.replace({{ document, identityKey, signer }}); - """), - 'documentDelete': example(f""" + """, + ), + 'documentDelete': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const ownerId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ - identityId: ownerId, - request: {{ type: 'all' }}, - }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_doc_key, + f""" await sdk.documents.delete({{ document: {{ id: '{document_id}', @@ -740,45 +861,35 @@ def evo_example_for_transition(key: str): identityKey, signer, }}); - """), - 'documentTransfer': example(f""" + """, + ), + 'documentTransfer': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const ownerId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ - identityId: ownerId, - request: {{ type: 'all' }}, - }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_doc_key, + f""" const document = await sdk.documents.get('{contract}', '{document_type}', '{document_id}'); document.revision = BigInt(document.revision) + 1n; await sdk.documents.transfer({{ document, - recipientId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + recipientId: '{recipient}', identityKey, signer, }}); - """), - 'documentPurchase': example(f""" + """, + ), + 'documentPurchase': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const buyerId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ - identityId: buyerId, - request: {{ type: 'all' }}, - }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_buyer_key, + f""" const document = await sdk.documents.get('{contract}', '{document_type}', '{document_id}'); document.revision = BigInt(document.revision) + 1n; @@ -789,21 +900,16 @@ def evo_example_for_transition(key: str): identityKey, signer, }}); - """), - 'documentSetPrice': example(f""" + """, + ), + 'documentSetPrice': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const ownerId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ - identityId: ownerId, - request: {{ type: 'all' }}, - }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_doc_key, + f""" const document = await sdk.documents.get('{contract}', '{document_type}', '{document_id}'); document.revision = BigInt(document.revision) + 1n; @@ -813,20 +919,18 @@ def evo_example_for_transition(key: str): identityKey, signer, }}); - """), + """, + ), # Tokens - 'tokenMint': example(f""" + 'tokenMint': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const identityId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ identityId, request: {{ type: 'all' }} }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_identity_key, + f""" const result = await sdk.tokens.mint({{ dataContractId: '{contract}', tokenPosition: 0, @@ -837,18 +941,16 @@ def evo_example_for_transition(key: str): identityKey, signer, }}); - """), - 'tokenBurn': example(f""" + """, + ), + 'tokenBurn': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const identityId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ identityId, request: {{ type: 'all' }} }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_identity_key, + f""" const result = await sdk.tokens.burn({{ dataContractId: '{contract}', tokenPosition: 0, @@ -858,103 +960,93 @@ def evo_example_for_transition(key: str): identityKey, signer, }}); - """), - 'tokenTransfer': example(f""" + """, + ), + 'tokenTransfer': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const senderId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ identityId: senderId, request: {{ type: 'all' }} }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_sender_key, + f""" const result = await sdk.tokens.transfer({{ dataContractId: '{contract}', tokenPosition: 0, amount: 5n, senderId, - recipientId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + recipientId: '{recipient}', publicNote: 'transfer', identityKey, signer, }}); - """), - 'tokenFreeze': example(f""" + """, + ), + 'tokenFreeze': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const authorityId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ identityId: authorityId, request: {{ type: 'all' }} }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_authority_key, + f""" await sdk.tokens.freeze({{ dataContractId: '{contract}', tokenPosition: 0, authorityId, - frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + frozenIdentityId: '{recipient}', publicNote: 'freeze', identityKey, signer, }}); - """), - 'tokenUnfreeze': example(f""" + """, + ), + 'tokenUnfreeze': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const authorityId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ identityId: authorityId, request: {{ type: 'all' }} }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_authority_key, + f""" await sdk.tokens.unfreeze({{ dataContractId: '{contract}', tokenPosition: 0, authorityId, - frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + frozenIdentityId: '{recipient}', publicNote: 'unfreeze', identityKey, signer, }}); - """), - 'tokenDestroyFrozen': example(f""" + """, + ), + 'tokenDestroyFrozen': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const authorityId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ identityId: authorityId, request: {{ type: 'all' }} }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_authority_key, + f""" await sdk.tokens.destroyFrozen({{ dataContractId: '{contract}', tokenPosition: 0, authorityId, - frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', + frozenIdentityId: '{recipient}', publicNote: 'destroy', identityKey, signer, }}); - """), - 'tokenSetPriceForDirectPurchase': example(f""" + """, + ), + 'tokenSetPriceForDirectPurchase': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const authorityId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ identityId: authorityId, request: {{ type: 'all' }} }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_authority_key, + f""" await sdk.tokens.setPrice({{ dataContractId: '{contract}', tokenPosition: 0, @@ -964,18 +1056,16 @@ def evo_example_for_transition(key: str): identityKey, signer, }}); - """), - 'tokenDirectPurchase': example(f""" + """, + ), + 'tokenDirectPurchase': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const buyerId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ identityId: buyerId, request: {{ type: 'all' }} }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_auth_key_setup('buyerId', compact=True, blank_before_keys=False), + f""" const result = await sdk.tokens.directPurchase({{ dataContractId: '{contract}', tokenPosition: 0, @@ -985,18 +1075,16 @@ def evo_example_for_transition(key: str): identityKey, signer, }}); - """), - 'tokenClaim': example(f""" + """, + ), + 'tokenClaim': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const identityId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ identityId, request: {{ type: 'all' }} }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_identity_key, + f""" const result = await sdk.tokens.claim({{ dataContractId: '{contract}', tokenPosition: 0, @@ -1006,18 +1094,16 @@ def evo_example_for_transition(key: str): identityKey, signer, }}); - """), - 'tokenEmergencyAction': example(f""" + """, + ), + 'tokenEmergencyAction': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const authorityId = '{identity}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - const keys = await sdk.identities.getKeys({{ identityId: authorityId, request: {{ type: 'all' }} }}); - const identityKey = keys.find( - k => k.purpose === 'AUTHENTICATION' && ['CRITICAL', 'HIGH', 'MEDIUM'].includes(k.securityLevel), - ); - + """, + signer_and_authority_key, + f""" await sdk.tokens.emergencyAction({{ dataContractId: '{contract}', tokenPosition: 0, @@ -1027,38 +1113,44 @@ def evo_example_for_transition(key: str): identityKey, signer, }}); - """), + """, + ), # DPNS / voting - 'dpnsRegister': example(f""" + 'dpnsRegister': compose_example( + f""" import {{ IdentitySigner }} from '@dashevo/evo-sdk'; const identityId = '{identity}'; const identity = await sdk.identities.fetch(identityId); - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + """, + signer_only, + """ // HIGH authentication key is typically used for DPNS registration. const identityKey = identity.getPublicKeyById(1) || identity.publicKeys.find( k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'HIGH', ); - const result = await sdk.dpns.registerName({{ + const result = await sdk.dpns.registerName({ label: 'alice', identity, identityKey, signer, - preorderCallback: (preorderDocument) => {{ + preorderCallback: (preorderDocument) => { console.log('preorder submitted', preorderDocument.id?.toString?.()); - }}, - }}); - """), - 'dpnsUsername': example(f""" + }, + }); + """, + ), + 'dpnsUsername': compose_example( + f""" import {{ IdentitySigner, ResourceVoteChoice, VotePoll }} from '@dashevo/evo-sdk'; const masternodeProTxHash = '{pro_tx}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExampleVotingKeyWifGoesHere'); + """, + signer_voting, + f""" // Voting key must match the masternode voting public key on the identity. const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING') @@ -1078,13 +1170,16 @@ def evo_example_for_transition(key: str): votingKey, signer, }}); - """), - 'masternodeVote': example(f""" + """, + ), + 'masternodeVote': compose_example( + f""" import {{ IdentitySigner, ResourceVoteChoice, VotePoll }} from '@dashevo/evo-sdk'; const masternodeProTxHash = '{pro_tx}'; - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExampleVotingKeyWifGoesHere'); + """, + signer_voting, + f""" const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING') || votingIdentity.getPublicKeyById(0); @@ -1103,10 +1198,12 @@ def evo_example_for_transition(key: str): votingKey, signer, }}); - """), + """, + ), # Platform Addresses — keep address-signer vs identity-signer distinction - 'addressTransfer': example(f""" + 'addressTransfer': compose_example( + f""" import {{ PlatformAddressInput, PlatformAddressOutput, @@ -1129,8 +1226,10 @@ def evo_example_for_transition(key: str): outputs: [output], signer, }}); - """), - 'addressTopUpIdentity': example(f""" + """, + ), + 'addressTopUpIdentity': compose_example( + f""" import {{ PlatformAddressInput, PlatformAddressSigner, PrivateKey }} from '@dashevo/evo-sdk'; const identity = await sdk.identities.fetch('{identity}'); @@ -1144,9 +1243,11 @@ def evo_example_for_transition(key: str): inputs: [input], signer, }}); - """), - 'addressWithdraw': example(f""" - import {{ PlatformAddressInput, PlatformAddressSigner, PrivateKey }} from '@dashevo/evo-sdk'; + """, + ), + 'addressWithdraw': compose_example( + """ + import { PlatformAddressInput, PlatformAddressSigner, PrivateKey } from '@dashevo/evo-sdk'; const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); const signer = new PlatformAddressSigner(); @@ -1154,36 +1255,40 @@ def evo_example_for_transition(key: str): const input = new PlatformAddressInput(platformAddr, 0, 100000n); // Provide a Core L1 output script (e.g. CoreScript.newP2PKH(...)). - const result = await sdk.addresses.withdraw({{ + const result = await sdk.addresses.withdraw({ inputs: [input], coreFeePerByte: 1, pooling: undefined, outputScript: /* CoreScript */ undefined, signer, - }}); - """), - 'addressTransferFromIdentity': example(f""" + }); + """, + ), + 'addressTransferFromIdentity': compose_example( + f""" import {{ IdentitySigner, PlatformAddressOutput }} from '@dashevo/evo-sdk'; // Uses IdentitySigner (identity transfer key), not PlatformAddressSigner. const identity = await sdk.identities.fetch('{identity}'); - const signer = new IdentitySigner(); - signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); - + """, + signer_only, + f""" const output = new PlatformAddressOutput('{platform_address}', 100000n); const result = await sdk.addresses.transferFromIdentity({{ identity, outputs: [output], signer, }}); - """), - 'addressFundFromAssetLock': example(f""" - import {{ + """, + ), + 'addressFundFromAssetLock': compose_example( + """ + import { AssetLockProof, PlatformAddressOutput, PlatformAddressSigner, PrivateKey, - }} from '@dashevo/evo-sdk'; + } from '@dashevo/evo-sdk'; // Asset-lock key signs the L1 funding proof; address signer controls outputs. const assetLockPrivateKey = PrivateKey.fromWIF('cAssetLockPrivateKeyWif...'); @@ -1194,15 +1299,17 @@ def evo_example_for_transition(key: str): const platformAddr = signer.addKey(addressPrivateKey); const output = new PlatformAddressOutput(platformAddr, 100000n); - const result = await sdk.addresses.fundFromAssetLock({{ + const result = await sdk.addresses.fundFromAssetLock({ assetLockProof, assetLockPrivateKey, outputs: [output], signer, - }}); - """), - 'addressCreateIdentity': example(f""" - import {{ + }); + """, + ), + 'addressCreateIdentity': compose_example( + """ + import { Identity, IdentityPublicKeyInCreation, IdentitySigner, @@ -1212,20 +1319,20 @@ def evo_example_for_transition(key: str): PrivateKey, Purpose, SecurityLevel, - }} from '@dashevo/evo-sdk'; + } from '@dashevo/evo-sdk'; const addressPrivateKey = PrivateKey.fromWIF('cAddressPrivateKeyWif...'); const identityPrivateKey = PrivateKey.fromWIF('cIdentityKeyWif...'); const identity = new Identity(/* 32-byte id */ new Uint8Array(32)); identity.addPublicKey( - new IdentityPublicKeyInCreation({{ + new IdentityPublicKeyInCreation({ keyId: 0, purpose: Purpose.AUTHENTICATION, securityLevel: SecurityLevel.MASTER, keyType: KeyType.ECDSA_SECP256K1, data: identityPrivateKey.getPublicKey().toBytes(), - }}).toIdentityPublicKey(), + }).toIdentityPublicKey(), ); const addressSigner = new PlatformAddressSigner(); @@ -1234,13 +1341,14 @@ def evo_example_for_transition(key: str): identitySigner.addKey(identityPrivateKey); const input = new PlatformAddressInput(sourceAddr, 0, 50_000_000_000n); - const result = await sdk.addresses.createIdentity({{ + const result = await sdk.addresses.createIdentity({ identity, inputs: [input], identitySigner, addressSigner, - }}); - """), + }); + """, + ), } return m.get(key) @@ -1301,29 +1409,21 @@ def render_parameters(params: List[dict]) -> str: def format_example(code: str, header: str) -> str: - formatted = (code or '').replace('\\n', '\n').strip() - if not formatted: + body_lines = normalize_example_lines(code) + if not body_lines: return header - body_lines = [re.sub(r'\bclient\b', 'sdk', line) for line in formatted.split('\n')] - # Only auto-prefix `return` for single-expression snippets. Multi-line # examples already contain setup + an explicit final call/return. - code_line_indexes = [ - index - for index, line in enumerate(body_lines) - if line.strip() and not line.strip().startswith('//') - ] - if len(code_line_indexes) == 1: - index = code_line_indexes[0] + indexes = code_line_indexes(body_lines) + if len(indexes) == 1: + index = indexes[0] stripped = body_lines[index].lstrip() indent = body_lines[index][: len(body_lines[index]) - len(stripped)] if not stripped.startswith('return '): body_lines[index] = f'{indent}return {stripped}' - lines = [header] - lines.extend(body_lines) - return '\n'.join(lines).rstrip() + return '\n'.join([header, *body_lines]).rstrip() def render_operation( prefix: str, @@ -1961,26 +2061,20 @@ def generate_docs_html(query_defs: dict, transition_defs: dict, type_metadata: d def format_ai_example_block(code: str | None, item_key: str) -> str: - snippet = (code or '').replace('\\n', '\n').strip() - if not snippet: + processed = normalize_example_lines(code) + if not processed: return f"// Example currently unavailable for `{item_key}`" - raw_lines = snippet.split('\n') - processed = [re.sub(r'\bclient\b', 'sdk', line) for line in raw_lines] - # Multi-line examples already declare imports/setup and a final call — leave intact. - code_line_indexes = [ - index - for index, line in enumerate(processed) - if line.strip() and not line.strip().startswith('//') - ] - if len(code_line_indexes) != 1: + indexes = code_line_indexes(processed) + if len(indexes) != 1: return '\n'.join(processed).rstrip() - first_index = code_line_indexes[0] + first_index = indexes[0] first_line = processed[first_index] stripped = first_line.lstrip() indent = first_line[: len(first_line) - len(stripped)] + snippet = '\n'.join(processed) already_has_declaration = ( stripped.startswith('const ') @@ -1994,7 +2088,7 @@ def format_ai_example_block(code: str | None, item_key: str) -> str: if not already_has_declaration: if stripped.startswith('return '): stripped = stripped[len('return ') :].lstrip() - processed[first_index] = f"{indent}const result = {stripped}" + processed[first_index] = f'{indent}const result = {stripped}' joined = '\n'.join(processed).rstrip() if joined and not joined.endswith(';'): diff --git a/tests/unit/transition-examples.test.js b/tests/unit/transition-examples.test.js index 9c12df2..38300fb 100644 --- a/tests/unit/transition-examples.test.js +++ b/tests/unit/transition-examples.test.js @@ -11,9 +11,11 @@ const aiReference = fs.readFileSync(path.join(ROOT, 'public/AI_REFERENCE.md'), ' const apiDefinitions = JSON.parse(fs.readFileSync(path.join(ROOT, 'public/api-definitions.json'), 'utf8')); const wasmSdkDts = fs.readFileSync(path.join(ROOT, 'node_modules/@dashevo/wasm-sdk/dist/sdk.d.ts'), 'utf8'); -const TRANSITION_KEYS = Object.values(apiDefinitions.transitions).flatMap((category) => - Object.keys(category.transitions || {}), +const TRANSITION_ENTRIES = Object.values(apiDefinitions.transitions).flatMap((category) => + Object.entries(category.transitions || {}), ); +const TRANSITION_KEYS = TRANSITION_ENTRIES.map(([key]) => key); +const TRANSITION_BY_KEY = Object.fromEntries(TRANSITION_ENTRIES); const METHOD_TO_OPTIONS = { 'identities.create': 'IdentityCreateOptions', @@ -50,7 +52,7 @@ const METHOD_TO_OPTIONS = { }; /** Pre-v4 call-shape markers that must not appear as options on SDK write calls. */ -const FORBIDDEN_CALL_OPTION_NAMES = [ +const FORBIDDEN_CALL_OPTION_NAMES = new Set([ 'privateKeyWif', 'assetLockPrivateKeyWif', 'votingKeyWif', @@ -68,6 +70,14 @@ const FORBIDDEN_CALL_OPTION_NAMES = [ 'destroyerId', 'definition', 'updates', +]); + +/** Classic one-liners from issue #63 that must not reappear in generated docs. */ +const CLASSIC_PRE_V4_PATTERNS = [ + /documents\.create\(\{\s*contractId,\s*type:\s*documentType,\s*ownerId,\s*data,\s*entropyHex,\s*privateKeyWif\s*\}\)/, + /identities\.topUp\(\{\s*identityId,\s*assetLockProof,\s*assetLockPrivateKeyWif\s*\}\)/, + /identities\.create\(\{\s*assetLockProof,\s*assetLockPrivateKeyWif,\s*publicKeys\s*\}\)/, + /sdk\.\.\(\{\s*\.\.\.params,\s*privateKeyWif\s*\}\)/, ]; function loadTransitionExamplesFromGenerator() { @@ -96,12 +106,8 @@ print(json.dumps(out)) function interfaceProperties(sourceText, interfaceName) { const source = ts.createSourceFile('sdk.d.ts', sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); const props = new Set(); - const visit = (node) => { - if ( - ts.isInterfaceDeclaration(node) - && node.name.text === interfaceName - && node.members - ) { + function visit(node) { + if (ts.isInterfaceDeclaration(node) && node.name.text === interfaceName && node.members) { for (const member of node.members) { if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) { props.add(member.name.text); @@ -109,7 +115,7 @@ function interfaceProperties(sourceText, interfaceName) { } } ts.forEachChild(node, visit); - }; + } visit(source); return props; } @@ -136,28 +142,35 @@ function extractSdkCallSites(example) { } } if (end === -1) continue; - const argsText = example.slice(openIdx + 1, end).trim(); - calls.push({ method: `${namespace}.${method}`, argsText }); + calls.push({ + method: `${namespace}.${method}`, + argsText: example.slice(openIdx + 1, end).trim(), + }); } return calls; } function topLevelObjectKeys(argsText) { - // Expect a single object literal argument: { ... } const trimmed = argsText.trim(); if (!trimmed.startsWith('{')) return []; - const source = ts.createSourceFile('example.ts', `const __opts = ${trimmed};`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + const source = ts.createSourceFile( + 'example.ts', + `const __opts = ${trimmed};`, + ts.ScriptTarget.Latest, + true, + ts.ScriptKind.TS, + ); const keys = []; - const visit = (node) => { + function visit(node) { if (ts.isObjectLiteralExpression(node) && node.parent && ts.isVariableDeclaration(node.parent)) { for (const prop of node.properties) { - if (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) { - if (ts.isIdentifier(prop.name)) keys.push(prop.name.text); + if ((ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) && ts.isIdentifier(prop.name)) { + keys.push(prop.name.text); } } } ts.forEachChild(node, visit); - }; + } visit(source); return keys; } @@ -165,25 +178,25 @@ function topLevelObjectKeys(argsText) { function exampleMentionsForbiddenCallOption(example) { const hits = []; for (const call of extractSdkCallSites(example)) { - const keys = topLevelObjectKeys(call.argsText); - for (const key of keys) { - if (FORBIDDEN_CALL_OPTION_NAMES.includes(key)) { + for (const key of topLevelObjectKeys(call.argsText)) { + if (FORBIDDEN_CALL_OPTION_NAMES.has(key)) { hits.push(`${call.method}({ ${key} })`); } } } - // Also catch single-line pre-v4 shapes that put privateKeyWif in the same object as contractId/type. - if (/\{\s*[^}]*\bprivateKeyWif\b[^}]*\}/.test(example)) { - // Only flag when it is not purely a key-construction object for IdentityPublicKeyInCreation etc. - // Those objects use privateKeyWif only in the removed pre-v4 publicKeys JSON; current examples - // should not include privateKeyWif as an object property at all. - if (/\bprivateKeyWif\s*:/.test(example)) { - hits.push('object property privateKeyWif'); - } + // Current examples must not document privateKeyWif as an object property at all. + if (/\bprivateKeyWif\s*:/.test(example)) { + hits.push('object property privateKeyWif'); } return hits; } +function collectClassicPreV4Hits(text, label) { + return CLASSIC_PRE_V4_PATTERNS + .filter((re) => re.test(text)) + .map((re) => `${label} still contains ${re}`); +} + const generatedExamples = loadTransitionExamplesFromGenerator(); describe('v4 state transition documentation examples', () => { @@ -199,18 +212,8 @@ describe('v4 state transition documentation examples', () => { const hits = exampleMentionsForbiddenCallOption(example); if (hits.length) failures.push(`${key}: ${hits.join(', ')}`); } - // Generated artifacts must also be free of the classic one-liners from issue #63. - for (const [label, text] of [['docs.html', docs], ['AI_REFERENCE.md', aiReference]]) { - const classic = [ - /documents\.create\(\{\s*contractId,\s*type:\s*documentType,\s*ownerId,\s*data,\s*entropyHex,\s*privateKeyWif\s*\}\)/, - /identities\.topUp\(\{\s*identityId,\s*assetLockProof,\s*assetLockPrivateKeyWif\s*\}\)/, - /identities\.create\(\{\s*assetLockProof,\s*assetLockPrivateKeyWif,\s*publicKeys\s*\}\)/, - /sdk\.\.\(\{\s*\.\.\.params,\s*privateKeyWif\s*\}\)/, - ]; - for (const re of classic) { - if (re.test(text)) failures.push(`${label} still contains ${re}`); - } - } + failures.push(...collectClassicPreV4Hits(docs, 'docs.html')); + failures.push(...collectClassicPreV4Hits(aiReference, 'AI_REFERENCE.md')); expect(failures).toEqual([]); }); @@ -234,13 +237,7 @@ describe('v4 state transition documentation examples', () => { it('passes only declared option properties on the final sdk write call', () => { const failures = []; - for (const [key, item] of Object.entries( - Object.fromEntries( - Object.values(apiDefinitions.transitions).flatMap((category) => - Object.entries(category.transitions || {}), - ), - ), - )) { + for (const [key, item] of Object.entries(TRANSITION_BY_KEY)) { const example = generatedExamples[key]; const sdkMethod = item.sdk_method; const optionsName = METHOD_TO_OPTIONS[sdkMethod]; @@ -248,6 +245,7 @@ describe('v4 state transition documentation examples', () => { failures.push(`${key}: no Options mapping for ${sdkMethod}`); continue; } + const allowed = interfaceProperties(wasmSdkDts, optionsName); // TypeScript may declare the same interface name more than once in the giant d.ts // (transition object variants). Prefer the richest write-options set when ambiguous. @@ -261,15 +259,19 @@ describe('v4 state transition documentation examples', () => { failures.push(`${key}: no await sdk.${sdkMethod}(...) call in example`); continue; } - const call = writeCalls[writeCalls.length - 1]; - const keys = topLevelObjectKeys(call.argsText); + + const keys = topLevelObjectKeys(writeCalls[writeCalls.length - 1].argsText); if (!keys.length) { failures.push(`${key}: could not parse options object for ${sdkMethod}`); continue; } + const unknown = keys.filter((k) => !allowed.has(k)); if (unknown.length) { - failures.push(`${key}: unknown option(s) on ${sdkMethod}: ${unknown.join(', ')} (allowed: ${[...allowed].sort().join(', ')})`); + failures.push( + `${key}: unknown option(s) on ${sdkMethod}: ${unknown.join(', ')} ` + + `(allowed: ${[...allowed].sort().join(', ')})`, + ); } } expect(failures).toEqual([]); From edeb92fdc72c581a679dee7007b16427b5839893 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 15:12:20 -0500 Subject: [PATCH 06/22] fix: use concrete CoreScript/PoolingWasm in withdraw example AddressFundsWithdrawOptions requires pooling and outputScript values. Use PoolingWasm.Standard and CoreScript.fromP2PKH instead of undefined placeholders and the nonexistent CoreScript.newP2PKH helper. Co-Authored-By: Claude --- scripts/generate_docs.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index b8189ed..b7d9d1e 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -1247,19 +1247,28 @@ def evo_example_for_transition(key: str): ), 'addressWithdraw': compose_example( """ - import { PlatformAddressInput, PlatformAddressSigner, PrivateKey } from '@dashevo/evo-sdk'; + import { + CoreScript, + PlatformAddressInput, + PlatformAddressSigner, + PoolingWasm, + PrivateKey, + } from '@dashevo/evo-sdk'; const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); const signer = new PlatformAddressSigner(); const platformAddr = signer.addKey(privateKey); const input = new PlatformAddressInput(platformAddr, 0, 100000n); - // Provide a Core L1 output script (e.g. CoreScript.newP2PKH(...)). + // 20-byte pubkey hash for the Core L1 P2PKH destination. + const corePubkeyHash = new Uint8Array(20); + const outputScript = CoreScript.fromP2PKH(corePubkeyHash); + const result = await sdk.addresses.withdraw({ inputs: [input], coreFeePerByte: 1, - pooling: undefined, - outputScript: /* CoreScript */ undefined, + pooling: PoolingWasm.Standard, + outputScript, signer, }); """, From fa1e6e7a9f2c58b761ed5b28e62cd8007aa82f04 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 15:12:20 -0500 Subject: [PATCH 07/22] fix: align Platform Address sdk_params with v4 option types Top-up and identity-to-address transfer take Identity, not identityId. Withdrawal requires coreFeePerByte, Pooling, CoreScript, and PlatformAddressSigner. Asset-lock funding requires AssetLockProof, PrivateKey, outputs, and a required PlatformAddressSigner. Co-Authored-By: Claude --- public/api-definitions.json | 66 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/public/api-definitions.json b/public/api-definitions.json index 466f184..abba7a0 100644 --- a/public/api-definitions.json +++ b/public/api-definitions.json @@ -2584,25 +2584,25 @@ ], "sdk_params": [ { - "name": "identityId", - "type": "string", - "label": "Identity ID", + "name": "identity", + "type": "Identity", + "label": "Identity", "required": true, - "description": "Base58 identity ID to top up" + "description": "Fetched Identity object to top up (sdk.identities.fetch)." }, { "name": "inputs", "type": "array", "label": "Inputs", "required": true, - "description": "Array of {address, nonce, amount (credits)} objects" + "description": "Array of PlatformAddressInput (or {address, nonce, amount (credits)}) objects" }, { "name": "signer", - "type": "object", + "type": "PlatformAddressSigner", "label": "Signer", "required": true, - "description": "PlatformAddressSigner instance" + "description": "PlatformAddressSigner containing keys for the input addresses" } ], "sdk_method": "addresses.topUpIdentity" @@ -2643,7 +2643,7 @@ "name": "coreFeePerByte", "type": "number", "label": "Core Fee Per Byte", - "required": false, + "required": true, "placeholder": "1" } ], @@ -2653,35 +2653,35 @@ "type": "array", "label": "Inputs", "required": true, - "description": "Array of {address, nonce, amount (credits)} objects" + "description": "Array of PlatformAddressInput (or {address, nonce, amount (credits)}) objects" }, { "name": "coreFeePerByte", "type": "number", "label": "Core Fee Per Byte", - "required": false, - "description": "Fee per byte for Core transaction" + "required": true, + "description": "Core (L1) mining fee per byte for the withdrawal transaction" }, { "name": "pooling", - "type": "string", + "type": "Pooling", "label": "Pooling", - "required": false, - "description": "Pooling strategy" + "required": true, + "description": "Pooling strategy (PoolingWasm.Never / IfAvailable / Standard)" }, { "name": "outputScript", - "type": "string", + "type": "CoreScript", "label": "Output Script", "required": true, - "description": "Dash Core output script" + "description": "Core L1 destination script (CoreScript.fromP2PKH / fromP2SH)" }, { "name": "signer", - "type": "object", + "type": "PlatformAddressSigner", "label": "Signer", "required": true, - "description": "PlatformAddressSigner instance" + "description": "PlatformAddressSigner containing keys for the input addresses" } ], "sdk_method": "addresses.withdraw" @@ -2707,25 +2707,25 @@ ], "sdk_params": [ { - "name": "identityId", - "type": "string", - "label": "Identity ID", + "name": "identity", + "type": "Identity", + "label": "Identity", "required": true, - "description": "Base58 identity ID to transfer from" + "description": "Fetched Identity object to transfer from (sdk.identities.fetch)." }, { "name": "outputs", "type": "array", "label": "Outputs", "required": true, - "description": "Array of {address, amount (credits)} objects for recipient addresses" + "description": "Array of PlatformAddressOutput (or {address, amount (credits)}) objects for recipient addresses" }, { "name": "signer", - "type": "object", + "type": "IdentitySigner", "label": "Signer", "required": true, - "description": "IdentitySigner instance" + "description": "IdentitySigner with the identity transfer key" } ], "sdk_method": "addresses.transferFromIdentity" @@ -2752,31 +2752,31 @@ "sdk_params": [ { "name": "assetLockProof", - "type": "string", + "type": "AssetLockProof", "label": "Asset Lock Proof", "required": true, - "description": "Hex-encoded asset lock proof" + "description": "AssetLockProof from Core (AssetLockProof.fromHex / createInstantAssetLockProof / createChainAssetLockProof)." }, { "name": "assetLockPrivateKey", - "type": "string", + "type": "PrivateKey", "label": "Asset Lock Private Key", "required": true, - "description": "WIF private key for asset lock" + "description": "PrivateKey controlling the asset-lock output (PrivateKey.fromWIF)." }, { "name": "outputs", "type": "array", "label": "Outputs", "required": true, - "description": "Array of {address, amount (credits)} objects" + "description": "Array of PlatformAddressOutput (or {address, amount (credits)}) objects" }, { "name": "signer", - "type": "object", + "type": "PlatformAddressSigner", "label": "Signer", - "required": false, - "description": "Optional PlatformAddressSigner instance" + "required": true, + "description": "PlatformAddressSigner containing keys for the funded output addresses" } ], "sdk_method": "addresses.fundFromAssetLock" From c09ef021ce89260f9a931e0662e3985b03faf0a2 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 15:12:20 -0500 Subject: [PATCH 08/22] test: guard Platform Address required options and metadata Assert withdraw examples use CoreScript.fromP2PKH/PoolingWasm, and that top-up, transfer-from-identity, withdraw, and fund-from-asset-lock sdk_params match v4 required option members/types. Co-Authored-By: Claude --- tests/unit/transition-examples.test.js | 110 +++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/tests/unit/transition-examples.test.js b/tests/unit/transition-examples.test.js index 38300fb..8e4043b 100644 --- a/tests/unit/transition-examples.test.js +++ b/tests/unit/transition-examples.test.js @@ -120,6 +120,35 @@ function interfaceProperties(sourceText, interfaceName) { return props; } +/** Required property names from the first matching interface declaration. */ +function interfaceRequiredProperties(sourceText, interfaceName) { + const source = ts.createSourceFile('sdk.d.ts', sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + const required = new Set(); + function visit(node) { + if (ts.isInterfaceDeclaration(node) && node.name.text === interfaceName && node.members) { + for (const member of node.members) { + if ( + ts.isPropertySignature(member) + && member.name + && ts.isIdentifier(member.name) + && !member.questionToken + ) { + required.add(member.name.text); + } + } + return; + } + ts.forEachChild(node, visit); + } + visit(source); + return required; +} + +function sdkParamByName(transitionKey, paramName) { + const params = TRANSITION_BY_KEY[transitionKey]?.sdk_params || []; + return params.find((p) => p.name === paramName); +} + function extractSdkCallSites(example) { const calls = []; const callRe = /await\s+sdk\.([a-zA-Z0-9_]+)\.([a-zA-Z0-9_]+)\s*\(/g; @@ -285,4 +314,85 @@ describe('v4 state transition documentation examples', () => { expect(aiReference).toContain('typed options object'); expect(aiReference).not.toContain('{ ...params, privateKeyWif }'); }); + + it('uses concrete CoreScript / PoolingWasm values for address withdraw', () => { + const example = generatedExamples.addressWithdraw; + expect(example).toMatch(/CoreScript\.fromP2PKH/); + expect(example).toMatch(/PoolingWasm\.Standard/); + expect(example).not.toMatch(/CoreScript\.newP2PKH/); + expect(example).not.toMatch(/pooling:\s*undefined/); + expect(example).not.toMatch(/outputScript:\s*(?:\/\*[^*]*\*\/\s*)?undefined/); + + const writeCall = extractSdkCallSites(example).find((call) => call.method === 'addresses.withdraw'); + expect(writeCall).toBeTruthy(); + const keys = topLevelObjectKeys(writeCall.argsText); + const required = interfaceRequiredProperties(wasmSdkDts, 'AddressFundsWithdrawOptions'); + for (const name of required) { + expect(keys, `missing required option ${name}`).toContain(name); + } + }); + + it('documents Platform Address option metadata with v4 types and requiredness', () => { + expect(sdkParamByName('addressTopUpIdentity', 'identity')).toMatchObject({ + type: 'Identity', + required: true, + }); + expect(sdkParamByName('addressTopUpIdentity', 'identityId')).toBeUndefined(); + + expect(sdkParamByName('addressTransferFromIdentity', 'identity')).toMatchObject({ + type: 'Identity', + required: true, + }); + expect(sdkParamByName('addressTransferFromIdentity', 'identityId')).toBeUndefined(); + + expect(sdkParamByName('addressWithdraw', 'coreFeePerByte')).toMatchObject({ + type: 'number', + required: true, + }); + expect(sdkParamByName('addressWithdraw', 'pooling')).toMatchObject({ + type: 'Pooling', + required: true, + }); + expect(sdkParamByName('addressWithdraw', 'outputScript')).toMatchObject({ + type: 'CoreScript', + required: true, + }); + expect(sdkParamByName('addressWithdraw', 'signer')).toMatchObject({ + type: 'PlatformAddressSigner', + required: true, + }); + + expect(sdkParamByName('addressFundFromAssetLock', 'assetLockProof')).toMatchObject({ + type: 'AssetLockProof', + required: true, + }); + expect(sdkParamByName('addressFundFromAssetLock', 'assetLockPrivateKey')).toMatchObject({ + type: 'PrivateKey', + required: true, + }); + expect(sdkParamByName('addressFundFromAssetLock', 'outputs')).toMatchObject({ + required: true, + }); + expect(sdkParamByName('addressFundFromAssetLock', 'signer')).toMatchObject({ + type: 'PlatformAddressSigner', + required: true, + }); + + // Example option objects must include every required declaration member. + const requiredChecks = [ + ['addressTopUpIdentity', 'addresses.topUpIdentity', 'IdentityTopUpFromAddressesOptions'], + ['addressTransferFromIdentity', 'addresses.transferFromIdentity', 'IdentityTransferToAddressesOptions'], + ['addressFundFromAssetLock', 'addresses.fundFromAssetLock', 'AddressFundingFromAssetLockOptions'], + ]; + for (const [key, method, optionsName] of requiredChecks) { + const example = generatedExamples[key]; + const writeCall = extractSdkCallSites(example).find((call) => call.method === method); + expect(writeCall, key).toBeTruthy(); + const keys = topLevelObjectKeys(writeCall.argsText); + const required = interfaceRequiredProperties(wasmSdkDts, optionsName); + for (const name of required) { + expect(keys, `${key} missing required option ${name}`).toContain(name); + } + } + }); }); From f279c3f20f5dce98f60e70f421c3de9cff1cf253 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 15:12:21 -0500 Subject: [PATCH 09/22] docs: regenerate AI_REFERENCE for Platform Address v4 metadata Co-Authored-By: Claude --- public/AI_REFERENCE.md | 105 ++++++++++++++++++-------- public/documentation-check-report.txt | 2 +- 2 files changed, 76 insertions(+), 31 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index 66a5b01..ea56619 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -1432,6 +1432,7 @@ import { // Asset lock from Core (hex) + the private key that controls that output. const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); + const assetLockPrivateKey = PrivateKey.fromWIF('cVExampleAssetLockKeyForIdentityFunding'); // Build the identity shell and attach public keys that will be registered. @@ -1510,6 +1511,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); + const signer = new IdentitySigner(); // Master key is required to add/disable identity keys. signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); @@ -1540,8 +1542,10 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + // Optional: pass signingKey when you need a specific transfer/auth key. const signingKey = identity.getPublicKeyById(3) // TRANSFER key when present || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); @@ -1574,8 +1578,10 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const signingKey = identity.getPublicKeyById(3) || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); @@ -1647,6 +1653,7 @@ Example: import { DataContract, IdentitySigner } from '@dashevo/evo-sdk'; const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); @@ -1727,6 +1734,7 @@ dataContract.version = (dataContract.version || 1) + 1; const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({ identityId: ownerId, request: { type: 'all' }, @@ -1761,6 +1769,7 @@ Example: import { Document, IdentitySigner } from '@dashevo/evo-sdk'; const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); @@ -1805,8 +1814,10 @@ Example: import { Document, IdentitySigner } from '@dashevo/evo-sdk'; const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({ identityId: ownerId, request: { type: 'all' }, @@ -1848,8 +1859,10 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({ identityId: ownerId, request: { type: 'all' }, @@ -1891,8 +1904,10 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({ identityId: ownerId, request: { type: 'all' }, @@ -1933,8 +1948,10 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const buyerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({ identityId: buyerId, request: { type: 'all' }, @@ -1976,8 +1993,10 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const ownerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + const keys = await sdk.identities.getKeys({ identityId: ownerId, request: { type: 'all' }, @@ -2015,8 +2034,10 @@ import { IdentitySigner } from '@dashevo/evo-sdk'; const identityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; const identity = await sdk.identities.fetch(identityId); + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + // HIGH authentication key is typically used for DPNS registration. const identityKey = identity.getPublicKeyById(1) || identity.publicKeys.find( @@ -2058,6 +2079,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const identityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const keys = await sdk.identities.getKeys({ identityId, request: { type: 'all' } }); @@ -2100,6 +2122,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const identityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const keys = await sdk.identities.getKeys({ identityId, request: { type: 'all' } }); @@ -2142,6 +2165,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const identityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const keys = await sdk.identities.getKeys({ identityId, request: { type: 'all' } }); @@ -2186,6 +2210,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const authorityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const keys = await sdk.identities.getKeys({ identityId: authorityId, request: { type: 'all' } }); @@ -2226,6 +2251,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const buyerId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const keys = await sdk.identities.getKeys({ identityId: buyerId, request: { type: 'all' } }); @@ -2267,6 +2293,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const authorityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const keys = await sdk.identities.getKeys({ identityId: authorityId, request: { type: 'all' } }); @@ -2309,6 +2336,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const senderId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const keys = await sdk.identities.getKeys({ identityId: senderId, request: { type: 'all' } }); @@ -2350,6 +2378,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const authorityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const keys = await sdk.identities.getKeys({ identityId: authorityId, request: { type: 'all' } }); @@ -2390,6 +2419,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const authorityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const keys = await sdk.identities.getKeys({ identityId: authorityId, request: { type: 'all' } }); @@ -2430,6 +2460,7 @@ Example: import { IdentitySigner } from '@dashevo/evo-sdk'; const authorityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); const keys = await sdk.identities.getKeys({ identityId: authorityId, request: { type: 'all' } }); @@ -2472,8 +2503,10 @@ Example: import { IdentitySigner, ResourceVoteChoice, VotePoll } from '@dashevo/evo-sdk'; const masternodeProTxHash = '143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExampleVotingKeyWifGoesHere'); + // Voting key must match the masternode voting public key on the identity. const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING') @@ -2521,8 +2554,10 @@ Example: import { IdentitySigner, ResourceVoteChoice, VotePoll } from '@dashevo/evo-sdk'; const masternodeProTxHash = '143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'; + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExampleVotingKeyWifGoesHere'); + const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING') || votingIdentity.getPublicKeyById(0); @@ -2593,14 +2628,14 @@ const result = await sdk.addresses.transfer({ *Top up an identity using Platform address credits* Parameters: -- `Identity ID` (string, required) - - Base58 identity ID to top up +- `Identity` (Identity, required) + - Fetched Identity object to top up (sdk.identities.fetch). - `Inputs` (array, required) - - Array of {address, nonce, amount (credits)} objects + - Array of PlatformAddressInput (or {address, nonce, amount (credits)}) objects -- `Signer` (object, required) - - PlatformAddressSigner instance +- `Signer` (PlatformAddressSigner, required) + - PlatformAddressSigner containing keys for the input addresses Returns: @@ -2629,19 +2664,19 @@ const result = await sdk.addresses.topUpIdentity({ Parameters: - `Inputs` (array, required) - - Array of {address, nonce, amount (credits)} objects + - Array of PlatformAddressInput (or {address, nonce, amount (credits)}) objects -- `Core Fee Per Byte` (number, optional) - - Fee per byte for Core transaction +- `Core Fee Per Byte` (number, required) + - Core (L1) mining fee per byte for the withdrawal transaction -- `Pooling` (string, optional) - - Pooling strategy +- `Pooling` (Pooling, required) + - Pooling strategy (PoolingWasm.Never / IfAvailable / Standard) -- `Output Script` (string, required) - - Dash Core output script +- `Output Script` (CoreScript, required) + - Core L1 destination script (CoreScript.fromP2PKH / fromP2SH) -- `Signer` (object, required) - - PlatformAddressSigner instance +- `Signer` (PlatformAddressSigner, required) + - PlatformAddressSigner containing keys for the input addresses Returns: @@ -2650,19 +2685,28 @@ Returns: Example: ```javascript -import { PlatformAddressInput, PlatformAddressSigner, PrivateKey } from '@dashevo/evo-sdk'; +import { + CoreScript, + PlatformAddressInput, + PlatformAddressSigner, + PoolingWasm, + PrivateKey, +} from '@dashevo/evo-sdk'; const privateKey = PrivateKey.fromWIF('cPrivateKeyWif...'); const signer = new PlatformAddressSigner(); const platformAddr = signer.addKey(privateKey); const input = new PlatformAddressInput(platformAddr, 0, 100000n); -// Provide a Core L1 output script (e.g. CoreScript.newP2PKH(...)). +// 20-byte pubkey hash for the Core L1 P2PKH destination. +const corePubkeyHash = new Uint8Array(20); +const outputScript = CoreScript.fromP2PKH(corePubkeyHash); + const result = await sdk.addresses.withdraw({ inputs: [input], coreFeePerByte: 1, - pooling: undefined, - outputScript: /* CoreScript */ undefined, + pooling: PoolingWasm.Standard, + outputScript, signer, }); ``` @@ -2671,14 +2715,14 @@ const result = await sdk.addresses.withdraw({ *Transfer credits from an identity to Platform addresses* Parameters: -- `Identity ID` (string, required) - - Base58 identity ID to transfer from +- `Identity` (Identity, required) + - Fetched Identity object to transfer from (sdk.identities.fetch). - `Outputs` (array, required) - - Array of {address, amount (credits)} objects for recipient addresses + - Array of PlatformAddressOutput (or {address, amount (credits)}) objects for recipient addresses -- `Signer` (object, required) - - IdentitySigner instance +- `Signer` (IdentitySigner, required) + - IdentitySigner with the identity transfer key Returns: @@ -2691,6 +2735,7 @@ import { IdentitySigner, PlatformAddressOutput } from '@dashevo/evo-sdk'; // Uses IdentitySigner (identity transfer key), not PlatformAddressSigner. const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); + const signer = new IdentitySigner(); signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); @@ -2706,17 +2751,17 @@ const result = await sdk.addresses.transferFromIdentity({ *Fund Platform addresses from an asset lock* Parameters: -- `Asset Lock Proof` (string, required) - - Hex-encoded asset lock proof +- `Asset Lock Proof` (AssetLockProof, required) + - AssetLockProof from Core (AssetLockProof.fromHex / createInstantAssetLockProof / createChainAssetLockProof). -- `Asset Lock Private Key` (string, required) - - WIF private key for asset lock +- `Asset Lock Private Key` (PrivateKey, required) + - PrivateKey controlling the asset-lock output (PrivateKey.fromWIF). - `Outputs` (array, required) - - Array of {address, amount (credits)} objects + - Array of PlatformAddressOutput (or {address, amount (credits)}) objects -- `Signer` (object, optional) - - Optional PlatformAddressSigner instance +- `Signer` (PlatformAddressSigner, required) + - PlatformAddressSigner containing keys for the funded output addresses Returns: diff --git a/public/documentation-check-report.txt b/public/documentation-check-report.txt index 22b59b4..031b24a 100644 --- a/public/documentation-check-report.txt +++ b/public/documentation-check-report.txt @@ -1,7 +1,7 @@ ================================================================================ Evo SDK Documentation Check ================================================================================ -Timestamp: 2026-07-16T12:42:01.638771 +Timestamp: 2026-07-20T15:11:32.832343 ✅ All documentation is up to date! ================================================================================ \ No newline at end of file From 26f2952411d9a9be0ae3d1c88c51dd87daeb806a Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 15:14:30 -0500 Subject: [PATCH 10/22] test: cache declaration property extraction in transition examples Parsing the full wasm-sdk d.ts once per interface kept the option-key suite near the default timeout after adding required-member checks. Co-Authored-By: Claude --- tests/unit/transition-examples.test.js | 57 ++++++++++++++------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/tests/unit/transition-examples.test.js b/tests/unit/transition-examples.test.js index 8e4043b..7206a6b 100644 --- a/tests/unit/transition-examples.test.js +++ b/tests/unit/transition-examples.test.js @@ -103,45 +103,48 @@ print(json.dumps(out)) return JSON.parse(result.stdout); } -function interfaceProperties(sourceText, interfaceName) { - const source = ts.createSourceFile('sdk.d.ts', sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); - const props = new Set(); +const interfaceAllPropsCache = new Map(); +const interfaceRequiredPropsCache = new Map(); +const wasmSdkSource = ts.createSourceFile('sdk.d.ts', wasmSdkDts, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + +function collectInterfaceProps(interfaceName) { + if (interfaceAllPropsCache.has(interfaceName)) { + return { + all: interfaceAllPropsCache.get(interfaceName), + required: interfaceRequiredPropsCache.get(interfaceName), + }; + } + + const all = new Set(); + let required = null; function visit(node) { if (ts.isInterfaceDeclaration(node) && node.name.text === interfaceName && node.members) { + const requiredHere = new Set(); for (const member of node.members) { if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) { - props.add(member.name.text); + all.add(member.name.text); + if (!member.questionToken) requiredHere.add(member.name.text); } } + // Prefer the first declaration for requiredness (write-options shapes). + if (required === null) required = requiredHere; } ts.forEachChild(node, visit); } - visit(source); - return props; + visit(wasmSdkSource); + if (required === null) required = new Set(); + interfaceAllPropsCache.set(interfaceName, all); + interfaceRequiredPropsCache.set(interfaceName, required); + return { all, required }; +} + +function interfaceProperties(_sourceText, interfaceName) { + return collectInterfaceProps(interfaceName).all; } /** Required property names from the first matching interface declaration. */ -function interfaceRequiredProperties(sourceText, interfaceName) { - const source = ts.createSourceFile('sdk.d.ts', sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); - const required = new Set(); - function visit(node) { - if (ts.isInterfaceDeclaration(node) && node.name.text === interfaceName && node.members) { - for (const member of node.members) { - if ( - ts.isPropertySignature(member) - && member.name - && ts.isIdentifier(member.name) - && !member.questionToken - ) { - required.add(member.name.text); - } - } - return; - } - ts.forEachChild(node, visit); - } - visit(source); - return required; +function interfaceRequiredProperties(_sourceText, interfaceName) { + return collectInterfaceProps(interfaceName).required; } function sdkParamByName(transitionKey, paramName) { From 2f758847e22e30f3553c12f47dbadae768768f5c Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 15:24:58 -0500 Subject: [PATCH 11/22] fix: make identity creation example functional Co-Authored-By: Claude --- public/AI_REFERENCE.md | 13 +++++------ scripts/generate_docs.py | 32 +++++++++++--------------- tests/unit/transition-examples.test.js | 7 +++++- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index ea56619..170844c 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -1430,26 +1430,25 @@ import { SecurityLevel, } from '@dashevo/evo-sdk'; -// Asset lock from Core (hex) + the private key that controls that output. +// Asset-lock proof and the separate private key controlling its Core output. const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); const assetLockPrivateKey = PrivateKey.fromWIF('cVExampleAssetLockKeyForIdentityFunding'); -// Build the identity shell and attach public keys that will be registered. -const identity = new Identity('random-or-derived-identity-id'); +// Identity key registered on Platform and held by IdentitySigner for key proofs. +const identityPrivateKey = PrivateKey.fromWIF('L1ExamplePrivateKeyWifGoesHere'); +const identity = new Identity(assetLockProof.createIdentityId()); const masterKey = new IdentityPublicKeyInCreation({ keyId: 0, purpose: Purpose.AUTHENTICATION, securityLevel: SecurityLevel.MASTER, keyType: KeyType.ECDSA_SECP256K1, - data: Uint8Array.from(atob('A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ'), c => c.charCodeAt(0)), + data: identityPrivateKey.getPublicKey().toBytes(), }).toIdentityPublicKey(); identity.addPublicKey(masterKey); -// IdentitySigner holds private keys for proving ownership of identity keys. -// Keep this separate from the asset-lock key. const signer = new IdentitySigner(); -signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); +signer.addKey(identityPrivateKey); await sdk.identities.create({ identity, diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index b7d9d1e..5a912d6 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -612,12 +612,6 @@ def evo_example_for_transition(key: str): comment='Master key is required to add/disable identity keys.', comment_before_add_key=True, ) - signer_identity_create = identity_signer_setup( - comment=( - 'IdentitySigner holds private keys for proving ownership of identity keys.\n' - '// Keep this separate from the asset-lock key.' - ), - ) signer_voting = identity_signer_setup(EXAMPLE_VOTING_KEY_WIF) signer_and_doc_key = signer_and_auth_key_setup('ownerId') signer_and_buyer_key = signer_and_auth_key_setup('buyerId') @@ -642,30 +636,32 @@ def evo_example_for_transition(key: str): SecurityLevel, } from '@dashevo/evo-sdk'; - // Asset lock from Core (hex) + the private key that controls that output. + // Asset-lock proof and the separate private key controlling its Core output. const assetLockProof = AssetLockProof.fromHex('a9147d3b...(hex-encoded)'); """, f"const assetLockPrivateKey = PrivateKey.fromWIF('{EXAMPLE_ASSET_LOCK_WIF}');", - """ - // Build the identity shell and attach public keys that will be registered. - const identity = new Identity('random-or-derived-identity-id'); - const masterKey = new IdentityPublicKeyInCreation({ + f""" + // Identity key registered on Platform and held by IdentitySigner for key proofs. + const identityPrivateKey = PrivateKey.fromWIF('{EXAMPLE_IDENTITY_SIGNER_WIF}'); + const identity = new Identity(assetLockProof.createIdentityId()); + const masterKey = new IdentityPublicKeyInCreation({{ keyId: 0, purpose: Purpose.AUTHENTICATION, securityLevel: SecurityLevel.MASTER, keyType: KeyType.ECDSA_SECP256K1, - data: Uint8Array.from(atob('A5GzYHPIolbHkFrp5l+s9IvF2lWMuuuSu3oWZB8vWHNJ'), c => c.charCodeAt(0)), - }).toIdentityPublicKey(); + data: identityPrivateKey.getPublicKey().toBytes(), + }}).toIdentityPublicKey(); identity.addPublicKey(masterKey); - """, - signer_identity_create, - """ - await sdk.identities.create({ + + const signer = new IdentitySigner(); + signer.addKey(identityPrivateKey); + + await sdk.identities.create({{ identity, assetLockProof, assetLockPrivateKey, signer, - }); + }}); """, ), 'identityTopUp': compose_example( diff --git a/tests/unit/transition-examples.test.js b/tests/unit/transition-examples.test.js index 7206a6b..ff225e2 100644 --- a/tests/unit/transition-examples.test.js +++ b/tests/unit/transition-examples.test.js @@ -256,7 +256,12 @@ describe('v4 state transition documentation examples', () => { expect(generatedExamples.dataContractCreate).toMatch(/new DataContract\s*\(/); expect(generatedExamples.identityCreate).toMatch(/AssetLockProof/); expect(generatedExamples.identityCreate).toMatch(/assetLockPrivateKey/); - expect(generatedExamples.identityCreate).toMatch(/IdentitySigner/); + expect(generatedExamples.identityCreate).toMatch(/new Identity\(assetLockProof\.createIdentityId\(\)\)/); + expect(generatedExamples.identityCreate).toMatch( + /data:\s*identityPrivateKey\.getPublicKey\(\)\.toBytes\(\)/, + ); + expect(generatedExamples.identityCreate).toMatch(/signer\.addKey\(identityPrivateKey\)/); + expect(generatedExamples.identityCreate).not.toMatch(/random-or-derived-identity-id|atob\(/); expect(generatedExamples.identityTopUp).toMatch(/assetLockPrivateKey/); // Top-up is asset-lock only — no IdentitySigner construction/import. expect(generatedExamples.identityTopUp).not.toMatch(/new IdentitySigner|import \{[^}]*IdentitySigner/); From 5f8447d4b747a7889738d8dbb7ae75350d2c2eb3 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 15:25:53 -0500 Subject: [PATCH 12/22] fix: use token contract in transition examples Co-Authored-By: Claude --- public/AI_REFERENCE.md | 20 ++++++++++---------- scripts/generate_docs.py | 21 +++++++++++---------- tests/unit/transition-examples.test.js | 22 ++++++++++++++++++++++ 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index 170844c..0a8c1fa 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -2087,7 +2087,7 @@ const identityKey = keys.find( ); const result = await sdk.tokens.burn({ - dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + dataContractId: 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', tokenPosition: 0, amount: 10n, identityId, @@ -2130,7 +2130,7 @@ const identityKey = keys.find( ); const result = await sdk.tokens.mint({ - dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + dataContractId: 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', tokenPosition: 0, amount: 100n, identityId, @@ -2173,7 +2173,7 @@ const identityKey = keys.find( ); const result = await sdk.tokens.claim({ - dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + dataContractId: 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', tokenPosition: 0, identityId, distributionType: 'perpetual', // or 'preProgrammed' @@ -2218,7 +2218,7 @@ const identityKey = keys.find( ); await sdk.tokens.setPrice({ - dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + dataContractId: 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', tokenPosition: 0, authorityId, price: 1000n, // or null to clear @@ -2259,7 +2259,7 @@ const identityKey = keys.find( ); const result = await sdk.tokens.directPurchase({ - dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + dataContractId: 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', tokenPosition: 0, buyerId, amount: 10n, @@ -2301,7 +2301,7 @@ const identityKey = keys.find( ); await sdk.tokens.emergencyAction({ - dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + dataContractId: 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', tokenPosition: 0, authorityId, action: 'pause', // or 'resume' @@ -2344,7 +2344,7 @@ const identityKey = keys.find( ); const result = await sdk.tokens.transfer({ - dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + dataContractId: 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', tokenPosition: 0, amount: 5n, senderId, @@ -2386,7 +2386,7 @@ const identityKey = keys.find( ); await sdk.tokens.freeze({ - dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + dataContractId: 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', tokenPosition: 0, authorityId, frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', @@ -2427,7 +2427,7 @@ const identityKey = keys.find( ); await sdk.tokens.unfreeze({ - dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + dataContractId: 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', tokenPosition: 0, authorityId, frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', @@ -2468,7 +2468,7 @@ const identityKey = keys.find( ); await sdk.tokens.destroyFrozen({ - dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + dataContractId: 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', tokenPosition: 0, authorityId, frozenIdentityId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index 5a912d6..6931892 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -601,6 +601,7 @@ def evo_example_for_transition(key: str): """ identity = TESTNET_TEST_DATA['identity_id'] contract = TESTNET_TEST_DATA['data_contract_id'] + token_contract = TESTNET_TEST_DATA['token_contract_id'] document_id = TESTNET_TEST_DATA['document_id'] document_type = TESTNET_TEST_DATA['document_type'] pro_tx = TESTNET_TEST_DATA['pro_tx_hash'] @@ -928,7 +929,7 @@ def evo_example_for_transition(key: str): signer_and_identity_key, f""" const result = await sdk.tokens.mint({{ - dataContractId: '{contract}', + dataContractId: '{token_contract}', tokenPosition: 0, amount: 100n, identityId, @@ -948,7 +949,7 @@ def evo_example_for_transition(key: str): signer_and_identity_key, f""" const result = await sdk.tokens.burn({{ - dataContractId: '{contract}', + dataContractId: '{token_contract}', tokenPosition: 0, amount: 10n, identityId, @@ -967,7 +968,7 @@ def evo_example_for_transition(key: str): signer_and_sender_key, f""" const result = await sdk.tokens.transfer({{ - dataContractId: '{contract}', + dataContractId: '{token_contract}', tokenPosition: 0, amount: 5n, senderId, @@ -987,7 +988,7 @@ def evo_example_for_transition(key: str): signer_and_authority_key, f""" await sdk.tokens.freeze({{ - dataContractId: '{contract}', + dataContractId: '{token_contract}', tokenPosition: 0, authorityId, frozenIdentityId: '{recipient}', @@ -1006,7 +1007,7 @@ def evo_example_for_transition(key: str): signer_and_authority_key, f""" await sdk.tokens.unfreeze({{ - dataContractId: '{contract}', + dataContractId: '{token_contract}', tokenPosition: 0, authorityId, frozenIdentityId: '{recipient}', @@ -1025,7 +1026,7 @@ def evo_example_for_transition(key: str): signer_and_authority_key, f""" await sdk.tokens.destroyFrozen({{ - dataContractId: '{contract}', + dataContractId: '{token_contract}', tokenPosition: 0, authorityId, frozenIdentityId: '{recipient}', @@ -1044,7 +1045,7 @@ def evo_example_for_transition(key: str): signer_and_authority_key, f""" await sdk.tokens.setPrice({{ - dataContractId: '{contract}', + dataContractId: '{token_contract}', tokenPosition: 0, authorityId, price: 1000n, // or null to clear @@ -1063,7 +1064,7 @@ def evo_example_for_transition(key: str): signer_and_auth_key_setup('buyerId', compact=True, blank_before_keys=False), f""" const result = await sdk.tokens.directPurchase({{ - dataContractId: '{contract}', + dataContractId: '{token_contract}', tokenPosition: 0, buyerId, amount: 10n, @@ -1082,7 +1083,7 @@ def evo_example_for_transition(key: str): signer_and_identity_key, f""" const result = await sdk.tokens.claim({{ - dataContractId: '{contract}', + dataContractId: '{token_contract}', tokenPosition: 0, identityId, distributionType: 'perpetual', // or 'preProgrammed' @@ -1101,7 +1102,7 @@ def evo_example_for_transition(key: str): signer_and_authority_key, f""" await sdk.tokens.emergencyAction({{ - dataContractId: '{contract}', + dataContractId: '{token_contract}', tokenPosition: 0, authorityId, action: 'pause', // or 'resume' diff --git a/tests/unit/transition-examples.test.js b/tests/unit/transition-examples.test.js index ff225e2..a400905 100644 --- a/tests/unit/transition-examples.test.js +++ b/tests/unit/transition-examples.test.js @@ -16,6 +16,20 @@ const TRANSITION_ENTRIES = Object.values(apiDefinitions.transitions).flatMap((ca ); const TRANSITION_KEYS = TRANSITION_ENTRIES.map(([key]) => key); const TRANSITION_BY_KEY = Object.fromEntries(TRANSITION_ENTRIES); +const TOKEN_TRANSITION_KEYS = [ + 'tokenMint', + 'tokenBurn', + 'tokenTransfer', + 'tokenFreeze', + 'tokenUnfreeze', + 'tokenDestroyFrozen', + 'tokenSetPriceForDirectPurchase', + 'tokenDirectPurchase', + 'tokenClaim', + 'tokenEmergencyAction', +]; +const DPNS_CONTRACT_ID = 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec'; +const TOKEN_CONTRACT_ID = 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A'; const METHOD_TO_OPTIONS = { 'identities.create': 'IdentityCreateOptions', @@ -314,6 +328,14 @@ describe('v4 state transition documentation examples', () => { expect(failures).toEqual([]); }); + it('uses the token contract only for token transition examples', () => { + for (const key of TOKEN_TRANSITION_KEYS) { + expect(generatedExamples[key], key).toContain(`dataContractId: '${TOKEN_CONTRACT_ID}'`); + expect(generatedExamples[key], key).not.toContain(`dataContractId: '${DPNS_CONTRACT_ID}'`); + } + expect(generatedExamples.documentCreate).toContain(`dataContractId: '${DPNS_CONTRACT_ID}'`); + }); + it('embeds v4 document create / identity top-up examples in generated docs', () => { expect(docs).toContain('new Document({'); expect(docs).toContain('identityKey'); From 3d20427b692c4b5c1dc060244b7f8181f7a741d8 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 15:26:37 -0500 Subject: [PATCH 13/22] fix: require withdrawal destination hash Co-Authored-By: Claude --- public/AI_REFERENCE.md | 11 +++++++++-- scripts/generate_docs.py | 11 +++++++++-- tests/unit/transition-examples.test.js | 5 +++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index 0a8c1fa..04cfa84 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -2697,8 +2697,15 @@ const signer = new PlatformAddressSigner(); const platformAddr = signer.addKey(privateKey); const input = new PlatformAddressInput(platformAddr, 0, 100000n); -// 20-byte pubkey hash for the Core L1 P2PKH destination. -const corePubkeyHash = new Uint8Array(20); +// Caller-supplied 20-byte public-key hash for the spendable Core L1 destination. +const corePubkeyHashHex = 'replace-with-40-hex-character-core-pubkey-hash'; +if (!/^[0-9a-fA-F]{40}$/.test(corePubkeyHashHex)) { + throw new Error('Set corePubkeyHashHex to the 20-byte hash from your Core P2PKH address'); +} +const corePubkeyHash = Uint8Array.from( + corePubkeyHashHex.match(/../g), + byte => Number.parseInt(byte, 16), +); const outputScript = CoreScript.fromP2PKH(corePubkeyHash); const result = await sdk.addresses.withdraw({ diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index 6931892..dbceb17 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -1257,8 +1257,15 @@ def evo_example_for_transition(key: str): const platformAddr = signer.addKey(privateKey); const input = new PlatformAddressInput(platformAddr, 0, 100000n); - // 20-byte pubkey hash for the Core L1 P2PKH destination. - const corePubkeyHash = new Uint8Array(20); + // Caller-supplied 20-byte public-key hash for the spendable Core L1 destination. + const corePubkeyHashHex = 'replace-with-40-hex-character-core-pubkey-hash'; + if (!/^[0-9a-fA-F]{40}$/.test(corePubkeyHashHex)) { + throw new Error('Set corePubkeyHashHex to the 20-byte hash from your Core P2PKH address'); + } + const corePubkeyHash = Uint8Array.from( + corePubkeyHashHex.match(/../g), + byte => Number.parseInt(byte, 16), + ); const outputScript = CoreScript.fromP2PKH(corePubkeyHash); const result = await sdk.addresses.withdraw({ diff --git a/tests/unit/transition-examples.test.js b/tests/unit/transition-examples.test.js index a400905..49f81c3 100644 --- a/tests/unit/transition-examples.test.js +++ b/tests/unit/transition-examples.test.js @@ -350,6 +350,11 @@ describe('v4 state transition documentation examples', () => { expect(example).toMatch(/CoreScript\.fromP2PKH/); expect(example).toMatch(/PoolingWasm\.Standard/); expect(example).not.toMatch(/CoreScript\.newP2PKH/); + expect(example).not.toMatch(/new Uint8Array\(20\)/); + expect(example).toMatch(/const corePubkeyHashHex = 'replace-with-40-hex-character-core-pubkey-hash'/); + expect(example).toMatch(/\^\[0-9a-fA-F\]\{40\}\$/); + expect(example).toMatch(/Uint8Array\.from\(/); + expect(example).toMatch(/CoreScript\.fromP2PKH\(corePubkeyHash\)/); expect(example).not.toMatch(/pooling:\s*undefined/); expect(example).not.toMatch(/outputScript:\s*(?:\/\*[^*]*\*\/\s*)?undefined/); From 22e046b80e28a95fb3e20d28cc37211b30031ea9 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 15:27:40 -0500 Subject: [PATCH 14/22] docs: refresh documentation check report Co-Authored-By: Claude --- public/documentation-check-report.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/documentation-check-report.txt b/public/documentation-check-report.txt index 031b24a..d1ee725 100644 --- a/public/documentation-check-report.txt +++ b/public/documentation-check-report.txt @@ -1,7 +1,7 @@ ================================================================================ Evo SDK Documentation Check ================================================================================ -Timestamp: 2026-07-20T15:11:32.832343 +Timestamp: 2026-07-20T15:26:58.000744 ✅ All documentation is up to date! ================================================================================ \ No newline at end of file From 78cf826d47b803f7010e17594f4a76c0d17a7d1d Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 16:10:08 -0500 Subject: [PATCH 15/22] fix: require PlatformAddressInput/Output arrays in address metadata Align Platform Address sdk_params with shipped v4 option types so transfer, top-up, withdraw, transfer-from-identity, fund-from-asset-lock, and create-identity document PlatformAddressInput[] / PlatformAddressOutput[] instead of plain objects, and use PoolingWasm / concrete signer types. Co-Authored-By: Claude --- public/api-definitions.json | 48 ++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/public/api-definitions.json b/public/api-definitions.json index abba7a0..2a04ba9 100644 --- a/public/api-definitions.json +++ b/public/api-definitions.json @@ -2527,24 +2527,24 @@ "sdk_params": [ { "name": "inputs", - "type": "array", + "type": "PlatformAddressInput[]", "label": "Inputs", "required": true, - "description": "Array of {address, nonce, amount (credits)} objects for sender addresses" + "description": "PlatformAddressInput[] - construct with new PlatformAddressInput(address, nonce, amount)." }, { "name": "outputs", - "type": "array", + "type": "PlatformAddressOutput[]", "label": "Outputs", "required": true, - "description": "Array of {address, amount (credits)} objects for recipient addresses" + "description": "PlatformAddressOutput[] - construct with new PlatformAddressOutput(address, amount)." }, { "name": "signer", - "type": "object", + "type": "PlatformAddressSigner", "label": "Signer", "required": true, - "description": "PlatformAddressSigner instance" + "description": "PlatformAddressSigner containing private keys for all input addresses." } ], "sdk_method": "addresses.transfer" @@ -2592,10 +2592,10 @@ }, { "name": "inputs", - "type": "array", + "type": "PlatformAddressInput[]", "label": "Inputs", "required": true, - "description": "Array of PlatformAddressInput (or {address, nonce, amount (credits)}) objects" + "description": "PlatformAddressInput[] - construct with new PlatformAddressInput(address, nonce, amount)." }, { "name": "signer", @@ -2650,10 +2650,10 @@ "sdk_params": [ { "name": "inputs", - "type": "array", + "type": "PlatformAddressInput[]", "label": "Inputs", "required": true, - "description": "Array of PlatformAddressInput (or {address, nonce, amount (credits)}) objects" + "description": "PlatformAddressInput[] - construct with new PlatformAddressInput(address, nonce, amount)." }, { "name": "coreFeePerByte", @@ -2664,10 +2664,10 @@ }, { "name": "pooling", - "type": "Pooling", + "type": "PoolingWasm", "label": "Pooling", "required": true, - "description": "Pooling strategy (PoolingWasm.Never / IfAvailable / Standard)" + "description": "Pooling strategy (PoolingWasm.Never / IfAvailable / Standard)." }, { "name": "outputScript", @@ -2715,10 +2715,10 @@ }, { "name": "outputs", - "type": "array", + "type": "PlatformAddressOutput[]", "label": "Outputs", "required": true, - "description": "Array of PlatformAddressOutput (or {address, amount (credits)}) objects for recipient addresses" + "description": "PlatformAddressOutput[] - construct with new PlatformAddressOutput(address, amount)." }, { "name": "signer", @@ -2766,10 +2766,10 @@ }, { "name": "outputs", - "type": "array", + "type": "PlatformAddressOutput[]", "label": "Outputs", "required": true, - "description": "Array of PlatformAddressOutput (or {address, amount (credits)}) objects" + "description": "PlatformAddressOutput[] - construct with new PlatformAddressOutput(address, amount)." }, { "name": "signer", @@ -2817,31 +2817,31 @@ "sdk_params": [ { "name": "identity", - "type": "object", + "type": "Identity", "label": "Identity", "required": true, - "description": "Identity object with public keys" + "description": "Identity object with public keys attached (new Identity(...) + addPublicKey / IdentityPublicKeyInCreation)." }, { "name": "inputs", - "type": "array", + "type": "PlatformAddressInput[]", "label": "Inputs", "required": true, - "description": "Array of {address, nonce, amount (credits)} objects" + "description": "PlatformAddressInput[] - construct with new PlatformAddressInput(address, nonce, amount)." }, { "name": "identitySigner", - "type": "object", + "type": "IdentitySigner", "label": "Identity Signer", "required": true, - "description": "IdentitySigner for signing identity keys" + "description": "IdentitySigner holding private keys that prove ownership of the identity public keys being registered." }, { "name": "addressSigner", - "type": "object", + "type": "PlatformAddressSigner", "label": "Address Signer", "required": true, - "description": "PlatformAddressSigner instance" + "description": "PlatformAddressSigner containing private keys for all input addresses." } ], "sdk_method": "addresses.createIdentity" From c108c445f1e3464baf83deb0e41c0dbfe40357f8 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 16:10:29 -0500 Subject: [PATCH 16/22] fix: format multiline AI query examples as const result Restore valid top-level JS for multiline query snippets that start with return await by wrapping pure expression examples as const result = await ... while leaving multi-statement transition examples intact. Co-Authored-By: Claude --- public/AI_REFERENCE.md | 120 +++++++++++++++++++-------------------- scripts/generate_docs.py | 56 ++++++++++++------ 2 files changed, 98 insertions(+), 78 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index 04cfa84..c86fae3 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -107,12 +107,12 @@ Returns: Example: ```javascript -return await sdk.identities.getKeys({ +const result = await sdk.identities.getKeys({ identityId: '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', request: { type: 'all' }, limit: 10, offset: 0 -}) +}); ``` **Get Contract Keys for Identities** - `identities.contractKeys` @@ -135,10 +135,10 @@ Returns: Example: ```javascript -return await sdk.identities.contractKeys({ +const result = await sdk.identities.contractKeys({ identityIds: ['5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'], contractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec' -}) +}); ``` **Get Identity Nonce** - `identities.nonce` @@ -376,11 +376,11 @@ Returns: Example: ```javascript -return await sdk.contracts.getHistory({ +const result = await sdk.contracts.getHistory({ dataContractId: 'HLY575cNazmc5824FxqaEMEBuzFeE4a98GDRNKbyJqCM', limit: 10, startAtMs: 0 -}) +}); ``` **Get Data Contracts** - `contracts.getMany` @@ -397,10 +397,10 @@ Returns: Example: ```javascript -return await sdk.contracts.getMany([ +const result = await sdk.contracts.getMany([ 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A' -]) +]); ``` #### Document Queries @@ -434,13 +434,13 @@ Returns: Example: ```javascript -return await sdk.documents.query({ +const result = await sdk.documents.query({ dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', documentTypeName: 'domain', where: [["normalizedParentDomainName", "==", "dash"]], orderBy: [["normalizedLabel", "asc"]], limit: 10 -}) +}); ``` **Get Document** - `documents.get` @@ -463,11 +463,11 @@ Returns: Example: ```javascript -return await sdk.documents.get( +const result = await sdk.documents.get( 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', 'domain', '7NYmEKQsYtniQRUmxwdPGeVcirMoPh5ZPyAKz8BWFy3r' -) +); ``` #### DPNS Queries @@ -634,14 +634,14 @@ Returns: Example: ```javascript -return await sdk.group.contestedResources({ +const result = await sdk.group.contestedResources({ dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', documentTypeName: 'domain', indexName: 'parentNameAndLabel', startAtValue: null, limit: 10, orderAscending: true -}) +}); ``` **Get Contested Resource Vote State** - `voting.contestedResourceVoteState` @@ -679,7 +679,7 @@ Returns: Example: ```javascript -return await sdk.voting.contestedResourceVoteState({ +const result = await sdk.voting.contestedResourceVoteState({ dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', documentTypeName: 'domain', indexName: 'parentNameAndLabel', @@ -687,7 +687,7 @@ return await sdk.voting.contestedResourceVoteState({ resultType: 'documents', limit: 10, orderAscending: true -}) +}); ``` **Get Voters for Identity** - `group.contestedResourceVotersForIdentity` @@ -723,7 +723,7 @@ Returns: Example: ```javascript -return await sdk.group.contestedResourceVotersForIdentity({ +const result = await sdk.group.contestedResourceVotersForIdentity({ dataContractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', documentTypeName: 'domain', indexName: 'parentNameAndLabel', @@ -731,7 +731,7 @@ return await sdk.group.contestedResourceVotersForIdentity({ contestantId: '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', limit: 10, orderAscending: true -}) +}); ``` **Get Identity Votes** - `voting.contestedResourceIdentityVotes` @@ -756,11 +756,11 @@ Returns: Example: ```javascript -return await sdk.voting.contestedResourceIdentityVotes({ +const result = await sdk.voting.contestedResourceIdentityVotes({ identityId: '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', limit: 10, orderAscending: true -}) +}); ``` **Get Vote Polls by End Date** - `voting.votePollsByEndDate` @@ -788,12 +788,12 @@ Returns: Example: ```javascript -return await sdk.voting.votePollsByEndDate({ +const result = await sdk.voting.votePollsByEndDate({ startTimeMs: null, endTimeMs: null, limit: 10, orderAscending: true, -}) +}); ``` #### Protocol & Version @@ -851,11 +851,11 @@ Returns: Example: ```javascript -return await sdk.epoch.epochsInfo({ +const result = await sdk.epoch.epochsInfo({ startEpoch: 8635, count: 5, ascending: true -}) +}); ``` **Get Current Epoch** - `epoch.current` @@ -890,11 +890,11 @@ Returns: Example: ```javascript -return await sdk.epoch.finalizedInfos({ +const result = await sdk.epoch.finalizedInfos({ startEpoch: 8635, count: 5, ascending: true -}) +}); ``` **Get Epoch Blocks by Evonode IDs** - `epoch.evonodesProposedBlocksByIds` @@ -912,10 +912,10 @@ Returns: Example: ```javascript -return await sdk.epoch.evonodesProposedBlocksByIds( +const result = await sdk.epoch.evonodesProposedBlocksByIds( 8635, ['143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'] -) +); ``` **Get Epoch Blocks by Range** - `epoch.evonodesProposedBlocksByRange` @@ -937,11 +937,11 @@ Returns: Example: ```javascript -return await sdk.epoch.evonodesProposedBlocksByRange({ +const result = await sdk.epoch.evonodesProposedBlocksByRange({ epoch: 8635, limit: 5, orderAscending: true -}) +}); ``` #### Token Queries @@ -979,10 +979,10 @@ Returns: Example: ```javascript -return await sdk.tokens.statuses([ +const result = await sdk.tokens.statuses([ 'Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv', 'H7FRpZJqZK933r9CzZMsCuf1BM34NT5P2wSJyjDkprqy' -]) +]); ``` **Get Direct Purchase Prices** - `tokens.directPurchasePrices` @@ -999,9 +999,9 @@ Returns: Example: ```javascript -return await sdk.tokens.directPurchasePrices([ +const result = await sdk.tokens.directPurchasePrices([ 'Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv' -]) +]); ``` **Get Token Contract Info** - `tokens.contractInfo` @@ -1038,10 +1038,10 @@ Returns: Example: ```javascript -return await sdk.tokens.perpetualDistributionLastClaim( +const result = await sdk.tokens.perpetualDistributionLastClaim( '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', 'Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv' -) +); ``` **Get Token Total Supply** - `tokens.totalSupply` @@ -2583,14 +2583,14 @@ await sdk.voting.masternodeVote({ *Transfer credits between Platform addresses* Parameters: -- `Inputs` (array, required) - - Array of {address, nonce, amount (credits)} objects for sender addresses +- `Inputs` (PlatformAddressInput[], required) + - PlatformAddressInput[] - construct with new PlatformAddressInput(address, nonce, amount). -- `Outputs` (array, required) - - Array of {address, amount (credits)} objects for recipient addresses +- `Outputs` (PlatformAddressOutput[], required) + - PlatformAddressOutput[] - construct with new PlatformAddressOutput(address, amount). -- `Signer` (object, required) - - PlatformAddressSigner instance +- `Signer` (PlatformAddressSigner, required) + - PlatformAddressSigner containing private keys for all input addresses. Returns: @@ -2630,8 +2630,8 @@ Parameters: - `Identity` (Identity, required) - Fetched Identity object to top up (sdk.identities.fetch). -- `Inputs` (array, required) - - Array of PlatformAddressInput (or {address, nonce, amount (credits)}) objects +- `Inputs` (PlatformAddressInput[], required) + - PlatformAddressInput[] - construct with new PlatformAddressInput(address, nonce, amount). - `Signer` (PlatformAddressSigner, required) - PlatformAddressSigner containing keys for the input addresses @@ -2662,14 +2662,14 @@ const result = await sdk.addresses.topUpIdentity({ *Withdraw Platform address credits to Dash Core* Parameters: -- `Inputs` (array, required) - - Array of PlatformAddressInput (or {address, nonce, amount (credits)}) objects +- `Inputs` (PlatformAddressInput[], required) + - PlatformAddressInput[] - construct with new PlatformAddressInput(address, nonce, amount). - `Core Fee Per Byte` (number, required) - Core (L1) mining fee per byte for the withdrawal transaction -- `Pooling` (Pooling, required) - - Pooling strategy (PoolingWasm.Never / IfAvailable / Standard) +- `Pooling` (PoolingWasm, required) + - Pooling strategy (PoolingWasm.Never / IfAvailable / Standard). - `Output Script` (CoreScript, required) - Core L1 destination script (CoreScript.fromP2PKH / fromP2SH) @@ -2724,8 +2724,8 @@ Parameters: - `Identity` (Identity, required) - Fetched Identity object to transfer from (sdk.identities.fetch). -- `Outputs` (array, required) - - Array of PlatformAddressOutput (or {address, amount (credits)}) objects for recipient addresses +- `Outputs` (PlatformAddressOutput[], required) + - PlatformAddressOutput[] - construct with new PlatformAddressOutput(address, amount). - `Signer` (IdentitySigner, required) - IdentitySigner with the identity transfer key @@ -2763,8 +2763,8 @@ Parameters: - `Asset Lock Private Key` (PrivateKey, required) - PrivateKey controlling the asset-lock output (PrivateKey.fromWIF). -- `Outputs` (array, required) - - Array of PlatformAddressOutput (or {address, amount (credits)}) objects +- `Outputs` (PlatformAddressOutput[], required) + - PlatformAddressOutput[] - construct with new PlatformAddressOutput(address, amount). - `Signer` (PlatformAddressSigner, required) - PlatformAddressSigner containing keys for the funded output addresses @@ -2804,17 +2804,17 @@ const result = await sdk.addresses.fundFromAssetLock({ *Create a new identity funded from Platform addresses* Parameters: -- `Identity` (object, required) - - Identity object with public keys +- `Identity` (Identity, required) + - Identity object with public keys attached (new Identity(...) + addPublicKey / IdentityPublicKeyInCreation). -- `Inputs` (array, required) - - Array of {address, nonce, amount (credits)} objects +- `Inputs` (PlatformAddressInput[], required) + - PlatformAddressInput[] - construct with new PlatformAddressInput(address, nonce, amount). -- `Identity Signer` (object, required) - - IdentitySigner for signing identity keys +- `Identity Signer` (IdentitySigner, required) + - IdentitySigner holding private keys that prove ownership of the identity public keys being registered. -- `Address Signer` (object, required) - - PlatformAddressSigner instance +- `Address Signer` (PlatformAddressSigner, required) + - PlatformAddressSigner containing private keys for all input addresses. Returns: diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index dbceb17..eabe794 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -2074,34 +2074,54 @@ def generate_docs_html(query_defs: dict, transition_defs: dict, type_metadata: d def format_ai_example_block(code: str | None, item_key: str) -> str: + """Format a snippet for AI_REFERENCE.md code fences. + + Query examples are often a single expression that spans multiple lines and + begin with `return await ...`. Those must become valid top-level JS + (`const result = await ...`). Multi-statement transition examples already + include imports/setup and a final call — leave them intact. + """ processed = normalize_example_lines(code) if not processed: return f"// Example currently unavailable for `{item_key}`" - # Multi-line examples already declare imports/setup and a final call — leave intact. indexes = code_line_indexes(processed) - if len(indexes) != 1: + if not indexes: return '\n'.join(processed).rstrip() - first_index = indexes[0] - first_line = processed[first_index] - stripped = first_line.lstrip() - indent = first_line[: len(first_line) - len(stripped)] - snippet = '\n'.join(processed) - - already_has_declaration = ( - stripped.startswith('const ') - or stripped.startswith('let ') - or stripped.startswith('var ') - or stripped.startswith('import ') - or stripped.startswith('await ') - or 'const result' in snippet - ) + def is_setup_line(line: str) -> bool: + stripped = line.lstrip() + return ( + stripped.startswith('import ') + or stripped.startswith('const ') + or stripped.startswith('let ') + or stripped.startswith('var ') + or stripped.startswith('function ') + or stripped.startswith('class ') + or stripped.startswith('if ') + or stripped.startswith('throw ') + ) + + # Pure multi-line expressions (no imports/bindings) still need wrapping. + has_setup = any(is_setup_line(processed[i]) for i in indexes) - if not already_has_declaration: + if not has_setup: + first_index = indexes[0] + first_line = processed[first_index] + stripped = first_line.lstrip() + indent = first_line[: len(first_line) - len(stripped)] if stripped.startswith('return '): stripped = stripped[len('return ') :].lstrip() - processed[first_index] = f'{indent}const result = {stripped}' + if not ( + stripped.startswith('const ') + or stripped.startswith('let ') + or stripped.startswith('var ') + or stripped.startswith('await ') + ): + processed[first_index] = f'{indent}const result = {stripped}' + elif stripped.startswith('await '): + # Bare top-level await expression without prior setup. + processed[first_index] = f'{indent}const result = {stripped}' joined = '\n'.join(processed).rstrip() if joined and not joined.endswith(';'): From 8f4694025aa7f5b37e2c8415159edf3f14857830 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 16:10:34 -0500 Subject: [PATCH 17/22] test: guard Platform Address arrays and AI query formatting Add focused regression coverage for PlatformAddressInput[] / PlatformAddressOutput[] metadata across address transitions and for multiline AI_REFERENCE query examples becoming const result = await. Co-Authored-By: Claude --- tests/unit/transition-examples.test.js | 97 +++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/tests/unit/transition-examples.test.js b/tests/unit/transition-examples.test.js index 49f81c3..84ffa97 100644 --- a/tests/unit/transition-examples.test.js +++ b/tests/unit/transition-examples.test.js @@ -368,24 +368,56 @@ describe('v4 state transition documentation examples', () => { }); it('documents Platform Address option metadata with v4 types and requiredness', () => { + // Transfer: typed input/output arrays, not plain objects. + expect(sdkParamByName('addressTransfer', 'inputs')).toMatchObject({ + type: 'PlatformAddressInput[]', + required: true, + }); + expect(sdkParamByName('addressTransfer', 'outputs')).toMatchObject({ + type: 'PlatformAddressOutput[]', + required: true, + }); + expect(sdkParamByName('addressTransfer', 'signer')).toMatchObject({ + type: 'PlatformAddressSigner', + required: true, + }); + expect(sdkParamByName('addressTransfer', 'inputs').description).toMatch(/PlatformAddressInput/); + expect(sdkParamByName('addressTransfer', 'outputs').description).toMatch(/PlatformAddressOutput/); + expect(sdkParamByName('addressTransfer', 'inputs').description).not.toMatch(/\{address, nonce, amount/); + expect(sdkParamByName('addressTransfer', 'outputs').description).not.toMatch(/\{address, amount/); + expect(sdkParamByName('addressTopUpIdentity', 'identity')).toMatchObject({ type: 'Identity', required: true, }); expect(sdkParamByName('addressTopUpIdentity', 'identityId')).toBeUndefined(); + expect(sdkParamByName('addressTopUpIdentity', 'inputs')).toMatchObject({ + type: 'PlatformAddressInput[]', + required: true, + }); + expect(sdkParamByName('addressTopUpIdentity', 'inputs').description).not.toMatch(/or \{address/); expect(sdkParamByName('addressTransferFromIdentity', 'identity')).toMatchObject({ type: 'Identity', required: true, }); expect(sdkParamByName('addressTransferFromIdentity', 'identityId')).toBeUndefined(); + expect(sdkParamByName('addressTransferFromIdentity', 'outputs')).toMatchObject({ + type: 'PlatformAddressOutput[]', + required: true, + }); + expect(sdkParamByName('addressTransferFromIdentity', 'outputs').description).not.toMatch(/or \{address/); + expect(sdkParamByName('addressWithdraw', 'inputs')).toMatchObject({ + type: 'PlatformAddressInput[]', + required: true, + }); expect(sdkParamByName('addressWithdraw', 'coreFeePerByte')).toMatchObject({ type: 'number', required: true, }); expect(sdkParamByName('addressWithdraw', 'pooling')).toMatchObject({ - type: 'Pooling', + type: 'PoolingWasm', required: true, }); expect(sdkParamByName('addressWithdraw', 'outputScript')).toMatchObject({ @@ -396,6 +428,7 @@ describe('v4 state transition documentation examples', () => { type: 'PlatformAddressSigner', required: true, }); + expect(sdkParamByName('addressWithdraw', 'inputs').description).not.toMatch(/or \{address/); expect(sdkParamByName('addressFundFromAssetLock', 'assetLockProof')).toMatchObject({ type: 'AssetLockProof', @@ -406,18 +439,40 @@ describe('v4 state transition documentation examples', () => { required: true, }); expect(sdkParamByName('addressFundFromAssetLock', 'outputs')).toMatchObject({ + type: 'PlatformAddressOutput[]', required: true, }); expect(sdkParamByName('addressFundFromAssetLock', 'signer')).toMatchObject({ type: 'PlatformAddressSigner', required: true, }); + expect(sdkParamByName('addressFundFromAssetLock', 'outputs').description).not.toMatch(/or \{address/); + + expect(sdkParamByName('addressCreateIdentity', 'identity')).toMatchObject({ + type: 'Identity', + required: true, + }); + expect(sdkParamByName('addressCreateIdentity', 'inputs')).toMatchObject({ + type: 'PlatformAddressInput[]', + required: true, + }); + expect(sdkParamByName('addressCreateIdentity', 'identitySigner')).toMatchObject({ + type: 'IdentitySigner', + required: true, + }); + expect(sdkParamByName('addressCreateIdentity', 'addressSigner')).toMatchObject({ + type: 'PlatformAddressSigner', + required: true, + }); + expect(sdkParamByName('addressCreateIdentity', 'inputs').description).not.toMatch(/\{address, nonce, amount/); // Example option objects must include every required declaration member. const requiredChecks = [ + ['addressTransfer', 'addresses.transfer', 'AddressFundsTransferOptions'], ['addressTopUpIdentity', 'addresses.topUpIdentity', 'IdentityTopUpFromAddressesOptions'], ['addressTransferFromIdentity', 'addresses.transferFromIdentity', 'IdentityTransferToAddressesOptions'], ['addressFundFromAssetLock', 'addresses.fundFromAssetLock', 'AddressFundingFromAssetLockOptions'], + ['addressCreateIdentity', 'addresses.createIdentity', 'IdentityCreateFromAddressesOptions'], ]; for (const [key, method, optionsName] of requiredChecks) { const example = generatedExamples[key]; @@ -430,4 +485,44 @@ describe('v4 state transition documentation examples', () => { } } }); + + it('formats multiline query examples as valid top-level const result assignments', () => { + const querySection = aiReference.split('## State Transition Operations')[0] || ''; + const queryBlocks = [...querySection.matchAll(/```javascript\n([\s\S]*?)```/g)].map((m) => m[1]); + + // Pattern intro block uses a placeholder, not a real call — skip blocks without sdk. + const queryCallBlocks = queryBlocks.filter((b) => /\bsdk\./.test(b)); + expect(queryCallBlocks.length).toBeGreaterThan(10); + + const invalidTopLevelReturn = queryCallBlocks.filter((b) => /^\s*return\s+await\b/m.test(b)); + expect(invalidTopLevelReturn, invalidTopLevelReturn.map((b) => b.slice(0, 80)).join('\n---\n')).toEqual([]); + + const multilineExpressionBlocks = queryCallBlocks.filter((b) => { + const codeLines = b.split('\n').filter((l) => l.trim() && !l.trim().startsWith('//')); + return codeLines.length > 1 && !codeLines.some((l) => /^\s*(import|const|let|var|function|class)\b/.test(l)); + }); + // After the formatter fix there should be no bare multiline expressions left; + // every former return-await multiline query is wrapped as const result = ... + expect(multilineExpressionBlocks).toEqual([]); + + // Representative multiline queries must be const result = await ... + for (const needle of [ + "sdk.identities.getKeys({", + "sdk.documents.query({", + "sdk.contracts.getMany([", + "sdk.tokens.statuses([", + ]) { + const block = queryCallBlocks.find((b) => b.includes(needle)); + expect(block, needle).toBeTruthy(); + expect(block).toMatch(/^\s*const result = await /m); + expect(block.trimEnd().endsWith(';')).toBe(true); + } + + // Transition examples stay multi-statement and must not be force-wrapped into one assignment. + const transitionSection = aiReference.split('## State Transition Operations')[1] || ''; + expect(transitionSection).toMatch(/import \{[\s\S]*IdentitySigner[\s\S]*\} from '@dashevo\/evo-sdk';/); + expect(transitionSection).toMatch(/const result = await sdk\.tokens\.mint\(\{/); + // Multi-statement identity create still has setup before the call. + expect(transitionSection).toMatch(/new Identity\(assetLockProof\.createIdentityId\(\)\)/); + }); }); From 959b8df4dbe745d48c6d0ebd89d89660a75b33e4 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 16:10:34 -0500 Subject: [PATCH 18/22] fix: avoid timestamp-only documentation check report churn Keep the previous report file when only the timestamp changes so docs check success does not create noise commits. Restore the prior report body from before the unexpected timestamp-only refresh. Co-Authored-By: Claude --- public/documentation-check-report.txt | 2 +- scripts/check_documentation.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/public/documentation-check-report.txt b/public/documentation-check-report.txt index d1ee725..031b24a 100644 --- a/public/documentation-check-report.txt +++ b/public/documentation-check-report.txt @@ -1,7 +1,7 @@ ================================================================================ Evo SDK Documentation Check ================================================================================ -Timestamp: 2026-07-20T15:26:58.000744 +Timestamp: 2026-07-20T15:11:32.832343 ✅ All documentation is up to date! ================================================================================ \ No newline at end of file diff --git a/scripts/check_documentation.py b/scripts/check_documentation.py index cc5fb07..99ab994 100644 --- a/scripts/check_documentation.py +++ b/scripts/check_documentation.py @@ -162,7 +162,17 @@ def main(): report = '\n'.join(lines) print(report) - (PUBLIC_DIR / 'documentation-check-report.txt').write_text(report, encoding='utf-8') + + # Avoid timestamp-only churn in git: keep the previous report when the + # status body is unchanged, rewriting only when warnings/errors change. + report_path = PUBLIC_DIR / 'documentation-check-report.txt' + previous = report_path.read_text(encoding='utf-8') if report_path.exists() else '' + + def body_without_timestamp(text: str) -> str: + return re.sub(r'^Timestamp:.*$', 'Timestamp:', text, count=1, flags=re.M) + + if body_without_timestamp(previous) != body_without_timestamp(report): + report_path.write_text(report, encoding='utf-8') sys.exit(1 if errors else 0) From 83211bdf8655f7f1c561174d4fb9cb9d0ee210d1 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 16:21:13 -0500 Subject: [PATCH 19/22] fix: auto-select TRANSFER/OWNER keys for credit examples Omit signingKey on identity credit transfer/withdrawal examples so the SDK selects a matching purpose key. Key ID 3 and AUTHENTICATION fallback were invalid for v4 transfer semantics. --- public/AI_REFERENCE.md | 15 ++++++--------- scripts/generate_docs.py | 27 ++++++++++++++++----------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index c86fae3..69b5eea 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -1543,18 +1543,16 @@ import { IdentitySigner } from '@dashevo/evo-sdk'; const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); const signer = new IdentitySigner(); +// Add the private key for a TRANSFER purpose identity key. signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); -// Optional: pass signingKey when you need a specific transfer/auth key. -const signingKey = identity.getPublicKeyById(3) // TRANSFER key when present - || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); - +// Omit signingKey so the SDK auto-selects an available TRANSFER key. +// Supplying a non-TRANSFER key (for example AUTHENTICATION) is invalid. await sdk.identities.creditTransfer({ identity, recipientId: 'H72iEt2zG4MEyoh3ZzCEMkYbDWqx1GvK1xHmpM8qH1yL', amount: 1000000n, signer, - signingKey, }); ``` @@ -1579,18 +1577,17 @@ import { IdentitySigner } from '@dashevo/evo-sdk'; const identity = await sdk.identities.fetch('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'); const signer = new IdentitySigner(); +// Add the private key for a TRANSFER or OWNER purpose identity key. signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); -const signingKey = identity.getPublicKeyById(3) - || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); - +// Omit signingKey so the SDK auto-selects a matching TRANSFER or OWNER key. +// Supplying a non-matching key (for example AUTHENTICATION) is invalid. const remainingBalance = await sdk.identities.creditWithdrawal({ identity, amount: 1000000n, toAddress: 'yT8DDY5NkX4Zt44Fy8QjmCekheJQH4EMkv', coreFeePerByte: 1, signer, - signingKey, }); ``` diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index eabe794..012bc0f 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -613,6 +613,16 @@ def evo_example_for_transition(key: str): comment='Master key is required to add/disable identity keys.', comment_before_add_key=True, ) + # Credit transfer requires a TRANSFER key; withdrawal allows TRANSFER or OWNER. + # Examples omit signingKey so the SDK auto-selects a matching purpose key. + signer_transfer = identity_signer_setup( + comment='Add the private key for a TRANSFER purpose identity key.', + comment_before_add_key=True, + ) + signer_transfer_or_owner = identity_signer_setup( + comment='Add the private key for a TRANSFER or OWNER purpose identity key.', + comment_before_add_key=True, + ) signer_voting = identity_signer_setup(EXAMPLE_VOTING_KEY_WIF) signer_and_doc_key = signer_and_auth_key_setup('ownerId') signer_and_buyer_key = signer_and_auth_key_setup('buyerId') @@ -687,18 +697,15 @@ def evo_example_for_transition(key: str): const identity = await sdk.identities.fetch('{identity}'); """, - signer_only, + signer_transfer, f""" - // Optional: pass signingKey when you need a specific transfer/auth key. - const signingKey = identity.getPublicKeyById(3) // TRANSFER key when present - || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); - + // Omit signingKey so the SDK auto-selects an available TRANSFER key. + // Supplying a non-TRANSFER key (for example AUTHENTICATION) is invalid. await sdk.identities.creditTransfer({{ identity, recipientId: '{recipient}', amount: 1000000n, signer, - signingKey, }}); """, ), @@ -708,18 +715,16 @@ def evo_example_for_transition(key: str): const identity = await sdk.identities.fetch('{identity}'); """, - signer_only, + signer_transfer_or_owner, """ - const signingKey = identity.getPublicKeyById(3) - || identity.publicKeys.find(k => k.purpose === 'AUTHENTICATION'); - + // Omit signingKey so the SDK auto-selects a matching TRANSFER or OWNER key. + // Supplying a non-matching key (for example AUTHENTICATION) is invalid. const remainingBalance = await sdk.identities.creditWithdrawal({ identity, amount: 1000000n, toAddress: 'yT8DDY5NkX4Zt44Fy8QjmCekheJQH4EMkv', coreFeePerByte: 1, signer, - signingKey, }); """, ), From 3b30b2b8c246e91da338421b2b1b8412b7e4e961 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Mon, 20 Jul 2026 16:21:14 -0500 Subject: [PATCH 20/22] test: guard credit transfer key purpose semantics Assert generated transfer/withdrawal examples omit signingKey, document TRANSFER (and OWNER for withdrawal), and never fall back to AUTHENTICATION. --- tests/unit/transition-examples.test.js | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/unit/transition-examples.test.js b/tests/unit/transition-examples.test.js index 84ffa97..a37ad78 100644 --- a/tests/unit/transition-examples.test.js +++ b/tests/unit/transition-examples.test.js @@ -286,6 +286,47 @@ describe('v4 state transition documentation examples', () => { expect(generatedExamples.dpnsRegister).toMatch(/IdentitySigner/); }); + it('selects credit transfer/withdrawal keys by purpose, never AUTHENTICATION fallback', () => { + const transfer = generatedExamples.identityCreditTransfer; + const withdrawal = generatedExamples.identityCreditWithdrawal; + + // Credit transfer requires TRANSFER; withdrawal allows TRANSFER or OWNER. + // Prefer omitting signingKey so the SDK auto-selects a matching purpose key. + for (const [key, example] of [ + ['identityCreditTransfer', transfer], + ['identityCreditWithdrawal', withdrawal], + ]) { + expect(example, key).not.toMatch(/getPublicKeyById\s*\(\s*3\s*\)/); + expect(example, key).not.toMatch(/purpose\s*===\s*['"]AUTHENTICATION['"]/); + expect(example, key).not.toMatch(/transfer\/auth/i); + + const writeMethod = key === 'identityCreditTransfer' + ? 'identities.creditTransfer' + : 'identities.creditWithdrawal'; + const writeCall = extractSdkCallSites(example).find((call) => call.method === writeMethod); + expect(writeCall, key).toBeTruthy(); + const optionKeys = topLevelObjectKeys(writeCall.argsText); + expect(optionKeys, `${key} must not pass signingKey`).not.toContain('signingKey'); + expect(optionKeys, key).toContain('signer'); + } + + expect(transfer).toMatch(/TRANSFER/); + expect(transfer).not.toMatch(/OWNER/); + expect(transfer).toMatch(/auto-selects an available TRANSFER key/i); + + expect(withdrawal).toMatch(/TRANSFER/); + expect(withdrawal).toMatch(/OWNER/); + expect(withdrawal).toMatch(/auto-selects a matching TRANSFER or OWNER key/i); + + // Generated docs must not reintroduce the invalid authentication fallback. + expect(docs).not.toMatch( + /identity\.getPublicKeyById\(3\)[\s\S]{0,120}purpose === ['"]AUTHENTICATION['"]/, + ); + expect(aiReference).not.toMatch( + /identity\.getPublicKeyById\(3\)[\s\S]{0,120}purpose === ['"]AUTHENTICATION['"]/, + ); + }); + it('passes only declared option properties on the final sdk write call', () => { const failures = []; for (const [key, item] of Object.entries(TRANSITION_BY_KEY)) { From 6f4050ce77467d2b10957e5c5cd09997f57097fa Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Fri, 31 Jul 2026 13:45:42 -0500 Subject: [PATCH 21/22] fix: enforce transition signing key purposes Co-Authored-By: Claude --- scripts/generate_docs.py | 25 ++++++++++++++++-------- tests/unit/transition-examples.test.js | 27 +++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index 0c4dc60..187cd61 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -1163,11 +1163,16 @@ def evo_example_for_transition(key: str): """, signer_only, """ - // HIGH authentication key is typically used for DPNS registration. - const identityKey = identity.getPublicKeyById(1) - || identity.publicKeys.find( - k => k.purpose === 'AUTHENTICATION' && k.securityLevel === 'HIGH', + // DPNS registration requires a strong authentication key. + const identityKey = identity.publicKeys.find( + k => k.purpose === 'AUTHENTICATION' + && ['CRITICAL', 'HIGH'].includes(k.securityLevel), + ); + if (!identityKey) { + throw new Error( + 'DPNS registration requires an AUTHENTICATION key with CRITICAL or HIGH security level', ); + } const result = await sdk.dpns.registerName({ label: 'alice', @@ -1190,8 +1195,10 @@ def evo_example_for_transition(key: str): f""" // Voting key must match the masternode voting public key on the identity. const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); - const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING') - || votingIdentity.getPublicKeyById(0); + const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING'); + if (!votingKey) {{ + throw new Error('Masternode voting requires a VOTING-purpose identity key'); + }} const votePoll = new VotePoll({{ contractId: '{contract}', @@ -1218,8 +1225,10 @@ def evo_example_for_transition(key: str): signer_voting, f""" const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); - const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING') - || votingIdentity.getPublicKeyById(0); + const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING'); + if (!votingKey) {{ + throw new Error('Masternode voting requires a VOTING-purpose identity key'); + }} const votePoll = new VotePoll({{ contractId: '{contract}', diff --git a/tests/unit/transition-examples.test.js b/tests/unit/transition-examples.test.js index 1f23354..dd34cb9 100644 --- a/tests/unit/transition-examples.test.js +++ b/tests/unit/transition-examples.test.js @@ -335,6 +335,31 @@ describe('v4 state transition documentation examples', () => { ); }); + it('selects DPNS keys only by authentication purpose and accepted security level', () => { + const example = generatedExamples.dpnsRegister; + + expect(example).toMatch(/purpose\s*===\s*['"]AUTHENTICATION['"]/); + expect(example).toContain("['CRITICAL', 'HIGH'].includes(k.securityLevel)"); + expect(example).not.toMatch(/getPublicKeyById\s*\(/); + expect(example).not.toMatch(/securityLevel\s*===\s*['"]MEDIUM['"]/); + expect(example).toMatch(/if \(!identityKey\)/); + expect(example).toContain( + 'DPNS registration requires an AUTHENTICATION key with CRITICAL or HIGH security level', + ); + }); + + it('requires a VOTING-purpose key for every masternode voting example', () => { + for (const key of ['dpnsUsername', 'masternodeVote']) { + const example = generatedExamples[key]; + + expect(example, key).toMatch(/find\(k => k\.purpose === ['"]VOTING['"]\)/); + expect(example, key).not.toMatch(/getPublicKeyById\s*\(/); + expect(example, key).not.toMatch(/\|\|\s*votingIdentity\./); + expect(example, key).toMatch(/if \(!votingKey\)/); + expect(example, key).toContain('Masternode voting requires a VOTING-purpose identity key'); + } + }); + it('passes only declared option properties on the final sdk write call', () => { const failures = []; for (const [key, item] of Object.entries(TRANSITION_BY_KEY)) { @@ -572,7 +597,7 @@ describe('v4 state transition documentation examples', () => { // Transition examples stay multi-statement and must not be force-wrapped into one assignment. const transitionSection = aiReference.split('## State Transition Operations')[1] || ''; expect(transitionSection).toMatch(/import \{[\s\S]*IdentitySigner[\s\S]*\} from '@dashevo\/evo-sdk';/); - expect(transitionSection).toMatch(/const result = await sdk\.tokens\.mint\(\{/); + expect(transitionSection).toMatch(/await sdk\.tokens\.mint\(\{/); // Multi-statement identity create still has setup before the call. expect(transitionSection).toMatch(/new Identity\(assetLockProof\.createIdentityId\(\)\)/); }); From baf93a663ea6473419830d382326db77e0492c46 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Fri, 31 Jul 2026 13:46:09 -0500 Subject: [PATCH 22/22] docs: regenerate transition key examples Co-Authored-By: Claude --- public/AI_REFERENCE.md | 86 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 3 deletions(-) diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index e9b20bc..9a79237 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -2248,7 +2248,34 @@ Returns: Example: ```javascript -const result = await sdk.dpns.registerName({ label, identity, identityKey, signer, preorderCallback }); +import { IdentitySigner } from '@dashevo/evo-sdk'; + +const identityId = '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'; +const identity = await sdk.identities.fetch(identityId); + +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExamplePrivateKeyWifGoesHere'); + +// DPNS registration requires a strong authentication key. +const identityKey = identity.publicKeys.find( + k => k.purpose === 'AUTHENTICATION' + && ['CRITICAL', 'HIGH'].includes(k.securityLevel), +); +if (!identityKey) { + throw new Error( + 'DPNS registration requires an AUTHENTICATION key with CRITICAL or HIGH security level', + ); +} + +const result = await sdk.dpns.registerName({ + label: 'alice', + identity, + identityKey, + signer, + preorderCallback: (preorderDocument) => { + console.log('preorder submitted', preorderDocument.id?.toString?.()); + }, +}); ``` #### Token Transitions @@ -2926,7 +2953,34 @@ Returns: Example: ```javascript -const result = await sdk.voting.masternodeVote({ masternodeProTxHash, votePoll, voteChoice, votingKey, signer }); +import { IdentitySigner, ResourceVoteChoice, VotePoll } from '@dashevo/evo-sdk'; + +const masternodeProTxHash = '143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'; + +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExampleVotingKeyWifGoesHere'); + +// Voting key must match the masternode voting public key on the identity. +const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); +const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING'); +if (!votingKey) { + throw new Error('Masternode voting requires a VOTING-purpose identity key'); +} + +const votePoll = new VotePoll({ + contractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + documentTypeName: 'domain', + indexName: 'parentNameAndLabel', + indexValues: ['dash', 'alice'], +}); + +await sdk.voting.masternodeVote({ + masternodeProTxHash, + votePoll, + voteChoice: ResourceVoteChoice.TowardsIdentity('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'), + votingKey, + signer, +}); ``` **Contested Resource** - `voting.masternodeVote` @@ -2969,7 +3023,33 @@ Returns: Example: ```javascript -const result = await sdk.voting.masternodeVote({ masternodeProTxHash, votePoll, voteChoice, votingKey, signer }); +import { IdentitySigner, ResourceVoteChoice, VotePoll } from '@dashevo/evo-sdk'; + +const masternodeProTxHash = '143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'; + +const signer = new IdentitySigner(); +signer.addKeyFromWif('L1ExampleVotingKeyWifGoesHere'); + +const votingIdentity = await sdk.identities.fetch(masternodeProTxHash); +const votingKey = votingIdentity.publicKeys.find(k => k.purpose === 'VOTING'); +if (!votingKey) { + throw new Error('Masternode voting requires a VOTING-purpose identity key'); +} + +const votePoll = new VotePoll({ + contractId: 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + documentTypeName: 'domain', + indexName: 'parentNameAndLabel', + indexValues: ['dash', 'alice'], +}); + +await sdk.voting.masternodeVote({ + masternodeProTxHash, + votePoll, + voteChoice: ResourceVoteChoice.TowardsIdentity('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk'), + votingKey, + signer, +}); ``` #### Platform Address Transitions