diff --git a/book/src/evo-sdk/tutorials/basic-token.md b/book/src/evo-sdk/tutorials/basic-token.md index 13125dd4da9..84a30dacc29 100644 --- a/book/src/evo-sdk/tutorials/basic-token.md +++ b/book/src/evo-sdk/tutorials/basic-token.md @@ -31,7 +31,12 @@ A token is defined as part of a data contract. The contract schema includes a `tokens` section alongside the usual document schemas. ```typescript -import { EvoSDK, wallet } from '@dashevo/evo-sdk'; +import { + EvoSDK, DataContract, Identifier, IdentitySigner, + TokenConfigurationConvention, TokenConfigurationLocalization, TokenConfiguration, + ChangeControlRules, AuthorizedActionTakers, TokenDistributionRules, + TokenKeepsHistoryRules, TokenMarketplaceRules, TokenTradeMode, +} from '@dashevo/evo-sdk'; const sdk = EvoSDK.testnetTrusted(); await sdk.connect(); @@ -46,62 +51,75 @@ const contractSchema = { tokenMetadata: { type: 'object', properties: { - tokenName: { type: 'string', maxLength: 64 }, - description: { type: 'string', maxLength: 256 }, + tokenName: { type: 'string', maxLength: 63, position: 0 }, + description: { type: 'string', maxLength: 256, position: 1 }, }, additionalProperties: false, }, }; -// Token configuration is passed separately when publishing -const tokenConfig = { - // Position 0 = first token in this contract - conventions: { - localizations: { - en: { - shouldCapitalize: true, - singularForm: 'CoffeeCoin', - pluralForm: 'CoffeeCoins', - }, - }, - decimals: 2, - }, - // The contract owner can mint manually - manualMinting: { - rules: { - // Allow the contract owner to mint - type: 'ownerOnly', - }, - }, - // The contract owner can burn their own tokens - manualBurning: { - rules: { - type: 'ownerOnly', - }, - }, - // Maximum supply (optional) - maxSupply: 1_000_000_00, // 1,000,000.00 with 2 decimals -}; +// Build the token configuration using SDK classes +const localization = new TokenConfigurationLocalization(true, 'CoffeeCoin', 'CoffeeCoins'); +const conventions = new TokenConfigurationConvention({ en: localization }, 2); + +const ownerOnly = new ChangeControlRules({ + authorizedToMakeChange: AuthorizedActionTakers.ContractOwner(), + adminActionTakers: AuthorizedActionTakers.ContractOwner(), +}); +const noOne = new ChangeControlRules({ + authorizedToMakeChange: AuthorizedActionTakers.NoOne(), + adminActionTakers: AuthorizedActionTakers.NoOne(), +}); + +const tokenConfig = new TokenConfiguration({ + conventions, + conventionsChangeRules: noOne, + baseSupply: 0n, + maxSupply: 1_000_000_00n, // 1,000,000.00 with 2 decimals + maxSupplyChangeRules: noOne, + keepsHistory: new TokenKeepsHistoryRules({ + isKeepingMintingHistory: true, + isKeepingBurningHistory: true, + isKeepingTransferHistory: true, + }), + distributionRules: new TokenDistributionRules({ + perpetualDistributionRules: noOne, + newTokensDestinationIdentityRules: noOne, + mintingAllowChoosingDestination: true, + mintingAllowChoosingDestinationRules: noOne, + changeDirectPurchasePricingRules: noOne, + }), + marketplaceRules: new TokenMarketplaceRules(TokenTradeMode.NotTradeable(), noOne), + manualMintingRules: ownerOnly, + manualBurningRules: ownerOnly, + freezeRules: noOne, + unfreezeRules: noOne, + destroyFrozenFundsRules: noOne, + emergencyActionRules: noOne, + mainControlGroupCanBeModified: AuthorizedActionTakers.NoOne(), +}); ``` ## Step 2: Publish the contract ```typescript -const contract = await sdk.contracts.publish({ - identityId, - documentSchemas: contractSchema, - tokens: [tokenConfig], - privateKeyWif, - signingKeyIndex, - nonce: await sdk.identities.nonce(identityId), +// Set up signing +const identity = await sdk.identities.fetch(identityId); +const identityKey = identity.publicKeys[signingKeyIndex]; +const signer = new IdentitySigner(); +signer.addKeyFromWif(privateKeyWif); + +const nonce = await sdk.identities.nonce(identityId); +const dataContract = new DataContract({ + ownerId: new Identifier(identityId), + identityNonce: nonce + 1n, + schemas: contractSchema, + tokens: { 0: tokenConfig }, }); +const contract = await sdk.contracts.publish({ dataContract, identityKey, signer }); -const contractId = contract.getId().toString(); +const contractId = contract.id.toString(); console.log('Contract published:', contractId); - -// Calculate the token ID (derived from contract ID + position) -const tokenId = await sdk.tokens.calculateId(contractId, 0); -console.log('Token ID:', tokenId); ``` ## Step 3: Mint tokens @@ -109,15 +127,21 @@ console.log('Token ID:', tokenId); The contract owner can mint tokens to any identity: ```typescript +// Token operations require a CRITICAL security level key. +// Fetch a key with the appropriate security level from the identity. +const criticalKey = identity.publicKeys[signingKeyIndex]; +const criticalSigner = new IdentitySigner(); +criticalSigner.addKeyFromWif(privateKeyWif); + // Mint 10,000.00 CoffeeCoins to yourself await sdk.tokens.mint({ - tokenId, - amount: 10_000_00, // 10,000.00 (2 decimal places) - recipientId: identityId, // mint to yourself - identityId, - privateKeyWif, - signingKeyIndex, - nonce: await sdk.identities.nonce(identityId), + dataContractId: new Identifier(contractId), + tokenPosition: 0, + amount: 10_000_00n, // 10,000.00 (2 decimal places) — must be bigint + recipientId: new Identifier(identityId), + identityId: new Identifier(identityId), + identityKey: criticalKey, + signer: criticalSigner, }); console.log('Minted 10,000 CoffeeCoins'); @@ -127,13 +151,13 @@ console.log('Minted 10,000 CoffeeCoins'); ```typescript await sdk.tokens.mint({ - tokenId, - amount: 500_00, // 500.00 CoffeeCoins - recipientId: 'RECIPIENT_IDENTITY_ID', - identityId, - privateKeyWif, - signingKeyIndex, - nonce: await sdk.identities.nonce(identityId), + dataContractId: new Identifier(contractId), + tokenPosition: 0, + amount: 500_00n, // 500.00 CoffeeCoins + recipientId: new Identifier('RECIPIENT_IDENTITY_ID'), + identityId: new Identifier(identityId), + identityKey: criticalKey, + signer: criticalSigner, }); ``` @@ -141,14 +165,17 @@ await sdk.tokens.mint({ ```typescript // Check your own balance -const myBalances = await sdk.tokens.identityBalances(identityId, [tokenId]); -const myBalance = myBalances.get(tokenId) ?? 0n; +const myBalances = await sdk.tokens.identityBalances(identityId, [contractId]); +let myBalance = 0n; +for (const [id, balance] of myBalances) { + if (id.toString() === contractId) myBalance = balance; +} console.log('My balance:', Number(myBalance) / 100, 'CoffeeCoins'); // Check multiple identities at once const balances = await sdk.tokens.balances( [identityId, 'OTHER_IDENTITY_ID'], - tokenId, + contractId, ); for (const [id, balance] of balances) { @@ -159,6 +186,7 @@ for (const [id, balance] of balances) { ### Check total supply ```typescript +const tokenId = await sdk.tokens.calculateId(contractId, 0); const supply = await sdk.tokens.totalSupply(tokenId); if (supply) { console.log('Total supply:', Number(supply.totalSupply) / 100, 'CoffeeCoins'); @@ -169,13 +197,13 @@ if (supply) { ```typescript await sdk.tokens.transfer({ - tokenId, - amount: 25_00, // 25.00 CoffeeCoins - recipientId: 'RECIPIENT_IDENTITY_ID', - identityId, - privateKeyWif, - signingKeyIndex, - nonce: await sdk.identities.nonce(identityId), + dataContractId: new Identifier(contractId), + tokenPosition: 0, + amount: 25_00n, // 25.00 CoffeeCoins + recipientId: new Identifier('RECIPIENT_IDENTITY_ID'), + senderId: new Identifier(identityId), + identityKey: criticalKey, + signer: criticalSigner, }); console.log('Transferred 25 CoffeeCoins'); @@ -187,12 +215,12 @@ Reduce the supply by burning tokens you own: ```typescript await sdk.tokens.burn({ - tokenId, - amount: 100_00, // 100.00 CoffeeCoins - identityId, - privateKeyWif, - signingKeyIndex, - nonce: await sdk.identities.nonce(identityId), + dataContractId: new Identifier(contractId), + tokenPosition: 0, + amount: 100_00n, // 100.00 CoffeeCoins + identityId: new Identifier(identityId), + identityKey: criticalKey, + signer: criticalSigner, }); console.log('Burned 100 CoffeeCoins'); @@ -203,7 +231,7 @@ console.log('Burned 100 CoffeeCoins'); Putting it all together as a complete script: ```typescript -import { EvoSDK } from '@dashevo/evo-sdk'; +import { EvoSDK, Identifier, IdentitySigner } from '@dashevo/evo-sdk'; async function main() { const sdk = EvoSDK.testnetTrusted(); @@ -211,21 +239,29 @@ async function main() { const identityId = 'YOUR_IDENTITY_ID'; const privateKeyWif = 'YOUR_PRIVATE_KEY_WIF'; - const tokenId = 'YOUR_TOKEN_ID'; // from step 2 + const contractId = 'YOUR_CONTRACT_ID'; // from step 2 + + // Set up signing (token ops require a CRITICAL security level key) + const identity = await sdk.identities.fetch(identityId); + const identityKey = identity.publicKeys[0]; + const signer = new IdentitySigner(); + signer.addKeyFromWif(privateKeyWif); // Check balance - const balances = await sdk.tokens.identityBalances(identityId, [tokenId]); - console.log('Balance:', balances.get(tokenId) ?? 0n); + const balances = await sdk.tokens.identityBalances(identityId, [contractId]); + for (const [id, balance] of balances) { + if (id.toString() === contractId) console.log('Balance:', balance); + } // Transfer await sdk.tokens.transfer({ - tokenId, - amount: 10_00, - recipientId: 'FRIEND_IDENTITY_ID', - identityId, - privateKeyWif, - signingKeyIndex: 0, - nonce: await sdk.identities.nonce(identityId), + dataContractId: new Identifier(contractId), + tokenPosition: 0, + amount: 10_00n, + recipientId: new Identifier('FRIEND_IDENTITY_ID'), + senderId: new Identifier(identityId), + identityKey, + signer, }); console.log('Transfer complete!'); diff --git a/book/src/evo-sdk/tutorials/car-sales.md b/book/src/evo-sdk/tutorials/car-sales.md index bcb1a96c919..b2d71b8e641 100644 --- a/book/src/evo-sdk/tutorials/car-sales.md +++ b/book/src/evo-sdk/tutorials/car-sales.md @@ -36,14 +36,14 @@ const carSalesSchema = { listing: { type: 'object', properties: { - make: { type: 'string', maxLength: 64 }, - model: { type: 'string', maxLength: 64 }, - year: { type: 'integer', minimum: 1900, maximum: 2100 }, - mileageKm: { type: 'integer', minimum: 0 }, - priceUsd: { type: 'integer', minimum: 0 }, - description: { type: 'string', maxLength: 1024 }, - imageUrl: { type: 'string', maxLength: 512, format: 'uri' }, - status: { type: 'string', enum: ['available', 'pending', 'sold'] }, + make: { type: 'string', maxLength: 63, position: 0 }, + model: { type: 'string', maxLength: 63, position: 1 }, + year: { type: 'integer', minimum: 1900, maximum: 2100, position: 2 }, + mileageKm: { type: 'integer', minimum: 0, position: 3 }, + priceUsd: { type: 'integer', minimum: 0, position: 4 }, + description: { type: 'string', maxLength: 1024, position: 5 }, + imageUrl: { type: 'string', maxLength: 512, format: 'uri', position: 6 }, + status: { type: 'string', enum: ['available', 'pending', 'sold'], position: 7 }, }, required: ['make', 'model', 'year', 'priceUsd', 'status'], additionalProperties: false, @@ -51,10 +51,10 @@ const carSalesSchema = { review: { type: 'object', properties: { - sellerId: { type: 'string', maxLength: 44 }, - listingId: { type: 'string', maxLength: 44 }, - rating: { type: 'integer', minimum: 1, maximum: 5 }, - comment: { type: 'string', maxLength: 512 }, + sellerId: { type: 'string', maxLength: 44, position: 0 }, + listingId: { type: 'string', maxLength: 44, position: 1 }, + rating: { type: 'integer', minimum: 1, maximum: 5, position: 2 }, + comment: { type: 'string', maxLength: 512, position: 3 }, }, required: ['sellerId', 'rating'], additionalProperties: false, @@ -65,7 +65,7 @@ const carSalesSchema = { ## Step 2: Connect and publish the contract ```typescript -import { EvoSDK, wallet } from '@dashevo/evo-sdk'; +import { EvoSDK, DataContract, Document, Identifier, IdentitySigner } from '@dashevo/evo-sdk'; const sdk = EvoSDK.testnetTrusted(); await sdk.connect(); @@ -75,16 +75,22 @@ const identityId = 'YOUR_IDENTITY_ID'; const privateKeyWif = 'YOUR_PRIVATE_KEY_WIF'; const signingKeyIndex = 0; +// Set up signing +const identity = await sdk.identities.fetch(identityId); +const identityKey = identity.publicKeys[signingKeyIndex]; +const signer = new IdentitySigner(); +signer.addKeyFromWif(privateKeyWif); + // Publish the data contract -const contract = await sdk.contracts.publish({ - identityId, - documentSchemas: carSalesSchema, - privateKeyWif, - signingKeyIndex, - nonce: await sdk.identities.nonce(identityId), +const nonce = await sdk.identities.nonce(identityId); +const dataContract = new DataContract({ + ownerId: new Identifier(identityId), + identityNonce: nonce + 1n, + schemas: carSalesSchema, }); +const contract = await sdk.contracts.publish({ dataContract, identityKey, signer }); -const contractId = contract.getId().toString(); +const contractId = contract.id.toString(); console.log('Contract published:', contractId); ``` @@ -93,12 +99,11 @@ Save the `contractId` — you will need it for all subsequent operations. ## Step 3: Create a listing ```typescript -const nonce = await sdk.identities.contractNonce(identityId, contractId); - -await sdk.documents.create({ - contractId, - documentType: 'listing', - document: { +const doc = new Document({ + documentTypeName: 'listing', + dataContractId: new Identifier(contractId), + ownerId: new Identifier(identityId), + properties: { make: 'Toyota', model: 'Camry', year: 2021, @@ -107,11 +112,8 @@ await sdk.documents.create({ description: 'Well-maintained, single owner, full service history.', status: 'available', }, - identityId, - privateKeyWif, - signingKeyIndex, - nonce, }); +await sdk.documents.create({ document: doc, identityKey, signer }); console.log('Listing created!'); ``` @@ -121,8 +123,8 @@ console.log('Listing created!'); ```typescript // Fetch all available listings const results = await sdk.documents.query({ - contractId, - documentType: 'listing', + dataContractId: contractId, + documentTypeName: 'listing', where: [['status', '==', 'available']], orderBy: [['priceUsd', 'asc']], limit: 20, @@ -130,7 +132,7 @@ const results = await sdk.documents.query({ for (const [id, doc] of results) { if (!doc) continue; - const data = doc.getData(); + const data = doc.properties as Record; console.log(`${data.year} ${data.make} ${data.model} — $${data.priceUsd}`); console.log(` ID: ${id}`); } @@ -140,8 +142,8 @@ for (const [id, doc] of results) { ```typescript const toyotas = await sdk.documents.query({ - contractId, - documentType: 'listing', + dataContractId: contractId, + documentTypeName: 'listing', where: [ ['make', '==', 'Toyota'], ['status', '==', 'available'], @@ -157,24 +159,11 @@ Mark a listing as sold: ```typescript const listingId = 'THE_LISTING_DOCUMENT_ID'; -await sdk.documents.replace({ - contractId, - documentType: 'listing', - documentId: listingId, - document: { - make: 'Toyota', - model: 'Camry', - year: 2021, - mileageKm: 45000, - priceUsd: 22500, - description: 'Well-maintained, single owner, full service history.', - status: 'sold', - }, - identityId, - privateKeyWif, - signingKeyIndex, - nonce: await sdk.identities.contractNonce(identityId, contractId), -}); +// Fetch the existing document, modify it, and bump the revision +const existing = await sdk.documents.get(contractId, 'listing', listingId); +existing.properties = { ...existing.properties, status: 'sold' }; +existing.revision = (existing.revision ?? 0n) + 1n; +await sdk.documents.replace({ document: existing, identityKey, signer }); console.log('Listing marked as sold'); ``` @@ -182,28 +171,32 @@ console.log('Listing marked as sold'); ## Step 6: Leave a review ```typescript -await sdk.documents.create({ - contractId, - documentType: 'review', - document: { +// Set up buyer signing +const buyerIdentity = await sdk.identities.fetch(buyerIdentityId); +const buyerKey = buyerIdentity.publicKeys[0]; +const buyerSigner = new IdentitySigner(); +buyerSigner.addKeyFromWif(buyerKeyWif); + +const reviewDoc = new Document({ + documentTypeName: 'review', + dataContractId: new Identifier(contractId), + ownerId: new Identifier(buyerIdentityId), + properties: { sellerId: 'SELLER_IDENTITY_ID', listingId: 'THE_LISTING_DOCUMENT_ID', rating: 5, comment: 'Great seller, car was exactly as described!', }, - identityId: buyerIdentityId, - privateKeyWif: buyerKeyWif, - signingKeyIndex: 0, - nonce: await sdk.identities.contractNonce(buyerIdentityId, contractId), }); +await sdk.documents.create({ document: reviewDoc, identityKey: buyerKey, signer: buyerSigner }); ``` ### Query reviews for a seller ```typescript const reviews = await sdk.documents.query({ - contractId, - documentType: 'review', + dataContractId: contractId, + documentTypeName: 'review', where: [['sellerId', '==', 'SELLER_IDENTITY_ID']], orderBy: [['rating', 'desc']], limit: 50, @@ -213,7 +206,8 @@ let totalRating = 0; let count = 0; for (const [, doc] of reviews) { if (!doc) continue; - totalRating += doc.getData().rating; + const props = doc.properties as Record; + totalRating += props.rating as number; count++; } console.log(`Average rating: ${(totalRating / count).toFixed(1)} (${count} reviews)`); diff --git a/book/src/evo-sdk/tutorials/card-game.md b/book/src/evo-sdk/tutorials/card-game.md index 89567f8eee7..79b24fd40ed 100644 --- a/book/src/evo-sdk/tutorials/card-game.md +++ b/book/src/evo-sdk/tutorials/card-game.md @@ -37,17 +37,23 @@ The contract defines three document types and one token: - **GemToken** — In-game currency for buying card packs ```typescript +import { + TokenConfigurationConvention, TokenConfigurationLocalization, TokenConfiguration, + ChangeControlRules, AuthorizedActionTakers, TokenDistributionRules, + TokenKeepsHistoryRules, TokenMarketplaceRules, TokenTradeMode, +} from '@dashevo/evo-sdk'; + const gameSchema = { card: { type: 'object', properties: { - name: { type: 'string', maxLength: 64 }, - element: { type: 'string', enum: ['fire', 'water', 'earth', 'air', 'shadow'] }, - rarity: { type: 'string', enum: ['common', 'uncommon', 'rare', 'legendary'] }, - power: { type: 'integer', minimum: 1, maximum: 100 }, - defense: { type: 'integer', minimum: 1, maximum: 100 }, - ability: { type: 'string', maxLength: 128 }, - edition: { type: 'integer', minimum: 1 }, + name: { type: 'string', maxLength: 63, position: 0 }, + element: { type: 'string', enum: ['fire', 'water', 'earth', 'air', 'shadow'], position: 1 }, + rarity: { type: 'string', enum: ['common', 'uncommon', 'rare', 'legendary'], position: 2 }, + power: { type: 'integer', minimum: 1, maximum: 100, position: 3 }, + defense: { type: 'integer', minimum: 1, maximum: 100, position: 4 }, + ability: { type: 'string', maxLength: 128, position: 5 }, + edition: { type: 'integer', minimum: 1, position: 6 }, }, required: ['name', 'element', 'rarity', 'power', 'defense', 'edition'], additionalProperties: false, @@ -55,12 +61,13 @@ const gameSchema = { deck: { type: 'object', properties: { - name: { type: 'string', maxLength: 64 }, + name: { type: 'string', maxLength: 63, position: 0 }, cardIds: { type: 'array', items: { type: 'string', maxLength: 44 }, minItems: 5, maxItems: 10, + position: 1, }, }, required: ['name', 'cardIds'], @@ -69,43 +76,64 @@ const gameSchema = { match: { type: 'object', properties: { - player1Id: { type: 'string', maxLength: 44 }, - player2Id: { type: 'string', maxLength: 44 }, - winnerId: { type: 'string', maxLength: 44 }, - player1Score: { type: 'integer', minimum: 0 }, - player2Score: { type: 'integer', minimum: 0 }, - timestamp: { type: 'integer' }, + player1Id: { type: 'string', maxLength: 44, position: 0 }, + player2Id: { type: 'string', maxLength: 44, position: 1 }, + winnerId: { type: 'string', maxLength: 44, position: 2 }, + player1Score: { type: 'integer', minimum: 0, position: 3 }, + player2Score: { type: 'integer', minimum: 0, position: 4 }, + timestamp: { type: 'integer', position: 5 }, }, required: ['player1Id', 'player2Id', 'winnerId', 'timestamp'], additionalProperties: false, }, }; -const gemTokenConfig = { - conventions: { - localizations: { - en: { - shouldCapitalize: true, - singularForm: 'Gem', - pluralForm: 'Gems', - }, - }, - decimals: 0, // whole numbers only - }, - manualMinting: { - rules: { type: 'ownerOnly' }, - }, - manualBurning: { - rules: { type: 'ownerOnly' }, - }, - maxSupply: 10_000_000, // 10 million Gems total -}; +// Build the token configuration using SDK classes +const localization = new TokenConfigurationLocalization(true, 'Gem', 'Gems'); +const conventions = new TokenConfigurationConvention({ en: localization }, 0); + +const ownerOnly = new ChangeControlRules({ + authorizedToMakeChange: AuthorizedActionTakers.ContractOwner(), + adminActionTakers: AuthorizedActionTakers.ContractOwner(), +}); +const noOne = new ChangeControlRules({ + authorizedToMakeChange: AuthorizedActionTakers.NoOne(), + adminActionTakers: AuthorizedActionTakers.NoOne(), +}); + +const gemTokenConfig = new TokenConfiguration({ + conventions, + conventionsChangeRules: noOne, + baseSupply: 0n, + maxSupply: 10_000_000n, // 10 million Gems total + maxSupplyChangeRules: noOne, + keepsHistory: new TokenKeepsHistoryRules({ + isKeepingMintingHistory: true, + isKeepingBurningHistory: true, + isKeepingTransferHistory: true, + }), + distributionRules: new TokenDistributionRules({ + perpetualDistributionRules: noOne, + newTokensDestinationIdentityRules: noOne, + mintingAllowChoosingDestination: true, + mintingAllowChoosingDestinationRules: noOne, + changeDirectPurchasePricingRules: noOne, + }), + marketplaceRules: new TokenMarketplaceRules(TokenTradeMode.NotTradeable(), noOne), + manualMintingRules: ownerOnly, + manualBurningRules: ownerOnly, + freezeRules: noOne, + unfreezeRules: noOne, + destroyFrozenFundsRules: noOne, + emergencyActionRules: noOne, + mainControlGroupCanBeModified: AuthorizedActionTakers.NoOne(), +}); ``` ## Step 2: Deploy the contract ```typescript -import { EvoSDK } from '@dashevo/evo-sdk'; +import { EvoSDK, DataContract, Document, Identifier, IdentitySigner } from '@dashevo/evo-sdk'; const sdk = EvoSDK.testnetTrusted(); await sdk.connect(); @@ -114,20 +142,28 @@ await sdk.connect(); const operatorId = 'OPERATOR_IDENTITY_ID'; const operatorKey = 'OPERATOR_PRIVATE_KEY_WIF'; +// Set up signing +const operatorIdentity = await sdk.identities.fetch(operatorId); +const operatorIdentityKey = operatorIdentity.publicKeys[0]; +const operatorSigner = new IdentitySigner(); +operatorSigner.addKeyFromWif(operatorKey); + +const nonce = await sdk.identities.nonce(operatorId); +const dataContract = new DataContract({ + ownerId: new Identifier(operatorId), + identityNonce: nonce + 1n, + schemas: gameSchema, + tokens: { 0: gemTokenConfig }, +}); const contract = await sdk.contracts.publish({ - identityId: operatorId, - documentSchemas: gameSchema, - tokens: [gemTokenConfig], - privateKeyWif: operatorKey, - signingKeyIndex: 0, - nonce: await sdk.identities.nonce(operatorId), + dataContract, + identityKey: operatorIdentityKey, + signer: operatorSigner, }); -const contractId = contract.getId().toString(); -const gemTokenId = await sdk.tokens.calculateId(contractId, 0); +const contractId = contract.id.toString(); console.log('Game contract:', contractId); -console.log('Gem token:', gemTokenId); ``` ## Step 3: Mint starter Gems for a new player @@ -136,15 +172,16 @@ When a player joins, give them starter Gems: ```typescript async function onboardPlayer(playerId: string) { + // Token operations require a CRITICAL security level key // Gift 100 Gems to the new player await sdk.tokens.mint({ - tokenId: gemTokenId, - amount: 100, - recipientId: playerId, - identityId: operatorId, - privateKeyWif: operatorKey, - signingKeyIndex: 0, - nonce: await sdk.identities.nonce(operatorId), + dataContractId: new Identifier(contractId), + tokenPosition: 0, + amount: 100n, + recipientId: new Identifier(playerId), + identityId: new Identifier(operatorId), + identityKey: operatorIdentityKey, + signer: operatorSigner, }); console.log(`Welcomed ${playerId} with 100 Gems`); @@ -168,14 +205,16 @@ const starterPack = [ async function createCards(cards: typeof starterPack) { for (const card of cards) { + const cardDoc = new Document({ + documentTypeName: 'card', + dataContractId: new Identifier(contractId), + ownerId: new Identifier(operatorId), + properties: card, + }); await sdk.documents.create({ - contractId, - documentType: 'card', - document: card, - identityId: operatorId, - privateKeyWif: operatorKey, - signingKeyIndex: 0, - nonce: await sdk.identities.contractNonce(operatorId, contractId), + document: cardDoc, + identityKey: operatorIdentityKey, + signer: operatorSigner, }); console.log(`Created: ${card.name} (${card.rarity})`); } @@ -191,26 +230,32 @@ The purchase flow: 2. Operator transfers card documents to the player ```typescript -const PACK_PRICE = 50; // 50 Gems per pack +const PACK_PRICE = 50n; // 50 Gems per pack async function buyPack(playerId: string, playerKey: string) { + // Set up player signing (token ops require CRITICAL security level key) + const playerIdentity = await sdk.identities.fetch(playerId); + const playerIdentityKey = playerIdentity.publicKeys[0]; + const playerSigner = new IdentitySigner(); + playerSigner.addKeyFromWif(playerKey); + // Player pays Gems to the operator await sdk.tokens.transfer({ - tokenId: gemTokenId, + dataContractId: new Identifier(contractId), + tokenPosition: 0, amount: PACK_PRICE, - recipientId: operatorId, - identityId: playerId, - privateKeyWif: playerKey, - signingKeyIndex: 0, - nonce: await sdk.identities.nonce(playerId), + recipientId: new Identifier(operatorId), + senderId: new Identifier(playerId), + identityKey: playerIdentityKey, + signer: playerSigner, }); console.log(`Player paid ${PACK_PRICE} Gems`); // Operator transfers cards to the player // (In production, select random cards from available pool) const availableCards = await sdk.documents.query({ - contractId, - documentType: 'card', + dataContractId: contractId, + documentTypeName: 'card', where: [['$ownerId', '==', operatorId]], limit: 5, }); @@ -218,16 +263,13 @@ async function buyPack(playerId: string, playerKey: string) { for (const [cardId, card] of availableCards) { if (!card) continue; await sdk.documents.transfer({ - contractId, - documentType: 'card', - documentId: cardId, - recipientId: playerId, - identityId: operatorId, - privateKeyWif: operatorKey, - signingKeyIndex: 0, - nonce: await sdk.identities.contractNonce(operatorId, contractId), + document: card, + recipientId: new Identifier(playerId), + identityKey: operatorIdentityKey, + signer: operatorSigner, }); - console.log(`Transferred ${card.getData().name} to player`); + const props = card.properties as Record; + console.log(`Transferred ${props.name} to player`); } } ``` @@ -237,8 +279,8 @@ async function buyPack(playerId: string, playerKey: string) { ```typescript async function getCollection(playerId: string) { const cards = await sdk.documents.query({ - contractId, - documentType: 'card', + dataContractId: contractId, + documentTypeName: 'card', where: [['$ownerId', '==', playerId]], orderBy: [['power', 'desc']], limit: 100, @@ -247,7 +289,7 @@ async function getCollection(playerId: string) { console.log(`\n${playerId}'s collection:`); for (const [id, card] of cards) { if (!card) continue; - const d = card.getData(); + const d = card.properties as Record; console.log(` [${d.rarity}] ${d.name} — ${d.element} — ATK:${d.power} DEF:${d.defense}`); } @@ -259,8 +301,8 @@ async function getCollection(playerId: string) { ```typescript const legendaries = await sdk.documents.query({ - contractId, - documentType: 'card', + dataContractId: contractId, + documentTypeName: 'card', where: [ ['$ownerId', '==', playerId], ['rarity', '==', 'legendary'], @@ -278,28 +320,35 @@ async function tradeCards( fromId: string, fromKey: string, fromCardId: string, toId: string, toKey: string, toCardId: string, ) { + // Set up signers for both players + const fromIdentity = await sdk.identities.fetch(fromId); + const fromIdentityKey = fromIdentity.publicKeys[0]; + const fromSigner = new IdentitySigner(); + fromSigner.addKeyFromWif(fromKey); + + const toIdentity = await sdk.identities.fetch(toId); + const toIdentityKey = toIdentity.publicKeys[0]; + const toSigner = new IdentitySigner(); + toSigner.addKeyFromWif(toKey); + + // Fetch both card documents + const fromCard = await sdk.documents.get(contractId, 'card', fromCardId); + const toCard = await sdk.documents.get(contractId, 'card', toCardId); + // Player A sends their card to Player B await sdk.documents.transfer({ - contractId, - documentType: 'card', - documentId: fromCardId, - recipientId: toId, - identityId: fromId, - privateKeyWif: fromKey, - signingKeyIndex: 0, - nonce: await sdk.identities.contractNonce(fromId, contractId), + document: fromCard, + recipientId: new Identifier(toId), + identityKey: fromIdentityKey, + signer: fromSigner, }); // Player B sends their card to Player A await sdk.documents.transfer({ - contractId, - documentType: 'card', - documentId: toCardId, - recipientId: fromId, - identityId: toId, - privateKeyWif: toKey, - signingKeyIndex: 0, - nonce: await sdk.identities.contractNonce(toId, contractId), + document: toCard, + recipientId: new Identifier(fromId), + identityKey: toIdentityKey, + signer: toSigner, }); console.log('Trade complete!'); @@ -314,10 +363,11 @@ async function recordMatch( winnerId: string, p1Score: number, p2Score: number, ) { - await sdk.documents.create({ - contractId, - documentType: 'match', - document: { + const matchDoc = new Document({ + documentTypeName: 'match', + dataContractId: new Identifier(contractId), + ownerId: new Identifier(operatorId), + properties: { player1Id, player2Id, winnerId, @@ -325,21 +375,22 @@ async function recordMatch( player2Score: p2Score, timestamp: Date.now(), }, - identityId: operatorId, - privateKeyWif: operatorKey, - signingKeyIndex: 0, - nonce: await sdk.identities.contractNonce(operatorId, contractId), + }); + await sdk.documents.create({ + document: matchDoc, + identityKey: operatorIdentityKey, + signer: operatorSigner, }); - // Reward the winner with Gems + // Reward the winner with Gems (token ops require CRITICAL security level key) await sdk.tokens.mint({ - tokenId: gemTokenId, - amount: 10, - recipientId: winnerId, - identityId: operatorId, - privateKeyWif: operatorKey, - signingKeyIndex: 0, - nonce: await sdk.identities.nonce(operatorId), + dataContractId: new Identifier(contractId), + tokenPosition: 0, + amount: 10n, + recipientId: new Identifier(winnerId), + identityId: new Identifier(operatorId), + identityKey: operatorIdentityKey, + signer: operatorSigner, }); console.log(`Match recorded. ${winnerId} wins and earns 10 Gems!`); @@ -353,8 +404,8 @@ Query match history to build a win count: ```typescript async function getWinCounts() { const matches = await sdk.documents.query({ - contractId, - documentType: 'match', + dataContractId: contractId, + documentTypeName: 'match', orderBy: [['timestamp', 'desc']], limit: 100, }); @@ -362,7 +413,8 @@ async function getWinCounts() { const wins = new Map(); for (const [, doc] of matches) { if (!doc) continue; - const winner = doc.getData().winnerId; + const props = doc.properties as Record; + const winner = props.winnerId as string; wins.set(winner, (wins.get(winner) ?? 0) + 1); } diff --git a/book/src/evo-sdk/tutorials/react-integration.md b/book/src/evo-sdk/tutorials/react-integration.md index a20b6dc1cd2..7bc4f00fad6 100644 --- a/book/src/evo-sdk/tutorials/react-integration.md +++ b/book/src/evo-sdk/tutorials/react-integration.md @@ -221,7 +221,10 @@ export function useTokenBalance(identityId: string, tokenId: string) { return useDashQuery( async (sdk) => { const balances = await sdk.tokens.identityBalances(identityId, [tokenId]); - return balances.get(tokenId) ?? 0n; + for (const [id, balance] of balances) { + if (id.toString() === tokenId) return balance; + } + return 0n; }, [identityId, tokenId], ); @@ -323,9 +326,9 @@ export function IdentityViewer() { {error &&

{error}

} {identity && (
-

ID: {identity.getId().toString()}

-

Balance: {identity.getBalance().toString()} credits

-

Public keys: {identity.getPublicKeys().length}

+

ID: {identity.id.toString()}

+

Balance: {identity.balance.toString()} credits

+

Public keys: {identity.publicKeys.length}

)} @@ -344,8 +347,8 @@ const CONTRACT_ID = 'YOUR_CONTRACT_ID'; export function ListingsList() { const { data: results, isLoading, error, refetch } = useDocuments({ - contractId: CONTRACT_ID, - documentType: 'listing', + dataContractId: CONTRACT_ID, + documentTypeName: 'listing', where: [['status', '==', 'available']], orderBy: [['priceUsd', 'asc']], limit: 20, @@ -362,7 +365,7 @@ export function ListingsList() {
    {[...results.entries()].map(([id, doc]) => { if (!doc) return null; - const d = doc.getData(); + const d = doc.properties as Record; return (
  • {d.year} {d.make} {d.model} — ${d.priceUsd} @@ -381,11 +384,13 @@ export function ListingsList() { ```tsx import { useState, useCallback } from 'react'; +import { Document, Identifier, IdentitySigner } from '@dashevo/evo-sdk'; import { useDashMutation } from '../hooks/useDashMutation'; const CONTRACT_ID = 'YOUR_CONTRACT_ID'; const IDENTITY_ID = 'YOUR_IDENTITY_ID'; const PRIVATE_KEY = 'YOUR_PRIVATE_KEY_WIF'; +const SIGNING_KEY_INDEX = 0; export function CreateListing() { const [make, setMake] = useState(''); @@ -395,11 +400,17 @@ export function CreateListing() { const mutation = useDashMutation( useCallback( - (sdk) => - sdk.documents.create({ - contractId: CONTRACT_ID, - documentType: 'listing', - document: { + async (sdk) => { + const identity = await sdk.identities.fetch(IDENTITY_ID); + const identityKey = identity.publicKeys[SIGNING_KEY_INDEX]; + const signer = new IdentitySigner(); + signer.addKeyFromWif(PRIVATE_KEY); + + const doc = new Document({ + documentTypeName: 'listing', + dataContractId: new Identifier(CONTRACT_ID), + ownerId: new Identifier(IDENTITY_ID), + properties: { make, model, year, @@ -407,11 +418,9 @@ export function CreateListing() { mileageKm: 0, status: 'available', }, - identityId: IDENTITY_ID, - privateKeyWif: PRIVATE_KEY, - signingKeyIndex: 0, - nonce: sdk.identities.contractNonce(IDENTITY_ID, CONTRACT_ID), - }), + }); + return sdk.documents.create({ document: doc, identityKey, signer }); + }, [make, model, year, price], ), );