This is page 39 of 93. Use http://codebase.md/goplausible/algorand-mcp?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── llms-install.md
├── llms.txt
├── package.json
├── packages
│ ├── client
│ │ ├── .env.example
│ │ ├── package.json
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── env.ts
│ │ │ ├── index.ts
│ │ │ └── LocalWallet.ts
│ │ └── tsconfig.json
│ └── server
│ ├── .env.example
│ ├── API specs
│ │ ├── algod_api.json
│ │ ├── indexer_api.json
│ │ ├── mcp.json
│ │ ├── nfd_api.json
│ │ ├── ultrade_api.json
│ │ ├── vestige_api.json
│ │ └── vestige_free_api.json
│ ├── Dockerfile
│ ├── jest.config.js
│ ├── package.json
│ ├── README.md
│ ├── smithery.yaml
│ ├── src
│ │ ├── algorand-client.ts
│ │ ├── env.ts
│ │ ├── index.ts
│ │ ├── resources
│ │ │ ├── index.ts
│ │ │ ├── knowledge
│ │ │ │ ├── ARCs.txt
│ │ │ │ ├── developers-algokit-architecture-decisions.txt
│ │ │ │ ├── developers-algokit-cli.txt
│ │ │ │ ├── developers-algokit-utils-python.txt
│ │ │ │ ├── developers-algokit-utils-typescript.txt
│ │ │ │ ├── developers-clis.txt
│ │ │ │ ├── developers-details.txt
│ │ │ │ ├── developers-liquid-auth.txt
│ │ │ │ ├── developers-nodes.txt
│ │ │ │ ├── developers-puya.txt
│ │ │ │ ├── developers-python.txt
│ │ │ │ ├── developers-sdks-js.txt
│ │ │ │ ├── developers-sdks-python.txt
│ │ │ │ ├── developers-tealscript.txt
│ │ │ │ ├── developers.txt
│ │ │ │ ├── index.ts
│ │ │ │ ├── taxonomy
│ │ │ │ │ ├── algokit-cli:README.md
│ │ │ │ │ ├── algokit:cli:algokit.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2022-11-14_sandbox-approach.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2022-11-22_beaker-testing-strategy.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2023-01-11_beaker_productionisation_review.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2023-01-11_brew_install.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2023-01-12_smart-contract-deployment.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2023-06-06_frontend-templates.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2023-07-19_advanced_generate_command.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2024-01-13_native_binaries.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2024-01-23_init-wizard-v2.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2024-01-31_binary_distribution.md
│ │ │ │ │ ├── algokit:cli:architecture-decisions:2024-03-06_local_dev_ui_packaging.md
│ │ │ │ │ ├── algokit:cli:articles:output_stability.md
│ │ │ │ │ ├── algokit:cli:cli:index.md
│ │ │ │ │ ├── algokit:cli:features:compile.md
│ │ │ │ │ ├── algokit:cli:features:completions.md
│ │ │ │ │ ├── algokit:cli:features:config.md
│ │ │ │ │ ├── algokit:cli:features:dispenser.md
│ │ │ │ │ ├── algokit:cli:features:doctor.md
│ │ │ │ │ ├── algokit:cli:features:explore.md
│ │ │ │ │ ├── algokit:cli:features:generate.md
│ │ │ │ │ ├── algokit:cli:features:goal.md
│ │ │ │ │ ├── algokit:cli:features:init.md
│ │ │ │ │ ├── algokit:cli:features:localnet.md
│ │ │ │ │ ├── algokit:cli:features:project:bootstrap.md
│ │ │ │ │ ├── algokit:cli:features:project:deploy.md
│ │ │ │ │ ├── algokit:cli:features:project:link.md
│ │ │ │ │ ├── algokit:cli:features:project:list.md
│ │ │ │ │ ├── algokit:cli:features:project:run.md
│ │ │ │ │ ├── algokit:cli:features:project.md
│ │ │ │ │ ├── algokit:cli:features:tasks:analyze.md
│ │ │ │ │ ├── algokit:cli:features:tasks:ipfs.md
│ │ │ │ │ ├── algokit:cli:features:tasks:mint.md
│ │ │ │ │ ├── algokit:cli:features:tasks:nfd.md
│ │ │ │ │ ├── algokit:cli:features:tasks:opt.md
│ │ │ │ │ ├── algokit:cli:features:tasks:send.md
│ │ │ │ │ ├── algokit:cli:features:tasks:sign.md
│ │ │ │ │ ├── algokit:cli:features:tasks:transfer.md
│ │ │ │ │ ├── algokit:cli:features:tasks:vanity_address.md
│ │ │ │ │ ├── algokit:cli:features:tasks:wallet.md
│ │ │ │ │ ├── algokit:cli:features:tasks.md
│ │ │ │ │ ├── algokit:cli:tutorials:algokit-template.md
│ │ │ │ │ ├── algokit:cli:tutorials:intro.md
│ │ │ │ │ ├── algokit:cli:tutorials:smart-contracts.md
│ │ │ │ │ ├── algokit:docs:testnet_api.md
│ │ │ │ │ ├── algokit:lora:README.md
│ │ │ │ │ ├── algokit:README.md
│ │ │ │ │ ├── algokit:utils:python:markdown:apidocs:algokit_utils:algokit_utils.md
│ │ │ │ │ ├── algokit:utils:python:markdown:capabilities:account.md
│ │ │ │ │ ├── algokit:utils:python:markdown:capabilities:app-client.md
│ │ │ │ │ ├── algokit:utils:python:markdown:capabilities:app-deploy.md
│ │ │ │ │ ├── algokit:utils:python:markdown:capabilities:client.md
│ │ │ │ │ ├── algokit:utils:python:markdown:capabilities:debugger.md
│ │ │ │ │ ├── algokit:utils:python:markdown:capabilities:dispenser-client.md
│ │ │ │ │ ├── algokit:utils:python:markdown:capabilities:transfer.md
│ │ │ │ │ ├── algokit:utils:python:markdown:index.md
│ │ │ │ │ ├── algokit:utils:python:README.md
│ │ │ │ │ ├── algokit:utils:python:source:capabilities:account.md
│ │ │ │ │ ├── algokit:utils:python:source:capabilities:app-client.md
│ │ │ │ │ ├── algokit:utils:python:source:capabilities:app-deploy.md
│ │ │ │ │ ├── algokit:utils:python:source:capabilities:client.md
│ │ │ │ │ ├── algokit:utils:python:source:capabilities:debugger.md
│ │ │ │ │ ├── algokit:utils:python:source:capabilities:dispenser-client.md
│ │ │ │ │ ├── algokit:utils:python:source:capabilities:transfer.md
│ │ │ │ │ ├── algokit:utils:python:source:index.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:account.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:algorand-client.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:amount.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:app-client.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:app-deploy.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:app.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:asset.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:client.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:debugging.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:dispenser-client.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:event-emitter.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:indexer.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:testing.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:transaction-composer.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:transaction.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:transfer.md
│ │ │ │ │ ├── algokit:utils:typescript:capabilities:typed-app-clients.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:testing.TestLogger.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:testing.TransactionLogger.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_account_manager.AccountManager.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_account.MultisigAccount.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_account.SigningAccount.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_algo_http_client_with_retry.AlgoHttpClientWithRetry.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_algorand_client_transaction_creator.AlgorandClientTransactionCreator.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_algorand_client_transaction_sender.AlgorandClientTransactionSender.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_algorand_client.AlgorandClient.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_amount.AlgoAmount.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_app_arc56.Arc56Method.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_app_client.AppClient.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_app_client.ApplicationClient.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_app_deployer.AppDeployer.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_app_factory.AppFactory.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_app_manager.AppManager.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_asset_manager.AssetManager.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_async_event_emitter.AsyncEventEmitter.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_client_manager.ClientManager.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_composer.TransactionComposer.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_config.UpdatableConfig.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_dispenser_client.TestNetDispenserApiClient.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_kmd_account_manager.KmdAccountManager.md
│ │ │ │ │ ├── algokit:utils:typescript:code:classes:types_logic_error.LogicError.md
│ │ │ │ │ ├── algokit:utils:typescript:code:enums:types_app.OnSchemaBreak.md
│ │ │ │ │ ├── algokit:utils:typescript:code:enums:types_app.OnUpdate.md
│ │ │ │ │ ├── algokit:utils:typescript:code:enums:types_indexer.AccountStatus.md
│ │ │ │ │ ├── algokit:utils:typescript:code:enums:types_indexer.ApplicationOnComplete.md
│ │ │ │ │ ├── algokit:utils:typescript:code:enums:types_indexer.SignatureType.md
│ │ │ │ │ ├── algokit:utils:typescript:code:enums:types_lifecycle_events.EventType.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_account_manager.EnsureFundedResult.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_account.AccountConfig.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_account.TransactionSignerAccount.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_algorand_client_interface.AlgorandClientInterface.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_arc56.Arc56Contract.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_arc56.Event.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_arc56.Method.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_arc56.ProgramSourceInfo.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_arc56.StorageKey.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_arc56.StorageMap.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_arc56.StructField.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.AppClientCallABIArgs.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.AppClientCallCoreParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.AppClientCompilationParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.AppClientCompilationResult.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.AppClientDeployCallInterfaceParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.AppClientDeployCoreParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.AppClientDeployParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.AppClientParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.AppSourceMaps.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.FundAppAccountParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.ResolveAppById.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.ResolveAppByIdBase.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_client.SourceMapExport.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_deployer.AppLookup.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_deployer.AppMetadata.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_factory.AppFactoryParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_manager.AppInformation.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_manager.BoxReference.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_manager.BoxValueRequestParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_manager.BoxValuesRequestParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_spec.AppSources.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_spec.AppSpec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_spec.CallConfig.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_spec.DeclaredSchemaValueSpec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_spec.Hint.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_spec.ReservedSchemaValueSpec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_spec.Schema.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_spec.SchemaSpec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_spec.StateSchemaSpec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app_spec.Struct.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.AppCallParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.AppCallTransactionResultOfType.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.AppCompilationResult.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.AppDeploymentParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.AppDeployMetadata.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.AppLookup.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.AppMetadata.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.AppReference.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.AppState.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.AppStorageSchema.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.BoxName.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.BoxReference.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.BoxValueRequestParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.BoxValuesRequestParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.CompiledTeal.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.CoreAppCallArgs.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.CreateAppParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.RawAppCallArgs.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.TealTemplateParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_app.UpdateAppParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_asset_manager.AssetInformation.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_asset_manager.BulkAssetOptInOutResult.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_asset.AssetBulkOptInOutParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_asset.AssetOptInParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_asset.AssetOptOutParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_asset.CreateAssetParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_client_manager.AlgoSdkClients.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_client_manager.TypedAppClient.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_client_manager.TypedAppFactory.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_composer.BuiltTransactions.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_config.Config.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_debugging.AVMTracesEventData.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_debugging.TealSourceDebugEventData.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_debugging.TealSourcesDebugEventData.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_dispenser_client.DispenserFundResponse.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_dispenser_client.DispenserLimitResponse.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_dispenser_client.TestNetDispenserApiClientParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_indexer.LookupAssetHoldingsOptions.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_logic_error.LogicErrorDetails.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_network_client.AlgoClientConfig.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_network_client.AlgoConfig.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_network_client.NetworkDetails.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_testing.AlgoKitLogCaptureFixture.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_testing.AlgorandFixture.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_testing.AlgorandFixtureConfig.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_testing.AlgorandTestAutomationContext.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_testing.GetTestAccountParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_testing.LogSnapshotConfig.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transaction.AtomicTransactionComposerToSend.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transaction.ConfirmedTransactionResult.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transaction.ConfirmedTransactionResults.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transaction.SendAtomicTransactionComposerResults.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transaction.SendParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transaction.SendTransactionParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transaction.SendTransactionResult.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transaction.SendTransactionResults.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transaction.TransactionGroupToSend.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transaction.TransactionToSign.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transfer.AlgoRekeyParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transfer.AlgoTransferParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transfer.EnsureFundedParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transfer.EnsureFundedReturnType.md
│ │ │ │ │ ├── algokit:utils:typescript:code:interfaces:types_transfer.TransferAssetParams.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:index.indexer.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:index.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:testing.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_account_manager_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_account_manager.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_account.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_algo_http_client_with_retry.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_algorand_client_asset_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_algorand_client_interface.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_algorand_client_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_algorand_client_transaction_creator.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_algorand_client_transaction_sender.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_algorand_client_transfer_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_algorand_client.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_amount_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_amount.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_app_arc56.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_app_client_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_app_client.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_app_deployer.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_app_factory_and_client_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_app_factory.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_app_manager.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_app_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_app.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_asset_manager.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_asset.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_async_event_emitter_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_async_event_emitter.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_client_manager_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_client_manager.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_composer.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_config.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_debugging.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_dispenser_client_spec.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_dispenser_client.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_expand.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_indexer.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_kmd_account_manager.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_lifecycle_events.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_logging.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_logic_error.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_network_client.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_testing.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_transaction.md
│ │ │ │ │ ├── algokit:utils:typescript:code:modules:types_transfer.md
│ │ │ │ │ ├── algokit:utils:typescript:code:README.md
│ │ │ │ │ ├── algokit:utils:typescript:README.md
│ │ │ │ │ ├── algokit:utils:typescript:v7-migration.md
│ │ │ │ │ ├── algokit:utils:typescript:v8-migration.md
│ │ │ │ │ ├── ARCs:ARC-template.md
│ │ │ │ │ ├── ARCs:assets:arc-0012:README.md
│ │ │ │ │ ├── ARCs:assets:arc-0034:TemplateForm.md
│ │ │ │ │ ├── ARCs:assets:arc-0062:README.md
│ │ │ │ │ ├── ARCs:pages:nfts.md
│ │ │ │ │ ├── ARCs:pages:wallets.md
│ │ │ │ │ ├── ARCs:README.md
│ │ │ │ │ ├── ARCs:specs:arc-0000.md
│ │ │ │ │ ├── ARCs:specs:arc-0001.md
│ │ │ │ │ ├── ARCs:specs:arc-0002.md
│ │ │ │ │ ├── ARCs:specs:arc-0003.md
│ │ │ │ │ ├── ARCs:specs:arc-0004.md
│ │ │ │ │ ├── ARCs:specs:arc-0005.md
│ │ │ │ │ ├── ARCs:specs:arc-0006.md
│ │ │ │ │ ├── ARCs:specs:arc-0007.md
│ │ │ │ │ ├── ARCs:specs:arc-0008.md
│ │ │ │ │ ├── ARCs:specs:arc-0009.md
│ │ │ │ │ ├── ARCs:specs:arc-0010.md
│ │ │ │ │ ├── ARCs:specs:arc-0011.md
│ │ │ │ │ ├── ARCs:specs:arc-0012.md
│ │ │ │ │ ├── ARCs:specs:arc-0015.md
│ │ │ │ │ ├── ARCs:specs:arc-0016.md
│ │ │ │ │ ├── ARCs:specs:arc-0018.md
│ │ │ │ │ ├── ARCs:specs:arc-0019.md
│ │ │ │ │ ├── ARCs:specs:arc-0020.md
│ │ │ │ │ ├── ARCs:specs:arc-0021.md
│ │ │ │ │ ├── ARCs:specs:arc-0022.md
│ │ │ │ │ ├── ARCs:specs:arc-0023.md
│ │ │ │ │ ├── ARCs:specs:arc-0025.md
│ │ │ │ │ ├── ARCs:specs:arc-0026.md
│ │ │ │ │ ├── ARCs:specs:arc-0028.md
│ │ │ │ │ ├── ARCs:specs:arc-0032.md
│ │ │ │ │ ├── ARCs:specs:arc-0033.md
│ │ │ │ │ ├── ARCs:specs:arc-0034.md
│ │ │ │ │ ├── ARCs:specs:arc-0035.md
│ │ │ │ │ ├── ARCs:specs:arc-0036.md
│ │ │ │ │ ├── ARCs:specs:arc-0042.md
│ │ │ │ │ ├── ARCs:specs:arc-0047.md
│ │ │ │ │ ├── ARCs:specs:arc-0048.md
│ │ │ │ │ ├── ARCs:specs:arc-0049.md
│ │ │ │ │ ├── ARCs:specs:arc-0054.md
│ │ │ │ │ ├── ARCs:specs:arc-0055.md
│ │ │ │ │ ├── ARCs:specs:arc-0056.md
│ │ │ │ │ ├── ARCs:specs:arc-0059.md
│ │ │ │ │ ├── ARCs:specs:arc-0062.md
│ │ │ │ │ ├── ARCs:specs:arc-0065.md
│ │ │ │ │ ├── ARCs:specs:arc-0069.md
│ │ │ │ │ ├── ARCs:specs:arc-0072.md
│ │ │ │ │ ├── ARCs:specs:arc-0073.md
│ │ │ │ │ ├── ARCs:specs:arc-0074.md
│ │ │ │ │ ├── ARCs:specs:arc-0076.md
│ │ │ │ │ ├── ARCs:specs:arc-0078.md
│ │ │ │ │ ├── ARCs:specs:arc-0079.md
│ │ │ │ │ ├── ARCs:specs:arc-0200.md
│ │ │ │ │ ├── clis_index.md
│ │ │ │ │ ├── developer:docs:about.md
│ │ │ │ │ ├── developer:docs:clis:algokey:algokey.md
│ │ │ │ │ ├── developer:docs:clis:algokey:generate.md
│ │ │ │ │ ├── developer:docs:clis:algokey:import.md
│ │ │ │ │ ├── developer:docs:clis:algokey:multisig:append-auth-addr.md
│ │ │ │ │ ├── developer:docs:clis:algokey:multisig:multisig.md
│ │ │ │ │ ├── developer:docs:clis:algokey:part:info.md
│ │ │ │ │ ├── developer:docs:clis:algokey:part:part.md
│ │ │ │ │ ├── developer:docs:clis:algokey:part:reparent.md
│ │ │ │ │ ├── developer:docs:clis:algokey:sign.md
│ │ │ │ │ ├── developer:docs:clis:conduit:conduit.md
│ │ │ │ │ ├── developer:docs:clis:conduit:init.md
│ │ │ │ │ ├── developer:docs:clis:conduit:list:exporters.md
│ │ │ │ │ ├── developer:docs:clis:conduit:list:importers.md
│ │ │ │ │ ├── developer:docs:clis:conduit:list:list.md
│ │ │ │ │ ├── developer:docs:clis:conduit:list:processors.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:diagcfg.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:metric:disable.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:metric:enable.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:metric:metric.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:metric:status.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:telemetry:disable.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:telemetry:enable.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:telemetry:endpoint.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:telemetry:name.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:telemetry:status.md
│ │ │ │ │ ├── developer:docs:clis:diagcfg:telemetry:telemetry.md
│ │ │ │ │ ├── developer:docs:clis:goal:node:restart.md
│ │ │ │ │ ├── developer:docs:clis:goal:node:start.md
│ │ │ │ │ ├── developer:docs:clis:goal:node:status.md
│ │ │ │ │ ├── developer:docs:clis:goal:node:stop.md
│ │ │ │ │ ├── developer:docs:clis:goal:node:wait.md
│ │ │ │ │ ├── developer:docs:clis:goal:protocols.md
│ │ │ │ │ ├── developer:docs:clis:goal:report.md
│ │ │ │ │ ├── developer:docs:clis:goal:version.md
│ │ │ │ │ ├── developer:docs:clis:goal:wallet:list.md
│ │ │ │ │ ├── developer:docs:clis:goal:wallet:new.md
│ │ │ │ │ ├── developer:docs:clis:goal:wallet:wallet.md
│ │ │ │ │ ├── developer:docs:clis:indexer:api-config.md
│ │ │ │ │ ├── developer:docs:clis:indexer:daemon.md
│ │ │ │ │ ├── developer:docs:clis:indexer:indexer.md
│ │ │ │ │ ├── developer:docs:clis:indexer:util:util.md
│ │ │ │ │ ├── developer:docs:clis:indexer:util:validator.md
│ │ │ │ │ ├── developer:docs:clis:kmd.md
│ │ │ │ │ ├── developer:docs:clis:tealdbg:debug.md
│ │ │ │ │ ├── developer:docs:clis:tealdbg:remote.md
│ │ │ │ │ ├── developer:docs:clis:tealdbg:tealdbg.md
│ │ │ │ │ ├── developer:docs:details:accounts:create.md
│ │ │ │ │ ├── developer:docs:details:accounts:index.md
│ │ │ │ │ ├── developer:docs:details:accounts:rekey.md
│ │ │ │ │ ├── developer:docs:details:algorand_consensus.md
│ │ │ │ │ ├── developer:docs:details:algorand-networks:betanet.md
│ │ │ │ │ ├── developer:docs:details:algorand-networks:index.md
│ │ │ │ │ ├── developer:docs:details:algorand-networks:mainnet.md
│ │ │ │ │ ├── developer:docs:details:algorand-networks:testnet.md
│ │ │ │ │ ├── developer:docs:details:asa.md
│ │ │ │ │ ├── developer:docs:details:atc.md
│ │ │ │ │ ├── developer:docs:details:atomic_transfers.md
│ │ │ │ │ ├── developer:docs:details:conduit.md
│ │ │ │ │ ├── developer:docs:details:crust.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:index.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:guidelines.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:index.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:jsonspec.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:index.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:v1.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:v10.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:v2.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:v3.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:v4.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:v5.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:v6.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:v7.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:v8.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:opcodes:v9.md
│ │ │ │ │ ├── developer:docs:details:dapps:avm:teal:specification.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:ABI:index.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:apps:create.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:apps:index.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:apps:innertx.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:apps:state.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:apps:txs.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:debugging.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:frontend:apps.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:frontend:smartsigs.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:guidelines.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:index.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:smartsigs:index.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:smartsigs:modes.md
│ │ │ │ │ ├── developer:docs:details:dapps:smart-contracts:smartsigs:walkthrough.md
│ │ │ │ │ ├── developer:docs:details:dapps:writing-contracts:beaker.md
│ │ │ │ │ ├── developer:docs:details:dapps:writing-contracts:pyteal.md
│ │ │ │ │ ├── developer:docs:details:dapps:writing-contracts:python.md
│ │ │ │ │ ├── developer:docs:details:encoding.md
│ │ │ │ │ ├── developer:docs:details:ethereum_to_algorand.md
│ │ │ │ │ ├── developer:docs:details:index.md
│ │ │ │ │ ├── developer:docs:details:indexer.md
│ │ │ │ │ ├── developer:docs:details:parameter_tables.md
│ │ │ │ │ ├── developer:docs:details:stateproofs:index.md
│ │ │ │ │ ├── developer:docs:details:stateproofs:light_client.md
│ │ │ │ │ ├── developer:docs:details:technical_faq.md
│ │ │ │ │ ├── developer:docs:details:transactions:index.md
│ │ │ │ │ ├── developer:docs:details:transactions:offline_transactions.md
│ │ │ │ │ ├── developer:docs:details:transactions:payment_prompts.md
│ │ │ │ │ ├── developer:docs:details:transactions:signatures.md
│ │ │ │ │ ├── developer:docs:details:transactions:transactions.md
│ │ │ │ │ ├── developer:docs:details:useful_resources.md
│ │ │ │ │ ├── developer:docs:get-started:algokit.md
│ │ │ │ │ ├── developer:docs:get-started:basics:what_is_blockchain.md
│ │ │ │ │ ├── developer:docs:get-started:basics:whats_a_dapp.md
│ │ │ │ │ ├── developer:docs:get-started:basics:where_to_start.md
│ │ │ │ │ ├── developer:docs:get-started:basics:why_algorand.md
│ │ │ │ │ ├── developer:docs:get-started:tokenization:ft.md
│ │ │ │ │ ├── developer:docs:get-started:tokenization:nft.md
│ │ │ │ │ ├── developer:docs:index.md
│ │ │ │ │ ├── developer:docs:rest-apis:algod.md
│ │ │ │ │ ├── developer:docs:rest-apis:indexer.md
│ │ │ │ │ ├── developer:docs:rest-apis:kmd.md
│ │ │ │ │ ├── developer:docs:rest-apis:restendpoints.md
│ │ │ │ │ ├── developer:docs:run-a-node:operations:catchup.md
│ │ │ │ │ ├── developer:docs:run-a-node:operations:switch_networks.md
│ │ │ │ │ ├── developer:docs:run-a-node:participate:generate_keys.md
│ │ │ │ │ ├── developer:docs:run-a-node:participate:index.md
│ │ │ │ │ ├── developer:docs:run-a-node:participate:offline.md
│ │ │ │ │ ├── developer:docs:run-a-node:participate:online.md
│ │ │ │ │ ├── developer:docs:run-a-node:participate:renew.md
│ │ │ │ │ ├── developer:docs:run-a-node:reference:artifacts.md
│ │ │ │ │ ├── developer:docs:run-a-node:reference:config.md
│ │ │ │ │ ├── developer:docs:run-a-node:reference:relay.md
│ │ │ │ │ ├── developer:docs:run-a-node:reference:telemetry-config.md
│ │ │ │ │ ├── developer:docs:run-a-node:setup:indexer.md
│ │ │ │ │ ├── developer:docs:run-a-node:setup:install.md
│ │ │ │ │ ├── developer:docs:run-a-node:setup:node-troubleshooting.md
│ │ │ │ │ ├── developer:docs:run-a-node:setup:types.md
│ │ │ │ │ ├── developer:docs:sdks:go:index.md
│ │ │ │ │ ├── developer:docs:sdks:index.md
│ │ │ │ │ ├── developer:docs:sdks:java:index.md
│ │ │ │ │ ├── developer:docs:sdks:javascript:index.md
│ │ │ │ │ ├── developer:docs:sdks:python:index.md
│ │ │ │ │ ├── developer:python:code:example:accounts.md
│ │ │ │ │ ├── developer:python:code:example:arc4_types.md
│ │ │ │ │ ├── developer:python:code:example:assets.md
│ │ │ │ │ ├── developer:python:code:example:box_storage.md
│ │ │ │ │ ├── developer:python:code:example:control_flow.md
│ │ │ │ │ ├── developer:python:code:example:crypto:merkle_tree.md
│ │ │ │ │ ├── developer:python:code:example:defi:amm.md
│ │ │ │ │ ├── developer:python:code:example:defi:auction.md
│ │ │ │ │ ├── developer:python:code:example:defi:htlc_logicsig.md
│ │ │ │ │ ├── developer:python:code:example:defi:marketplace.md
│ │ │ │ │ ├── developer:python:code:example:events:arc28_events.md
│ │ │ │ │ ├── developer:python:code:example:global_storage.md
│ │ │ │ │ ├── developer:python:code:example:governance:simple_voting.md
│ │ │ │ │ ├── developer:python:code:example:hello_world.md
│ │ │ │ │ ├── developer:python:code:example:inner_transactions.md
│ │ │ │ │ ├── developer:python:code:example:local_storage.md
│ │ │ │ │ ├── developer:python:code:example:nft:proof_of_attendance.md
│ │ │ │ │ ├── developer:python:code:example:privacy:zk_whitelist.md
│ │ │ │ │ ├── developer:python:code:example:scratch_storage.md
│ │ │ │ │ ├── developer:python:code:example:self_payment.md
│ │ │ │ │ ├── developer:python:code:example:struct_in_box.md
│ │ │ │ │ ├── developer:python:code:example:subsidize_app_call.md
│ │ │ │ │ ├── developer:python:code:example:transactions.md
│ │ │ │ │ ├── developer:python:code:example:utility:calculator.md
│ │ │ │ │ ├── devportal-code-examples:projects:python-contract-examples:README.md
│ │ │ │ │ ├── devportal-code-examples:README.md
│ │ │ │ │ ├── docs:.walletconnect:index.md
│ │ │ │ │ ├── docs:.walletconnect:walletconnect-schema.md
│ │ │ │ │ ├── docs:README.md
│ │ │ │ │ ├── docs:scripts:example_tracker:example_list.md
│ │ │ │ │ ├── docs:scripts:README.md
│ │ │ │ │ ├── index.md
│ │ │ │ │ ├── liquid_auth_index.md
│ │ │ │ │ ├── liquid-auth:ARCHITECTURE.md
│ │ │ │ │ ├── liquid-auth:decisions:1-Service-Authentication.md
│ │ │ │ │ ├── liquid-auth:decisions:2-Bidirectional-Communication.md
│ │ │ │ │ ├── liquid-auth:decisions:3-Peer-to-Peer-Signaling.md
│ │ │ │ │ ├── liquid-auth:decisions:4-Fido-Extension.md
│ │ │ │ │ ├── liquid-auth:decisions:README.md
│ │ │ │ │ ├── liquid-auth:docs:architecture.md
│ │ │ │ │ ├── liquid-auth:docs:clients:android:provider-service:authenticate.md
│ │ │ │ │ ├── liquid-auth:docs:clients:android:provider-service:register.md
│ │ │ │ │ ├── liquid-auth:docs:clients:browser:authentication.md
│ │ │ │ │ ├── liquid-auth:docs:clients:browser:example.md
│ │ │ │ │ ├── liquid-auth:docs:introduction.md
│ │ │ │ │ ├── liquid-auth:docs:README.md
│ │ │ │ │ ├── liquid-auth:docs:server:environment-variables.md
│ │ │ │ │ ├── liquid-auth:docs:server:integrations.md
│ │ │ │ │ ├── liquid-auth:docs:server:introduction.md
│ │ │ │ │ ├── liquid-auth:docs:server:running-locally.md
│ │ │ │ │ ├── liquid-auth:README.md
│ │ │ │ │ ├── liquid-auth:SEQUENCE.md
│ │ │ │ │ ├── liquid-auth:services:liquid-auth-api-js:src:assertion:assertion.controller.post.request.md
│ │ │ │ │ ├── liquid-auth:services:liquid-auth-api-js:src:assertion:assertion.controller.post.response.md
│ │ │ │ │ ├── liquid-auth:services:liquid-auth-api-js:src:attestation:attestation.controller.post.request.md
│ │ │ │ │ ├── liquid-auth:services:liquid-auth-api-js:src:auth:auth.controller.get.user.md
│ │ │ │ │ ├── liquid-auth:sites:express-dapp:README.md
│ │ │ │ │ ├── liquid-auth:VISION.md
│ │ │ │ │ ├── puya_index.md
│ │ │ │ │ ├── puya:docs:algopy_testing:index.md
│ │ │ │ │ ├── puya:docs:api-algopy.arc4.md
│ │ │ │ │ ├── puya:docs:api-algopy.gtxn.md
│ │ │ │ │ ├── puya:docs:api-algopy.itxn.md
│ │ │ │ │ ├── puya:docs:api-algopy.md
│ │ │ │ │ ├── puya:docs:api-algopy.op.md
│ │ │ │ │ ├── puya:docs:api.md
│ │ │ │ │ ├── puya:docs:compiler.md
│ │ │ │ │ ├── puya:docs:index.md
│ │ │ │ │ ├── puya:docs:language-guide.md
│ │ │ │ │ ├── puya:docs:lg-arc28.md
│ │ │ │ │ ├── puya:docs:lg-arc4.md
│ │ │ │ │ ├── puya:docs:lg-builtins.md
│ │ │ │ │ ├── puya:docs:lg-calling-apps.md
│ │ │ │ │ ├── puya:docs:lg-compile.md
│ │ │ │ │ ├── puya:docs:lg-control.md
│ │ │ │ │ ├── puya:docs:lg-errors.md
│ │ │ │ │ ├── puya:docs:lg-logs.md
│ │ │ │ │ ├── puya:docs:lg-modules.md
│ │ │ │ │ ├── puya:docs:lg-opcode-budget.md
│ │ │ │ │ ├── puya:docs:lg-ops.md
│ │ │ │ │ ├── puya:docs:lg-storage.md
│ │ │ │ │ ├── puya:docs:lg-structure.md
│ │ │ │ │ ├── puya:docs:lg-transactions.md
│ │ │ │ │ ├── puya:docs:lg-types.md
│ │ │ │ │ ├── puya:docs:lg-unsupported-python-features.md
│ │ │ │ │ ├── puya:docs:principles.md
│ │ │ │ │ ├── puya:examples:auction:README.md
│ │ │ │ │ ├── puya:python:testing:docs:algopy.md
│ │ │ │ │ ├── puya:python:testing:docs:api.md
│ │ │ │ │ ├── puya:python:testing:docs:coverage.md
│ │ │ │ │ ├── puya:python:testing:docs:examples.md
│ │ │ │ │ ├── puya:python:testing:docs:faq.md
│ │ │ │ │ ├── puya:python:testing:docs:index.md
│ │ │ │ │ ├── puya:python:testing:docs:testing-guide:arc4-types.md
│ │ │ │ │ ├── puya:python:testing:docs:testing-guide:avm-types.md
│ │ │ │ │ ├── puya:python:testing:docs:testing-guide:concepts.md
│ │ │ │ │ ├── puya:python:testing:docs:testing-guide:contract-testing.md
│ │ │ │ │ ├── puya:python:testing:docs:testing-guide:index.md
│ │ │ │ │ ├── puya:python:testing:docs:testing-guide:opcodes.md
│ │ │ │ │ ├── puya:python:testing:docs:testing-guide:signature-testing.md
│ │ │ │ │ ├── puya:python:testing:docs:testing-guide:state-management.md
│ │ │ │ │ ├── puya:python:testing:docs:testing-guide:subroutines.md
│ │ │ │ │ ├── puya:python:testing:docs:testing-guide:transactions.md
│ │ │ │ │ ├── puya:python:testing:examples:README.md
│ │ │ │ │ ├── puya:python:testing:README.md
│ │ │ │ │ ├── puya:README.md
│ │ │ │ │ ├── puya:src:puya:ARCHITECTURE.md
│ │ │ │ │ ├── puya:src:puyapy:_typeshed:README.md
│ │ │ │ │ ├── puya:src:puyapy:_vendor:mypy:typeshed:stdlib:_typeshed:README.md
│ │ │ │ │ ├── puya:src:puyapy:awst_build:README.md
│ │ │ │ │ ├── puya:stubs:README.md
│ │ │ │ │ ├── puya:tests:test_expected_output:README.md
│ │ │ │ │ ├── puya:typescript:docs:architecture-decisions:2024-05-21_primitive-bytes-and-strings.md
│ │ │ │ │ ├── puya:typescript:docs:architecture-decisions:2024-05-21_primitive-integer-types.md
│ │ │ │ │ ├── puya:typescript:docs:README.md
│ │ │ │ │ ├── puya:typescript:packages:algo-ts:readme.md
│ │ │ │ │ ├── puya:typescript:README.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIAddressType.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIArrayDynamicType.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIArrayStaticType.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIBoolType.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIByteType.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIContract.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIInterface.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIMethod.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIStringType.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABITupleType.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIType.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIUfixedType.md
│ │ │ │ │ ├── SDKs:javascript:classes:ABIUintType.md
│ │ │ │ │ ├── SDKs:javascript:classes:Algodv2.md
│ │ │ │ │ ├── SDKs:javascript:classes:AtomicTransactionComposer.md
│ │ │ │ │ ├── SDKs:javascript:classes:DryrunResult.md
│ │ │ │ │ ├── SDKs:javascript:classes:Indexer.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.Account.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.AccountParticipation.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.AccountResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.AccountsResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.AccountStateDelta.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.Application.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.ApplicationLocalState.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.ApplicationLocalStatesResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.ApplicationLogData.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.ApplicationLogsResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.ApplicationParams.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.ApplicationResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.ApplicationsResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.ApplicationStateSchema.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.Asset.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.AssetBalancesResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.AssetHolding.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.AssetHoldingsResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.AssetParams.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.AssetResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.AssetsResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.Block.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.BlockRewards.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.BlockUpgradeState.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.BlockUpgradeVote.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.Box.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.BoxDescriptor.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.BoxesResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.ErrorResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.EvalDelta.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.EvalDeltaKeyValue.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.HashFactory.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.HealthCheck.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.IndexerStateProofMessage.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.MerkleArrayProof.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.MiniAssetHolding.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.ParticipationUpdates.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.StateProofFields.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.StateProofParticipant.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.StateProofReveal.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.StateProofSignature.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.StateProofSigSlot.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.StateProofTracking.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.StateProofVerifier.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.StateSchema.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TealKeyValue.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TealValue.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.Transaction.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionApplication.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionAssetConfig.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionAssetFreeze.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionAssetTransfer.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionKeyreg.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionPayment.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionSignature.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionSignatureLogicsig.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionSignatureMultisig.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionSignatureMultisigSubsignature.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionsResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:indexerModels.TransactionStateProof.md
│ │ │ │ │ ├── SDKs:javascript:classes:Kmd.md
│ │ │ │ │ ├── SDKs:javascript:classes:LogicSig.md
│ │ │ │ │ ├── SDKs:javascript:classes:LogicSigAccount.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.Account.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AccountApplicationResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AccountAssetHolding.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AccountAssetResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AccountAssetsInformationResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AccountParticipation.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AccountStateDelta.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AppCallLogs.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.Application.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.ApplicationInitialStates.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.ApplicationKVStorage.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.ApplicationLocalReference.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.ApplicationLocalState.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.ApplicationParams.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.ApplicationStateOperation.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.ApplicationStateSchema.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.Asset.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AssetHolding.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AssetHoldingReference.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AssetParams.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AvmKeyValue.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.AvmValue.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.BlockHashResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.BlockLogsResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.BlockResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.BlockTxidsResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.Box.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.BoxDescriptor.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.BoxesResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.BoxReference.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.BuildVersion.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.CompileResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.DisassembleResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.DryrunRequest.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.DryrunResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.DryrunSource.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.DryrunState.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.DryrunTxnResult.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.ErrorResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.EvalDelta.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.EvalDeltaKeyValue.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.GetBlockTimeStampOffsetResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.GetSyncRoundResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.KvDelta.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.LedgerStateDeltaForTransactionGroup.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.LightBlockHeaderProof.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.NodeStatusResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.PendingTransactionResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.PendingTransactionsResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.PostTransactionsResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.ScratchChange.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulateInitialStates.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulateRequest.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulateRequestTransactionGroup.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulateResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulateTraceConfig.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulateTransactionGroupResult.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulateTransactionResult.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulateUnnamedResourcesAccessed.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulationEvalOverrides.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulationOpcodeTraceUnit.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SimulationTransactionExecTrace.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.StateProof.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.StateProofMessage.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.SupplyResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.TealKeyValue.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.TealValue.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.TransactionGroupLedgerStateDeltasForRoundResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.TransactionParametersResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.TransactionProofResponse.md
│ │ │ │ │ ├── SDKs:javascript:classes:modelsv2.Version.md
│ │ │ │ │ ├── SDKs:javascript:classes:SourceMap.md
│ │ │ │ │ ├── SDKs:javascript:classes:Transaction.md
│ │ │ │ │ ├── SDKs:javascript:enums:ABIReferenceType.md
│ │ │ │ │ ├── SDKs:javascript:enums:ABITransactionType.md
│ │ │ │ │ ├── SDKs:javascript:enums:AtomicTransactionComposerStatus.md
│ │ │ │ │ ├── SDKs:javascript:enums:IntDecoding.md
│ │ │ │ │ ├── SDKs:javascript:enums:OnApplicationComplete.md
│ │ │ │ │ ├── SDKs:javascript:enums:TransactionType.md
│ │ │ │ │ ├── SDKs:javascript:examples:README.md
│ │ │ │ │ ├── SDKs:javascript:FAQ.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:ABIContractNetworkInfo.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:ABIContractNetworks.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:ABIContractParams.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:ABIInterfaceParams.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:ABIMethodArgParams.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:ABIMethodParams.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:ABIMethodReturnParams.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:ABIResult.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:Account.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:Address.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:AlgodTokenHeader.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:BaseHTTPClient.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:BaseHTTPClientError.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:BaseHTTPClientResponse.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:BoxReference.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:CustomTokenHeader.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:EncodedAssetParams.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:EncodedBoxReference.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:EncodedGlobalStateSchema.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:EncodedLocalStateSchema.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:EncodedLogicSig.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:EncodedLogicSigAccount.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:EncodedMultisig.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:EncodedSignedTransaction.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:EncodedSubsig.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:EncodedTransaction.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:IndexerTokenHeader.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:KMDTokenHeader.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:MultisigMetadata.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:SignedTransaction.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:SuggestedParams.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:TransactionParams.md
│ │ │ │ │ ├── SDKs:javascript:interfaces:TransactionWithSigner.md
│ │ │ │ │ ├── SDKs:javascript:modules:indexerModels.md
│ │ │ │ │ ├── SDKs:javascript:modules:modelsv2.md
│ │ │ │ │ ├── SDKs:javascript:modules.md
│ │ │ │ │ ├── SDKs:javascript:README.md
│ │ │ │ │ ├── SDKs:python:algosdk:v2client:harness:README.md
│ │ │ │ │ ├── SDKs:python:examples:README.md
│ │ │ │ │ ├── SDKs:python:README.md
│ │ │ │ │ ├── tealscript:examples_amm_README.md
│ │ │ │ │ ├── tealscript:examples_auction_README.md
│ │ │ │ │ ├── tealscript:examples_big_box_README.md
│ │ │ │ │ ├── tealscript:examples_itxns_README.md
│ │ │ │ │ ├── tealscript:examples_lsig_with_app_README.md
│ │ │ │ │ ├── tealscript:examples_reti_README.md
│ │ │ │ │ ├── tealscript:FEATURES.md
│ │ │ │ │ ├── tealscript:guides_atomic_txn.md
│ │ │ │ │ ├── tealscript:guides_features.md
│ │ │ │ │ ├── tealscript:guides_getting_started.md
│ │ │ │ │ ├── tealscript:guides_inner_transactions.md
│ │ │ │ │ ├── tealscript:guides_lifecycle.md
│ │ │ │ │ ├── tealscript:guides_math.md
│ │ │ │ │ ├── tealscript:guides_methods.md
│ │ │ │ │ ├── tealscript:guides_multiple_contracts.md
│ │ │ │ │ ├── tealscript:guides_pyteal.md
│ │ │ │ │ ├── tealscript:guides_storage.md
│ │ │ │ │ ├── tealscript:guides_Supported Types_arrays.md
│ │ │ │ │ ├── tealscript:guides_Supported Types_numbers.md
│ │ │ │ │ ├── TEALScript:README.md
│ │ │ │ │ ├── tealscript:tests_test_package_README.md
│ │ │ │ │ ├── tealscript:tutorials_Hello World_0001-intro.md
│ │ │ │ │ ├── tealscript:tutorials_Hello World_0002-init.md
│ │ │ │ │ ├── tealscript:tutorials_Hello World_0003-contract.md
│ │ │ │ │ ├── tealscript:tutorials_Hello World_0004-artifacts.md
│ │ │ │ │ ├── tealscript:tutorials_Hello World_0005-hello.md
│ │ │ │ │ └── tealscript:tutorials_Hello World_0006-test.md
│ │ │ │ └── taxonomy-categories
│ │ │ │ ├── algokit-utils.json
│ │ │ │ ├── algokit.json
│ │ │ │ ├── arcs.json
│ │ │ │ ├── clis.json
│ │ │ │ ├── details.json
│ │ │ │ ├── developers.json
│ │ │ │ ├── liquid-auth.json
│ │ │ │ ├── nodes.json
│ │ │ │ ├── puya.json
│ │ │ │ ├── python.json
│ │ │ │ ├── sdks.json
│ │ │ │ └── tealscript.json
│ │ │ └── wallet
│ │ │ └── index.ts
│ │ ├── tools
│ │ │ ├── accountManager.ts
│ │ │ ├── algodManager.ts
│ │ │ ├── apiManager
│ │ │ │ ├── algod
│ │ │ │ │ ├── account.ts
│ │ │ │ │ ├── application.ts
│ │ │ │ │ ├── asset.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── transaction.ts
│ │ │ │ ├── example
│ │ │ │ │ ├── get-balance.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── indexer
│ │ │ │ │ ├── account.ts
│ │ │ │ │ ├── application.ts
│ │ │ │ │ ├── asset.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ └── transaction.ts
│ │ │ │ ├── nfd
│ │ │ │ │ └── index.ts
│ │ │ │ ├── tinyman
│ │ │ │ │ ├── analytics.ts
│ │ │ │ │ ├── bootstrap.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── liquidity.ts
│ │ │ │ │ ├── opt_in.ts
│ │ │ │ │ ├── pool.ts
│ │ │ │ │ ├── remove_liquidity.ts
│ │ │ │ │ └── swap.ts
│ │ │ │ ├── ultrade
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── market.ts
│ │ │ │ │ ├── system.ts
│ │ │ │ │ └── wallet.ts
│ │ │ │ └── vestige
│ │ │ │ ├── assets.ts
│ │ │ │ ├── balances.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── networks.ts
│ │ │ │ ├── notes.ts
│ │ │ │ ├── pools.ts
│ │ │ │ ├── protocols.ts
│ │ │ │ ├── swaps.ts
│ │ │ │ └── vaults.ts
│ │ │ ├── arc26Manager.ts
│ │ │ ├── index.ts
│ │ │ ├── knowledgeManager.ts
│ │ │ ├── transactionManager
│ │ │ │ ├── accountTransactions.ts
│ │ │ │ ├── appTransactions
│ │ │ │ │ ├── callTxn.ts
│ │ │ │ │ ├── clearTxn.ts
│ │ │ │ │ ├── closeOutTxn.ts
│ │ │ │ │ ├── createTxn.ts
│ │ │ │ │ ├── deleteTxn.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── optInTxn.ts
│ │ │ │ │ ├── test
│ │ │ │ │ │ ├── counter_approval.teal
│ │ │ │ │ │ ├── counter_clear.teal
│ │ │ │ │ │ ├── storage_test_approval_v2.teal
│ │ │ │ │ │ ├── storage_test_approval.teal
│ │ │ │ │ │ └── storage_test_clear.teal
│ │ │ │ │ ├── types.ts
│ │ │ │ │ └── updateTxn.ts
│ │ │ │ ├── assetTransactions.ts
│ │ │ │ ├── generalTransaction.ts
│ │ │ │ └── index.ts
│ │ │ └── utilityManager.ts
│ │ ├── types.ts
│ │ └── utils
│ │ └── responseProcessor.ts
│ ├── tests
│ │ ├── resources
│ │ │ ├── algod
│ │ │ │ ├── account.test.ts
│ │ │ │ ├── application.test.ts
│ │ │ │ ├── asset.test.ts
│ │ │ │ └── transaction.test.ts
│ │ │ └── indexer
│ │ │ ├── account.test.ts
│ │ │ ├── application.test.ts
│ │ │ ├── asset.test.ts
│ │ │ └── transaction.test.ts
│ │ └── tools
│ │ ├── accountManager.test.ts
│ │ ├── algodManager.test.ts
│ │ ├── apiManager
│ │ │ └── example
│ │ │ └── get-balance.test.ts
│ │ ├── transactionManager
│ │ │ ├── accountTransactionManager.test.ts
│ │ │ ├── appTransactionManager.test.ts
│ │ │ ├── assetTransactionManager.test.ts
│ │ │ ├── generalTransactionManager.test.ts
│ │ │ └── transactionManager.test.ts
│ │ └── utilityManager.test.ts
│ └── tsconfig.json
├── README.md
├── rename_files.sh
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/packages/server/src/resources/knowledge/taxonomy/puya:typescript:docs:architecture-decisions:2024-05-21_primitive-integer-types.md:
--------------------------------------------------------------------------------
```markdown
1 | # Architecture Decision Record - Primitive integer types
2 |
3 | - **Status**: Draft
4 | - **Owner:** Tristan Menzel
5 | - **Deciders**: Alessandro Cappellato (Algorand Foundation), Joe Polny (Algorand Foundation), Rob Moore (MakerX)
6 | - **Date created**: 2024-05-21
7 | - **Date decided**: N/A
8 | - **Date updated**: 2024-05-31
9 |
10 | ## Context
11 |
12 | The AVM supports two integer types in its standard set of ops.
13 |
14 | * **uint64**: An unsigned 64-bit integer where the AVM will error on over or under flows
15 | * **biguint**: An unsigned variable bit, big-endian integer represented as an array of bytes with an indeterminate number of leading zeros which are truncated by several math ops. The max size of a biguint is 512-bits. Over and under flows will cause errors.
16 |
17 | EcmaScript supports two numeric types.
18 |
19 | * **number**: A floating point signed value with 64 bits of precision capable of a max safe integer value of 2^53 - 1. A number can be declared with a numeric literal, or with the `Number(...)` factory method.
20 | * **bigint**: A signed arbitrary-precision integer with an implementation defined limit based on the platform. In practice this is greater than 512-bit. A bigint can be declared with a numeric literal and `n` suffix, or with the `BigInt(...)` factory method.
21 |
22 | EcmaScript and TypeScript both do not support operator overloading, despite some [previous](https://github.com/tc39/notes/blob/main/meetings/2023-11/november-28.md#withdrawing-operator-overloading) [attempts](https://github.com/microsoft/TypeScript/issues/2319) to do so.
23 |
24 | TealScript [makes use of branded `number` types](https://tealscript.netlify.app/guides/supported-types/numbers/) for all bit sizes from 8 => 512, although it doesn't allow `number` variables, you must specify the actual type you want (e.g. `uint64`). Since the source code is never executed, the safe limits of the `number` type are not a concern. Compiled code does not perform overflow checks on calculations until a return value is being encoded meaning a uint<8> is effectively a uint<64> until it's returned.
25 |
26 | Algorand Python has specific [UInt64 and BigUint types](https://algorandfoundation.github.io/puya/lg-types.html#avm-types) that have semantics that exactly match the AVM semantics. Python allows for operator overloading so these types also use native operators (where they align to functionality in the underlying AVM).
27 |
28 |
29 | ## Requirements
30 |
31 | - Support uint64 and biguint AVM types
32 | - Use idiomatic TypeScript expressions for numeric expressions, including mathematical operators (`+`, `-`, `*`, `/`, etc.)
33 | - Semantic compatibility between AVM execution and TypeScript execution (e.g. in unit tests)
34 |
35 | ## Principles
36 |
37 | - **[AlgoKit Guiding Principles](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md#guiding-principles)** - specifically Seamless onramp, Leverage existing ecosystem, Meet devs where they are
38 | - **[Algorand Python Principles](https://algorandfoundation.github.io/puya/principles.html#principles)**
39 | - **[Algorand TypeScript Guiding Principles](../README.md#guiding-principals)**
40 |
41 | ## Options
42 |
43 | ### Option 1 - Native types
44 |
45 | EcmaScript's `number` type is ill-suited to representing either AVM type reliably as it does not have the safe range to cover the full range of a uint64. Being a floating point number, it would also require truncating after division.
46 |
47 | EcmaScript's `bigint` is a better fit for both types but does not underflow when presented with a negative number, nor does it overflow at any meaningful limit for the AVM types.
48 |
49 | If we solved the over/under flow checking with a custom TypeScript transformer we still face an issue that `uint64` and `biguint` would not have discrete types for the compiler to know the difference between them and also we would have no type safety against accidentally passing a `biguint` to a method that expects a `uint64` and vice versa.
50 |
51 | ### Option 2 - Wrapper classes
52 |
53 | A `UInt64` and `BigUint` class could be defined which make use of `bigint` internally to perform maths operations and check for over or under flows after each op.
54 |
55 | ```ts
56 | class UInt64 {
57 |
58 | private value: bigint
59 |
60 | constructor(value: bigint | number) {
61 | this.value = this.checkBounds(value)
62 | }
63 |
64 | add(other: UInt64): UInt64 {
65 | return new UInt64(this.value + other.value)
66 | }
67 |
68 | /* etc */
69 | }
70 |
71 | ```
72 |
73 | This solution provides the ultimate in type safety and semantic/syntactic compatibility, and requires no custom TypeScript transformer to run _correctly_ on Node.js. The semantics should be obvious to anyone familiar with Object Oriented Programming. The downside is that neither EcmaScript nor TypeScript support operator overloading which results in more verbose and unwieldy math expressions. The lack of idiomatic TypeScript mathematical operators is a deal breaker that rules this option out.
74 |
75 | ```ts
76 | const a = UInt64(500n)
77 | const b = Uint64(256)
78 |
79 | // Not supported (a compile error in TS)
80 | const c1 = a + b
81 | // Works, but is verbose and unwieldy for more complicated expressions and isn't idiomatic TypeScript
82 | const c2 = a.add(b)
83 |
84 | ```
85 |
86 | ### Option 3 - Branded `bigint`
87 |
88 | TypeScript allows you to intersect primitive types with a simple interface to brand a value in a way which is incompatible with another primitive branded with a different value within the type system. In this option the base type that is branded is `bigint`, which aligns to th discussion in Option 1 about the logical type to represent `uint64` and `biguint`.
89 |
90 | ```ts
91 | // Constructors
92 | declare function UInt64(v): uint64
93 | declare function BigUint(v): uint64
94 |
95 | // Branded types
96 | type uint64 = bigint & { __type?: 'uint64' }
97 | type biguint = bigint & { __type?: 'biguint' }
98 |
99 |
100 | const a: uint64 = 323n // Declare with type annotation and raw `bigint` literal
101 | const b = UInt64(12n) // Declare with factory
102 | const b2 = UInt64(12) // Factory could also take `number` literals (compiler could check they aren't negative and are integers)
103 |
104 | // c1 type is `bigint`, but we can mandate a type hint with the compiler (c2)
105 | const c1 = a + b
106 | const c2: uint64 = a + b
107 |
108 | // No TypeScript type error, but semantically ambiguous - is a+b performed as a biguint op or a uint64 one and then converted?
109 | // (We could detect this as a compiler error though)
110 | const c3: biguint = a + b
111 |
112 | // Type error on b: Argument of type 'uint64' is not assignable to parameter of type 'biguint'. Nice!
113 | test(a, b)
114 | function test(x: uint64, y: biguint) {
115 | // ...
116 | }
117 |
118 | ```
119 |
120 | This solution looks like normal TypeScript and results in math expressions that are much easier to read. The factory methods (e.g. `UInt64(4n)`) mimics native equivalents and should be familiar to existing developers.
121 |
122 | The drawbacks of this solution are:
123 | - Less implicit type safety for branded types as TypeScript will infer the type of any binary math expression to be the base numeric type (a type annotation will be required where ever an identifier is declared, and the compiler will need to enforce this)
124 | - In order to have TypeScript execution semantics of a `uint64` or `biguint` match the AVM, a custom TypeScript transformer will be required to wrap numeric operations in logic that checks for over and under flows line-by-line; this is straightforward to write though and has been successfully spiked out
125 | - Additional type checking will be required by the compiler to catch instances of assigning one numeric type to the other (accidental implicit assignment) e.g. assigning a `uint64` value to `biguint`.
126 | - Literals will require an `n` suffix
127 | - `bigint` cannot be used to index an object/array (only `number | string | symbol`)
128 |
129 |
130 | ### Option 4 Explicitly tagged brand types
131 |
132 | A variation of option 3 with non-optional `__type` tags would prevent accidental implicit assignment errors when assigning between (say) `uint64` and `biguint`, but require explicit casting on all ops and any methods that return the base type.
133 |
134 | ```ts
135 | declare function Uint64(v): uint64
136 | declare function BigUint(v): uint64
137 |
138 | type uint64 = bigint & { __type: 'uint64' }
139 | type biguint = bigint & { __type: 'biguint' }
140 |
141 | // Require factory or cast on declaration
142 | const a: uint64 = 323n as uint64
143 | const b = Uint64(12n)
144 |
145 | // Also require factory or cast on math
146 | let c2: uint64
147 |
148 | c2 = a + b // error
149 | c2 = Uint64(a + b) // ok
150 | c2 = (a + b) as uint64 // ok
151 | ```
152 |
153 | This introduces a degree of type safety with the in-built TypeScript type system at the significant expense of legibility and writability.
154 |
155 |
156 | ### Option 5 Branded `number` (TEALScript approach)
157 |
158 | TEALScript uses a similar approach to option 3, but uses `number` as the underlying type rather than `bigint`. This has the advantage of being directly compatible with casting raw numbers (e.g. `const x: uint64 = 3` rather than `const x: uint64 = 3n`).
159 |
160 | Furthermore, any JavaScript prototype methods that return `number` like `array.length` will be similarly able to be directly used and casted to `uint64` rather than wrapping in a factory method (e.g. `const x: uint64 = [1, 2, 3].length` rather than `const x = UInt64([1, 2, 3].length)`). It's not currently clear if any such methods will be exposed within the stub types that emerge in Algorand TypeScript though; if option 1 isn't chosen then ideally we would want to avoid exposing `number` within Algorand TypeScript altogether. Key prototypes that have `number` include `string` (see [Primitive bytes and strings](./2024-05-21_primitive-bytes-and-strings.md)) and array (but TypeScript allows you to define wrapper classes that support [iteration](https://www.typescriptlang.org/docs/handbook/iterators-and-generators.html) [and](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) [spreading](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/isConcatSpreadable) so we can likely avoid `Array<T>` prototype).
161 |
162 | If `number` is used as the base brand type for `uint64` and `bigint` is used as the base brand type for `biguint` (a type that TEALScript doesn't implement, so not a breaking change) then accidental implicit assignment errors are prevented by the TypeScript type system.
163 |
164 | A key issue with using `number` as the base type is that per option 1, it's semantically a floating point number, not an integer. It is possible for the compiler to check for and disallow non-integer constant literals though, which would prevent a non-integer value appearing outside of division. A custom TypeScript transformer will need to wrap division operations to allow the result to be truncated as an integer; this is a violation of the semantic compatibility principle, but given a branded type would be used rather than `number` (the fact the base type is `number` is largely hidden from the developer) it probably doesn't violate the principle of least surprise and may be considered an acceptable compromise.
165 |
166 | The other problem with use of `number` as the base brand type is that you will lose precision and get linting errors when representing a number greater than 53-bits as a constant literal e.g. `const x: uint64 = 9007199254740992`. It *may* be possible for a custom TypeScript transformer to get the value before precision is lost (needs investigation) and then disable that particular linting tool, but that is a fairly clear violation of semantic compatibility. The workaround would have to be that the compiler detects numbers > `Number.MAX_SAFE_INTEGER` and complains and instead you would have to use the factory syntax with a `bigint` constant literal e.g. `const x = UInt64(9007199254740992n)`.
167 |
168 |
169 | ## Preferred option
170 |
171 | Either option 3 or option 5 depending on comfort level in using a floating point number type as the base type for `uint64`, requiring extra compiler checks & more complex custom transformers to overcome this, and not being able to cleanly represent very large integers as a constant literal vs lack of TypeScript protection against accidental implicit assignment of `uint64` and `biguint` (but can be checked by the compiler), and needing to avoid prototype methods that return `number` (although this matches semantic compatibility so may be a good idea anyway).
172 |
173 | Option 3 is also a breaking change for TEALScript, which would require `number` literals to either be suffixed with the `bigint` suffix (`n`) or be wrapped in a `UInt64()` factory call.
174 |
175 | Option 1 and 2 are excluded because they don't meet the requirements of semantic compatibility and least surprise. Option 4 is excluded, because the resulting syntax is unpractical.
176 |
177 | ## Selected option
178 |
179 | Option 5 has been selected as the best option
```
--------------------------------------------------------------------------------
/packages/server/tests/resources/algod/transaction.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
2 | import {
3 | transactionResources,
4 | transactionResourceSchemas,
5 | pendingTransactionInformation,
6 | pendingTransactionsByAddress,
7 | pendingTransactions,
8 | getTransactionParams,
9 | status,
10 | statusAfterBlock,
11 | handleTransactionResources
12 | } from '../../../src/resources/algod/transaction.js';
13 | import { algodClient } from '../../../src/algorand-client.js';
14 |
15 | // Mock algosdk client
16 | jest.mock('../../../src/algorand-client.js', () => ({
17 | algodClient: {
18 | pendingTransactionInformation: jest.fn(),
19 | pendingTransactionByAddress: jest.fn(),
20 | pendingTransactionsInformation: jest.fn(),
21 | getTransactionParams: jest.fn(),
22 | status: jest.fn(),
23 | statusAfterBlock: jest.fn()
24 | },
25 | API_URIS: {
26 | PENDING_TRANSACTION: 'algorand://transaction/{txid}/pending',
27 | PENDING_TRANSACTIONS_BY_ADDRESS: 'algorand://account/{address}/transactions/pending',
28 | PENDING_TRANSACTIONS: 'algorand://transactions/pending',
29 | TRANSACTION_PARAMS: 'algorand://transaction/params',
30 | NODE_STATUS: 'algorand://node/status',
31 | NODE_STATUS_AFTER_BLOCK: 'algorand://node/status/after/{round}'
32 | }
33 | }));
34 |
35 | describe('Algod Transaction Resources', () => {
36 | beforeEach(() => {
37 | jest.clearAllMocks();
38 | });
39 |
40 | describe('Resource Definitions', () => {
41 | it('should define transaction resources', () => {
42 | expect(transactionResources).toHaveLength(6);
43 | expect(transactionResources.map(r => r.name)).toEqual([
44 | 'Pending Transaction',
45 | 'Pending Transactions By Address',
46 | 'All Pending Transactions',
47 | 'Transaction Parameters',
48 | 'Node Status',
49 | 'Node Status After Block'
50 | ]);
51 | });
52 |
53 | it('should define resource schemas', () => {
54 | expect(Object.keys(transactionResourceSchemas)).toHaveLength(6);
55 | expect(transactionResourceSchemas).toHaveProperty('algorand://transaction/{txid}/pending');
56 | expect(transactionResourceSchemas).toHaveProperty('algorand://account/{address}/transactions/pending');
57 | expect(transactionResourceSchemas).toHaveProperty('algorand://transactions/pending');
58 | expect(transactionResourceSchemas).toHaveProperty('algorand://transaction/params');
59 | expect(transactionResourceSchemas).toHaveProperty('algorand://node/status');
60 | expect(transactionResourceSchemas).toHaveProperty('algorand://node/status/after/{round}');
61 | });
62 | });
63 |
64 | describe('Pending Transaction Information', () => {
65 | const mockTxId = 'MOCK_TX_ID';
66 | const mockResponse = {
67 | poolError: '',
68 | txn: { type: 'pay' },
69 | confirmedRound: 1234
70 | };
71 |
72 | beforeEach(() => {
73 | (algodClient.pendingTransactionInformation as jest.Mock).mockReturnValue({
74 | do: jest.fn().mockResolvedValue(mockResponse)
75 | });
76 | });
77 |
78 | it('should fetch pending transaction information', async () => {
79 | const result = await pendingTransactionInformation(mockTxId);
80 | expect(result).toEqual(mockResponse);
81 | expect(algodClient.pendingTransactionInformation).toHaveBeenCalledWith(mockTxId);
82 | });
83 |
84 | it('should handle errors', async () => {
85 | const error = new Error('Network error');
86 | (algodClient.pendingTransactionInformation as jest.Mock).mockReturnValue({
87 | do: jest.fn().mockRejectedValue(error)
88 | });
89 |
90 | await expect(pendingTransactionInformation(mockTxId))
91 | .rejects
92 | .toThrow('Failed to get pending transaction: Network error');
93 | });
94 | });
95 |
96 | describe('Pending Transactions By Address', () => {
97 | const mockAddress = 'MOCK_ADDRESS';
98 | const mockResponse = {
99 | topTransactions: [{ txn: { type: 'pay' } }],
100 | totalTransactions: 1
101 | };
102 |
103 | beforeEach(() => {
104 | (algodClient.pendingTransactionByAddress as jest.Mock).mockReturnValue({
105 | do: jest.fn().mockResolvedValue(mockResponse)
106 | });
107 | });
108 |
109 | it('should fetch pending transactions by address', async () => {
110 | const result = await pendingTransactionsByAddress(mockAddress);
111 | expect(result).toEqual(mockResponse);
112 | expect(algodClient.pendingTransactionByAddress).toHaveBeenCalledWith(mockAddress);
113 | });
114 |
115 | it('should handle errors', async () => {
116 | const error = new Error('Network error');
117 | (algodClient.pendingTransactionByAddress as jest.Mock).mockReturnValue({
118 | do: jest.fn().mockRejectedValue(error)
119 | });
120 |
121 | await expect(pendingTransactionsByAddress(mockAddress))
122 | .rejects
123 | .toThrow('Failed to get pending transactions by address: Network error');
124 | });
125 | });
126 |
127 | describe('All Pending Transactions', () => {
128 | const mockResponse = {
129 | topTransactions: [{ txn: { type: 'pay' } }],
130 | totalTransactions: 1
131 | };
132 |
133 | beforeEach(() => {
134 | (algodClient.pendingTransactionsInformation as jest.Mock).mockReturnValue({
135 | max: jest.fn().mockReturnThis(),
136 | do: jest.fn().mockResolvedValue(mockResponse)
137 | });
138 | });
139 |
140 | it('should fetch all pending transactions', async () => {
141 | const result = await pendingTransactions();
142 | expect(result).toEqual(mockResponse);
143 | expect(algodClient.pendingTransactionsInformation).toHaveBeenCalled();
144 | });
145 |
146 | it('should handle max transactions parameter', async () => {
147 | const maxTxns = 10;
148 | await pendingTransactions(maxTxns);
149 | const mockMax = (algodClient.pendingTransactionsInformation as jest.Mock).mock.results[0].value.max;
150 | expect(mockMax).toHaveBeenCalledWith(maxTxns);
151 | });
152 |
153 | it('should handle errors', async () => {
154 | const error = new Error('Network error');
155 | (algodClient.pendingTransactionsInformation as jest.Mock).mockReturnValue({
156 | max: jest.fn().mockReturnThis(),
157 | do: jest.fn().mockRejectedValue(error)
158 | });
159 |
160 | await expect(pendingTransactions())
161 | .rejects
162 | .toThrow('Failed to get pending transactions: Network error');
163 | });
164 | });
165 |
166 | describe('Transaction Parameters', () => {
167 | const mockResponse = {
168 | fee: 1000,
169 | firstRound: 1234,
170 | lastRound: 1235,
171 | genesisHash: 'hash',
172 | genesisID: 'testnet-v1.0'
173 | };
174 |
175 | beforeEach(() => {
176 | (algodClient.getTransactionParams as jest.Mock).mockReturnValue({
177 | do: jest.fn().mockResolvedValue(mockResponse)
178 | });
179 | });
180 |
181 | it('should fetch transaction parameters', async () => {
182 | const result = await getTransactionParams();
183 | expect(result).toEqual(mockResponse);
184 | expect(algodClient.getTransactionParams).toHaveBeenCalled();
185 | });
186 |
187 | it('should handle errors', async () => {
188 | const error = new Error('Network error');
189 | (algodClient.getTransactionParams as jest.Mock).mockReturnValue({
190 | do: jest.fn().mockRejectedValue(error)
191 | });
192 |
193 | await expect(getTransactionParams())
194 | .rejects
195 | .toThrow('Failed to get transaction params: Network error');
196 | });
197 | });
198 |
199 | describe('Node Status', () => {
200 | const mockResponse = {
201 | lastRound: 1234,
202 | catchupTime: 0,
203 | hasSyncedSinceStartup: true
204 | };
205 |
206 | beforeEach(() => {
207 | (algodClient.status as jest.Mock).mockReturnValue({
208 | do: jest.fn().mockResolvedValue(mockResponse)
209 | });
210 | });
211 |
212 | it('should fetch node status', async () => {
213 | const result = await status();
214 | expect(result).toEqual(mockResponse);
215 | expect(algodClient.status).toHaveBeenCalled();
216 | });
217 |
218 | it('should handle errors', async () => {
219 | const error = new Error('Network error');
220 | (algodClient.status as jest.Mock).mockReturnValue({
221 | do: jest.fn().mockRejectedValue(error)
222 | });
223 |
224 | await expect(status())
225 | .rejects
226 | .toThrow('Failed to get status: Network error');
227 | });
228 | });
229 |
230 | describe('Status After Block', () => {
231 | const mockRound = 1234;
232 | const mockResponse = {
233 | lastRound: mockRound + 1,
234 | catchupTime: 0,
235 | hasSyncedSinceStartup: true
236 | };
237 |
238 | beforeEach(() => {
239 | (algodClient.statusAfterBlock as jest.Mock).mockReturnValue({
240 | do: jest.fn().mockResolvedValue(mockResponse)
241 | });
242 | });
243 |
244 | it('should fetch status after block', async () => {
245 | const result = await statusAfterBlock(mockRound);
246 | expect(result).toEqual(mockResponse);
247 | expect(algodClient.statusAfterBlock).toHaveBeenCalledWith(mockRound);
248 | });
249 |
250 | it('should handle errors', async () => {
251 | const error = new Error('Network error');
252 | (algodClient.statusAfterBlock as jest.Mock).mockReturnValue({
253 | do: jest.fn().mockRejectedValue(error)
254 | });
255 |
256 | await expect(statusAfterBlock(mockRound))
257 | .rejects
258 | .toThrow('Failed to get status after block: Network error');
259 | });
260 | });
261 |
262 | describe('Resource Handler', () => {
263 | const mockTxId = 'MOCK_TX_ID';
264 | const mockAddress = 'MOCK_ADDRESS';
265 | const mockRound = 1234;
266 |
267 | beforeEach(() => {
268 | // Reset all mocks with success responses
269 | (algodClient.pendingTransactionInformation as jest.Mock).mockReturnValue({
270 | do: jest.fn().mockResolvedValue({ txn: { type: 'pay' }, confirmedRound: mockRound })
271 | });
272 | (algodClient.pendingTransactionByAddress as jest.Mock).mockReturnValue({
273 | do: jest.fn().mockResolvedValue({ topTransactions: [], totalTransactions: 0 })
274 | });
275 | (algodClient.pendingTransactionsInformation as jest.Mock).mockReturnValue({
276 | max: jest.fn().mockReturnThis(),
277 | do: jest.fn().mockResolvedValue({ topTransactions: [], totalTransactions: 0 })
278 | });
279 | (algodClient.getTransactionParams as jest.Mock).mockReturnValue({
280 | do: jest.fn().mockResolvedValue({ fee: 1000 })
281 | });
282 | (algodClient.status as jest.Mock).mockReturnValue({
283 | do: jest.fn().mockResolvedValue({ lastRound: mockRound })
284 | });
285 | (algodClient.statusAfterBlock as jest.Mock).mockReturnValue({
286 | do: jest.fn().mockResolvedValue({ lastRound: mockRound + 1 })
287 | });
288 | });
289 |
290 | it('should handle pending transaction URI', async () => {
291 | const uri = `algorand://transaction/${mockTxId}/pending`;
292 | const result = await handleTransactionResources(uri);
293 | expect(result).toHaveLength(1);
294 | expect(JSON.parse(result[0].text)).toHaveProperty('transaction');
295 | expect(JSON.parse(result[0].text)).toHaveProperty('currentRound');
296 | });
297 |
298 | it('should handle pending transactions by address URI', async () => {
299 | const uri = `algorand://account/${mockAddress}/transactions/pending`;
300 | const result = await handleTransactionResources(uri);
301 | expect(result).toHaveLength(1);
302 | expect(JSON.parse(result[0].text)).toHaveProperty('transactions');
303 | expect(JSON.parse(result[0].text)).toHaveProperty('totalTransactions');
304 | });
305 |
306 | it('should handle all pending transactions URI', async () => {
307 | const uri = 'algorand://transactions/pending';
308 | const result = await handleTransactionResources(uri);
309 | expect(result).toHaveLength(1);
310 | expect(JSON.parse(result[0].text)).toHaveProperty('transactions');
311 | expect(JSON.parse(result[0].text)).toHaveProperty('totalTransactions');
312 | });
313 |
314 | it('should handle transaction parameters URI', async () => {
315 | const uri = 'algorand://transaction/params';
316 | const result = await handleTransactionResources(uri);
317 | expect(result).toHaveLength(1);
318 | expect(JSON.parse(result[0].text)).toHaveProperty('params');
319 | });
320 |
321 | it('should handle node status URI', async () => {
322 | const uri = 'algorand://node/status';
323 | const result = await handleTransactionResources(uri);
324 | expect(result).toHaveLength(1);
325 | expect(JSON.parse(result[0].text)).toHaveProperty('status');
326 | });
327 |
328 | it('should handle status after block URI', async () => {
329 | const uri = `algorand://node/status/after/${mockRound}`;
330 | const result = await handleTransactionResources(uri);
331 | expect(result).toHaveLength(1);
332 | expect(JSON.parse(result[0].text)).toHaveProperty('status');
333 | });
334 |
335 | it('should return empty array for unknown URI', async () => {
336 | const uri = 'algorand://unknown';
337 | const result = await handleTransactionResources(uri);
338 | expect(result).toHaveLength(0);
339 | });
340 |
341 | it('should handle errors with McpError', async () => {
342 | const error = new Error('Network error');
343 | (algodClient.pendingTransactionInformation as jest.Mock).mockReturnValue({
344 | do: jest.fn().mockRejectedValue(error)
345 | });
346 |
347 | const uri = `algorand://transaction/${mockTxId}/pending`;
348 | await expect(handleTransactionResources(uri))
349 | .rejects
350 | .toThrow(new McpError(ErrorCode.InternalError, 'Network error'));
351 | });
352 | });
353 | });
354 |
```
--------------------------------------------------------------------------------
/packages/server/src/resources/knowledge/taxonomy/algokit:utils:typescript:capabilities:testing.md:
--------------------------------------------------------------------------------
```markdown
1 | # Automated testing
2 |
3 | Automated testing is a higher-order use case capability provided by AlgoKit Utils that builds on top of the core capabilities. It allows you to use terse, robust automated testing primitives that work across any testing framework (including jest and vitest) to facilitate fixture management, quickly generating isolated and funded test accounts, transaction logging, indexer wait management and log capture.
4 |
5 | To see some usage examples check out the all of the [automated tests](../../src/) and the various \*.spec.ts files (AlgoKit Utils [dogfoods](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) it's own testing library). Alternatively, you can see an example of using this library to test a smart contract with [the tests](https://github.com/algorandfoundation/nft_voting_tool/blob/main/src/algorand/smart_contracts/tests/voting.spec.ts) for the [on-chain voting tool](https://github.com/algorandfoundation/nft_voting_tool#readme).
6 |
7 | ## Module import
8 |
9 | The testing capability is not exposed from the [root algokit module](../code/modules/index.md) so there is a clear separation between testing functionality and non-testing functionality.
10 |
11 | To access all of the functionality in the testing capability individually, you can import the [testing module](../code/modules/testing.md):
12 |
13 | ```typescript
14 | import * as algotesting from '@algorandfoundation/algokit-utils/testing'
15 | ```
16 |
17 | ## Algorand fixture
18 |
19 | In general, the only entrypoint you will need to use the testing capability is just by importing the `algorandFixture` since it exposes the rest of the functionality in a manner that is easy to integrate with an underlying test framework like Jest or vitest:
20 |
21 | ```typescript
22 | import { algorandFixture } from '@algorandfoundation/algokit-utils/testing'
23 | ```
24 |
25 | ### Using with Jest
26 |
27 | To integrate with [Jest](https://jestjs.io/) you need to pass the `fixture.beforeEach` method into Jest's `beforeEach` method and then within each test you can access `fixture.context` to access per-test isolated fixture values.
28 |
29 | ```typescript
30 | import { describe, test, beforeEach } from '@jest/globals'
31 | import { algorandFixture } from './testing'
32 |
33 | describe('MY MODULE', () => {
34 | const fixture = algorandFixture()
35 | beforeEach(fixture.beforeEach, 10_000)
36 |
37 | test('MY TEST', async () => {
38 | const { algorand, testAccount /* ... */ } = fixture.context
39 |
40 | // Test stuff!
41 | })
42 | })
43 | ```
44 |
45 | Occasionally there may be a delay when first running the fixture setup so we add a 10s timeout to avoid intermittent test failures (`10_000`).
46 |
47 | ### Using with vitest
48 |
49 | To integrate with [vitest](https://vitest.dev/) you need to pass the `fixture.beforeEach` method into vitest's `beforeEach` method and then within each test you can access `fixture.context` to access per-test isolated fixture values.
50 |
51 | ```typescript
52 | import { describe, test, beforeEach } from 'vitest'
53 | import { algorandFixture } from './testing'
54 |
55 | describe('MY MODULE', () => {
56 | const fixture = algorandFixture()
57 | beforeEach(fixture.beforeEach, 10_000)
58 |
59 | test('MY TEST', async () => {
60 | const { algorand, testAccount /* ... */ } = fixture.context
61 |
62 | // Test stuff!
63 | })
64 | })
65 | ```
66 |
67 | ### Fixture configuration
68 |
69 | When calling `algorandFixture()` you can optionally pass in some fixture configuration, with any of these properties (all optional):
70 |
71 | - `algod?: Algodv2` - An optional algod client, if not specified then it will create one against environment variables defined network (if present) or default LocalNet
72 | - `indexer?: Indexer` - An optional indexer client, if not specified then it will create one against environment variables defined network (if present) or default LocalNet
73 | - `kmd?: Kmd` - An optional kmd client, if not specified then it will create one against environment variables defined network (if present) or default LocalNet
74 | - `testAccountFunding?: AlgoAmount` - The [amount](./amount.md) of funds to allocate to the default testing account, if not specified then it will get `10` ALGO
75 |
76 | ### Using the fixture context
77 |
78 | The `fixture.context` property is of type [`AlgorandTestAutomationContext`](../code/interfaces/types_testing.AlgorandTestAutomationContext.md) exposes the following properties from which you can pick which ones you want in a given test using an object [destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment):
79 |
80 | - `algorand: AlgorandClient` - An [`AlgorandClient`](./algorand-client.md) instance
81 | - `algod: Algodv2` - Proxy Algod client instance that will log sent transactions in `transactionLogger`
82 | - `indexer: Indexer` - Indexer client instance
83 | - `kmd: Kmd` - KMD client instance
84 | - `transactionLogger: TransactionLogger` - Transaction logger that will log transaction IDs for all transactions issued by `algod`
85 | - `testAccount: Account` - Funded test account that is ephemerally created for each test
86 | - `generateAccount: (params: GetTestAccountParams) => Promise<Account>` - Generate and fund an additional ephemerally created account
87 | - `waitForIndexer: () => Promise<void>` - Wait for the indexer to catch up with all transactions logged by transactionLogger
88 | - `waitForIndexerTransaction: (transactionId: string) => Promise<TransactionLookupResult>` - Wait for the indexer to catch up with the given transaction ID
89 |
90 | ## Log capture fixture
91 |
92 | If you want to capture log messages from AlgoKit that are issued within your test so that you can assert on them or parse them for debugging information etc. then you can use the log capture fixture.
93 |
94 | ```typescript
95 | import { algoKitLogCaptureFixture } from '@algorandfoundation/algokit-utils/testing'
96 | ```
97 |
98 | The log capture fixture works by setting the logger within the AlgoKit configuration to be a [`TestLogger`](../code/classes/testing.TestLogger.md) during the test run.
99 |
100 | ### Using with Jest
101 |
102 | To integrate with [Jest](https://jestjs.io/) you need to pass the `fixture.beforeEach` method into Jest's `beforeEach` method and then within each test you can access `fixture.context` to access per-test isolated fixture values.
103 |
104 | ```typescript
105 | import { describe, test, beforeEach, afterEach } from '@jest/globals'
106 | import { algoKitLogCaptureFixture } from './testing'
107 |
108 | describe('MY MODULE', () => {
109 | const logs = algoKitLogCaptureFixture()
110 | beforeEach(logs.beforeEach)
111 | afterEach(logs.afterEach)
112 |
113 | test('MY TEST', async () => {
114 | const { algorand, testAccount } = fixture.context
115 | // Test stuff!
116 |
117 | const capturedLogs = logs.testLogger.capturedLogs
118 | // do stuff with the logs
119 | })
120 | })
121 | ```
122 |
123 | ### Using with vitest
124 |
125 | To integrate with [vitest](https://vitest.dev/) you need to pass the `fixture.beforeEach` method into vitest's `beforeEach` method and then within each test you can access `fixture.context` to access per-test isolated fixture values.
126 |
127 | ```typescript
128 | import { describe, test, beforeEach, afterEach } from 'vitest'
129 | import { algoKitLogCaptureFixture } from './testing'
130 |
131 | describe('MY MODULE', () => {
132 | const logs = algoKitLogCaptureFixture()
133 | beforeEach(logs.beforeEach)
134 | afterEach(logs.afterEach)
135 |
136 | test('MY TEST', async () => {
137 | const { algorand, testAccount } = fixture.context
138 | // Test stuff!
139 |
140 | const capturedLogs = logs.testLogger.capturedLogs
141 | // do stuff with the logs
142 | })
143 | })
144 | ```
145 |
146 | ### Snapshot testing the logs
147 |
148 | If you want to quickly pin some behaviour of what logic you have does in terms of invoking AlgoKit methods you can do a [snapshot test](https://jestjs.io/docs/snapshot-testing) / [approval test](https://approvaltests.com/) of the captured log output. The only problem is this output will contain identifiers that will change for every test run and thus will constantly break the snapshot. In order to work around this you can use the `getLogSnapshot` method on the `TestLogger`, which will replace those changing values with predictable strings to keep the snapshot integrity intact.
149 |
150 | This might look something like this:
151 |
152 | ```typescript
153 | const { algorand, testAccount } = fixture.context
154 | const result = await algorand.client.getTypedClientById(HelloWorldContractClient, { id: 0 }).deploy()
155 | expect(
156 | logging.testLogger.getLogSnapshot({
157 | accounts: [testAccount],
158 | transactions: [result.transaction],
159 | apps: [result.appId],
160 | }),
161 | ).toMatchSnapshot()
162 | ```
163 |
164 | ## Waiting for indexer
165 |
166 | Often there will be things that you do in your test that you may want to assert in using data that is exclusively in indexer such as transaction notes. The problem is indexer asynchronously indexes the data in algod, even when devmode is turned on and algod instantly confirms transactions.
167 |
168 | This means it's easy to create tests that are flaky and have intermittent test failures (sometimes indexer is up to date and other times it hasn't caught up yet).
169 |
170 | The testing capability provides mechanisms for waiting for indexer to catch up, namely:
171 |
172 | - `algotesting.runWhenIndexerCaughtUp(run: () => Promise<T>)` - Executes the given action every 200ms up to 20 times until there is no longer an error with a `status` property with `404` and then returns the result of the action; this will work for any call that calls indexer APIs expecting to return a single record
173 | - `algorandFixture.waitForIndexer()` - Waits for indexer to catch up with all transactions that have been captured by the `transactionLogger` in the Algorand fixture
174 | - `algorandFixture.waitForIndexerTransaction(transactionId)` - Waits for indexer to catch up with the single transaction of the given ID
175 |
176 | ## Logging transactions
177 |
178 | When testing, it can be useful to capture all of the transactions that have been issued with a given test run. They can then be asserted on, or used for [waiting for indexer](#waiting-for-indexer), etc.
179 |
180 | The testing capability provides the ability to capture transactions via the [`TransactionLogger`](../code/classes/testing.TransactionLogger.md) class.
181 |
182 | The `TransactionLogger` has the following methods:
183 |
184 | - `logRawTransaction(signedTransactions: Uint8Array | Uint8Array[])` - Logs the IDs of the given signed transaction(s)
185 | - `capture(algod)` - Returns a proxy `algosdk.Algodv2` instance that wraps the given `algod` client that will call `logRawTransaction` for every call to `sendRawTransaction` on that algod instance
186 | - `sentTransactionIds` - Returns the currently captured list of transaction IDs that have been logged
187 | - `clear()` - Clears the current list of transaction IDs
188 | - `waitForIndexer(indexer)` - [Waits for the given indexer instance to catch up](#waiting-for-indexer) with the currently logged transaction IDs
189 |
190 | The easiest way to use this functionality is via the [Algorand fixture](#algorand-fixture), which automatically provides a `transactionLogger` and a proxy `algod` connected to that `transactionLogger`.
191 |
192 | ## Getting a test account
193 |
194 | When testing, it's often useful to ephemerally generate random accounts, fund them with some number of Algo and then use that account to perform transactions. By creating an ephemeral, random account you naturally get isolation between tests and test runs and don't need to start from a specific blockchain network state. This makes test less flakey, and also means the same test can be run against LocalNet and (say) TestNet.
195 |
196 | The key when generating a test account is getting hold of a [dispenser](./transfer.md#dispenser) and then [ensuring the test account is funded](./transfer.md#ensurefunded).
197 |
198 | To make it easier to quickly get a test account the testing capability provides the following mechanisms:
199 |
200 | - [`algotesting.getTestAccount(testAccountParams, algod, kmd?)`](../code/modules/testing.md#gettestaccount) - Generates a random new account, logs the mnemonic of the account (unless suppressed), funds it from the [dispenser](./transfer.md#dispenser)
201 | - `algorandFixture.testAccount` - A test account that is always generated for every test (log output suppressed to reduce noise, but worth noting that means the mnemonic isn't logged for this account), by default it is given 10 Algo unless overridden in the [fixture config](#fixture-configuration)
202 | - [`algorandFixture.generateAccount(testAccountParams)`](../code/interfaces/types_testing.AlgorandTestAutomationContext.md#generateaccount) - Allows you to quickly generate a test account with the `algod` and `kmd` instances that are part of the given fixture
203 |
204 | The parameters object that controls test account generation, [`GetTestAccountParams`](../code/interfaces/types_testing.GetTestAccountParams.md), has the following properties:
205 |
206 | - `initialFunds: AlgoAmount` - Initial funds to ensure the account has
207 | - `suppressLog?: boolean` - Whether to suppress the log (which includes a mnemonic) or not (default: do not suppress the log)
208 |
```
--------------------------------------------------------------------------------
/packages/server/tests/resources/indexer/asset.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
2 | import {
3 | assetResources,
4 | assetResourceSchemas,
5 | lookupAssetByID,
6 | lookupAssetBalances,
7 | lookupAssetTransactions,
8 | searchForAssets,
9 | handleAssetResources
10 | } from '../../../src/resources/indexer/asset.js';
11 | import { indexerClient } from '../../../src/algorand-client.js';
12 |
13 | // Mock algosdk client
14 | jest.mock('../../../src/algorand-client.js', () => ({
15 | indexerClient: {
16 | lookupAssetByID: jest.fn(),
17 | lookupAssetBalances: jest.fn(),
18 | lookupAssetTransactions: jest.fn(),
19 | searchForAssets: jest.fn()
20 | },
21 | API_URIS: {
22 | ASSET_DETAILS: 'algorand://asset/{asset-id}',
23 | ASSET_BALANCES: 'algorand://asset/{asset-id}/balances',
24 | ASSET_TRANSACTIONS: 'algorand://asset/{asset-id}/transactions',
25 | ASSET_BALANCES_BY_ID: 'algorand://asset/{asset-id}/balances/{address}',
26 | ASSET_TRANSACTIONS_BY_ID: 'algorand://asset/{asset-id}/transactions/{txid}'
27 | }
28 | }));
29 |
30 | describe('Indexer Asset Resources', () => {
31 | beforeEach(() => {
32 | jest.clearAllMocks();
33 | });
34 |
35 | describe('Resource Definitions', () => {
36 | it('should define asset resources', () => {
37 | expect(assetResources).toHaveLength(5);
38 | expect(assetResources.map(r => r.name)).toEqual([
39 | 'Asset Details',
40 | 'Asset Balances',
41 | 'Asset Transactions',
42 | 'Asset Balance By Address',
43 | 'Asset Transaction By ID'
44 | ]);
45 | });
46 |
47 | it('should define resource schemas', () => {
48 | expect(Object.keys(assetResourceSchemas)).toHaveLength(5);
49 | expect(assetResourceSchemas).toHaveProperty('algorand://asset/{asset-id}');
50 | expect(assetResourceSchemas).toHaveProperty('algorand://asset/{asset-id}/balances');
51 | expect(assetResourceSchemas).toHaveProperty('algorand://asset/{asset-id}/transactions');
52 | expect(assetResourceSchemas).toHaveProperty('algorand://asset/{asset-id}/balances/{address}');
53 | expect(assetResourceSchemas).toHaveProperty('algorand://asset/{asset-id}/transactions/{txid}');
54 | });
55 | });
56 |
57 | describe('Asset Information', () => {
58 | const mockAssetId = 123;
59 | const mockResponse = {
60 | asset: {
61 | index: mockAssetId,
62 | params: {
63 | creator: 'MOCK_ADDRESS',
64 | name: 'Test Asset',
65 | unitName: 'TEST',
66 | total: 1000000,
67 | decimals: 6
68 | }
69 | },
70 | currentRound: 1234
71 | };
72 |
73 | beforeEach(() => {
74 | (indexerClient.lookupAssetByID as jest.Mock).mockReturnValue({
75 | do: jest.fn().mockResolvedValue(mockResponse)
76 | });
77 | });
78 |
79 | it('should fetch asset information', async () => {
80 | const result = await lookupAssetByID(mockAssetId);
81 | expect(result).toEqual(mockResponse);
82 | expect(indexerClient.lookupAssetByID).toHaveBeenCalledWith(mockAssetId);
83 | });
84 |
85 | it('should handle errors', async () => {
86 | const error = new Error('Network error');
87 | (indexerClient.lookupAssetByID as jest.Mock).mockReturnValue({
88 | do: jest.fn().mockRejectedValue(error)
89 | });
90 |
91 | await expect(lookupAssetByID(mockAssetId))
92 | .rejects
93 | .toThrow('Failed to get asset info: Network error');
94 | });
95 | });
96 |
97 | describe('Asset Balances', () => {
98 | const mockAssetId = 123;
99 | const mockResponse = {
100 | balances: [{ address: 'addr1', amount: 100 }],
101 | currentRound: 1234
102 | };
103 |
104 | beforeEach(() => {
105 | (indexerClient.lookupAssetBalances as jest.Mock).mockReturnValue({
106 | limit: jest.fn().mockReturnThis(),
107 | currencyGreaterThan: jest.fn().mockReturnThis(),
108 | currencyLessThan: jest.fn().mockReturnThis(),
109 | nextToken: jest.fn().mockReturnThis(),
110 | do: jest.fn().mockResolvedValue(mockResponse)
111 | });
112 | });
113 |
114 | it('should fetch asset balances', async () => {
115 | const result = await lookupAssetBalances(mockAssetId);
116 | expect(result).toEqual(mockResponse);
117 | expect(indexerClient.lookupAssetBalances).toHaveBeenCalledWith(mockAssetId);
118 | });
119 |
120 | it('should handle search parameters', async () => {
121 | const params = {
122 | limit: 10,
123 | currencyGreaterThan: 1000,
124 | currencyLessThan: 2000,
125 | nextToken: 'token123'
126 | };
127 |
128 | await lookupAssetBalances(mockAssetId, params);
129 | const mock = indexerClient.lookupAssetBalances as jest.Mock;
130 | const chain = mock.mock.results[0].value;
131 |
132 | expect(chain.limit).toHaveBeenCalledWith(params.limit);
133 | expect(chain.currencyGreaterThan).toHaveBeenCalledWith(params.currencyGreaterThan);
134 | expect(chain.currencyLessThan).toHaveBeenCalledWith(params.currencyLessThan);
135 | expect(chain.nextToken).toHaveBeenCalledWith(params.nextToken);
136 | });
137 |
138 | it('should handle errors', async () => {
139 | const error = new Error('Network error');
140 | (indexerClient.lookupAssetBalances as jest.Mock).mockReturnValue({
141 | do: jest.fn().mockRejectedValue(error)
142 | });
143 |
144 | await expect(lookupAssetBalances(mockAssetId))
145 | .rejects
146 | .toThrow('Failed to get asset balances: Network error');
147 | });
148 | });
149 |
150 | describe('Asset Transactions', () => {
151 | const mockAssetId = 123;
152 | const mockResponse = {
153 | transactions: [{ id: 'txn1' }],
154 | currentRound: 1234
155 | };
156 |
157 | beforeEach(() => {
158 | (indexerClient.lookupAssetTransactions as jest.Mock).mockReturnValue({
159 | limit: jest.fn().mockReturnThis(),
160 | beforeTime: jest.fn().mockReturnThis(),
161 | afterTime: jest.fn().mockReturnThis(),
162 | minRound: jest.fn().mockReturnThis(),
163 | maxRound: jest.fn().mockReturnThis(),
164 | address: jest.fn().mockReturnThis(),
165 | addressRole: jest.fn().mockReturnThis(),
166 | excludeCloseTo: jest.fn().mockReturnThis(),
167 | nextToken: jest.fn().mockReturnThis(),
168 | do: jest.fn().mockResolvedValue(mockResponse)
169 | });
170 | });
171 |
172 | it('should fetch asset transactions', async () => {
173 | const result = await lookupAssetTransactions(mockAssetId);
174 | expect(result).toEqual(mockResponse);
175 | expect(indexerClient.lookupAssetTransactions).toHaveBeenCalledWith(mockAssetId);
176 | });
177 |
178 | it('should handle search parameters', async () => {
179 | const params = {
180 | limit: 10,
181 | beforeTime: '2023-01-01',
182 | afterTime: '2022-01-01',
183 | minRound: 1000,
184 | maxRound: 2000,
185 | address: 'addr1',
186 | addressRole: 'sender',
187 | excludeCloseTo: true,
188 | nextToken: 'token123'
189 | };
190 |
191 | await lookupAssetTransactions(mockAssetId, params);
192 | const mock = indexerClient.lookupAssetTransactions as jest.Mock;
193 | const chain = mock.mock.results[0].value;
194 |
195 | expect(chain.limit).toHaveBeenCalledWith(params.limit);
196 | expect(chain.beforeTime).toHaveBeenCalledWith(params.beforeTime);
197 | expect(chain.afterTime).toHaveBeenCalledWith(params.afterTime);
198 | expect(chain.minRound).toHaveBeenCalledWith(params.minRound);
199 | expect(chain.maxRound).toHaveBeenCalledWith(params.maxRound);
200 | expect(chain.address).toHaveBeenCalledWith(params.address);
201 | expect(chain.addressRole).toHaveBeenCalledWith(params.addressRole);
202 | expect(chain.excludeCloseTo).toHaveBeenCalledWith(params.excludeCloseTo);
203 | expect(chain.nextToken).toHaveBeenCalledWith(params.nextToken);
204 | });
205 |
206 | it('should handle errors', async () => {
207 | const error = new Error('Network error');
208 | (indexerClient.lookupAssetTransactions as jest.Mock).mockReturnValue({
209 | do: jest.fn().mockRejectedValue(error)
210 | });
211 |
212 | await expect(lookupAssetTransactions(mockAssetId))
213 | .rejects
214 | .toThrow('Failed to get asset transactions: Network error');
215 | });
216 | });
217 |
218 | describe('Search Assets', () => {
219 | const mockResponse = {
220 | assets: [{ index: 1, params: { name: 'Test' } }],
221 | currentRound: 1234
222 | };
223 |
224 | beforeEach(() => {
225 | (indexerClient.searchForAssets as jest.Mock).mockReturnValue({
226 | limit: jest.fn().mockReturnThis(),
227 | creator: jest.fn().mockReturnThis(),
228 | name: jest.fn().mockReturnThis(),
229 | unit: jest.fn().mockReturnThis(),
230 | index: jest.fn().mockReturnThis(),
231 | nextToken: jest.fn().mockReturnThis(),
232 | do: jest.fn().mockResolvedValue(mockResponse)
233 | });
234 | });
235 |
236 | it('should search assets', async () => {
237 | const result = await searchForAssets();
238 | expect(result).toEqual(mockResponse);
239 | expect(indexerClient.searchForAssets).toHaveBeenCalled();
240 | });
241 |
242 | it('should handle search parameters', async () => {
243 | const params = {
244 | limit: 10,
245 | creator: 'addr1',
246 | name: 'Test',
247 | unit: 'TEST',
248 | assetId: 123,
249 | nextToken: 'token123'
250 | };
251 |
252 | await searchForAssets(params);
253 | const mock = indexerClient.searchForAssets as jest.Mock;
254 | const chain = mock.mock.results[0].value;
255 |
256 | expect(chain.limit).toHaveBeenCalledWith(params.limit);
257 | expect(chain.creator).toHaveBeenCalledWith(params.creator);
258 | expect(chain.name).toHaveBeenCalledWith(params.name);
259 | expect(chain.unit).toHaveBeenCalledWith(params.unit);
260 | expect(chain.index).toHaveBeenCalledWith(params.assetId);
261 | expect(chain.nextToken).toHaveBeenCalledWith(params.nextToken);
262 | });
263 |
264 | it('should handle errors', async () => {
265 | const error = new Error('Network error');
266 | (indexerClient.searchForAssets as jest.Mock).mockReturnValue({
267 | do: jest.fn().mockRejectedValue(error)
268 | });
269 |
270 | await expect(searchForAssets())
271 | .rejects
272 | .toThrow('Failed to search assets: Network error');
273 | });
274 | });
275 |
276 | describe('Resource Handler', () => {
277 | const mockAssetId = 123;
278 | const mockAddress = 'MOCK_ADDRESS';
279 | const mockTxId = 'MOCK_TX_ID';
280 |
281 | beforeEach(() => {
282 | // Reset all mocks with success responses
283 | (indexerClient.lookupAssetByID as jest.Mock).mockReturnValue({
284 | do: jest.fn().mockResolvedValue({
285 | asset: { index: mockAssetId },
286 | currentRound: 1234
287 | })
288 | });
289 | (indexerClient.lookupAssetBalances as jest.Mock).mockReturnValue({
290 | do: jest.fn().mockResolvedValue({
291 | balances: [{ address: mockAddress }],
292 | currentRound: 1234
293 | })
294 | });
295 | (indexerClient.lookupAssetTransactions as jest.Mock).mockReturnValue({
296 | do: jest.fn().mockResolvedValue({
297 | transactions: [{ id: mockTxId }],
298 | currentRound: 1234
299 | })
300 | });
301 | });
302 |
303 | it('should handle asset details URI', async () => {
304 | const uri = `algorand://asset/${mockAssetId}`;
305 | const result = await handleAssetResources(uri);
306 | expect(result).toHaveLength(1);
307 | expect(JSON.parse(result[0].text)).toHaveProperty('asset');
308 | expect(JSON.parse(result[0].text)).toHaveProperty('currentRound');
309 | });
310 |
311 | it('should handle asset balances URI', async () => {
312 | const uri = `algorand://asset/${mockAssetId}/balances`;
313 | const result = await handleAssetResources(uri);
314 | expect(result).toHaveLength(1);
315 | expect(JSON.parse(result[0].text)).toHaveProperty('balances');
316 | expect(JSON.parse(result[0].text)).toHaveProperty('currentRound');
317 | });
318 |
319 | it('should handle asset transactions URI', async () => {
320 | const uri = `algorand://asset/${mockAssetId}/transactions`;
321 | const result = await handleAssetResources(uri);
322 | expect(result).toHaveLength(1);
323 | expect(JSON.parse(result[0].text)).toHaveProperty('transactions');
324 | expect(JSON.parse(result[0].text)).toHaveProperty('currentRound');
325 | });
326 |
327 | it('should handle asset balance by address URI', async () => {
328 | const uri = `algorand://asset/${mockAssetId}/balances/${mockAddress}`;
329 | const result = await handleAssetResources(uri);
330 | expect(result).toHaveLength(1);
331 | expect(JSON.parse(result[0].text)).toHaveProperty('balance');
332 | expect(JSON.parse(result[0].text)).toHaveProperty('currentRound');
333 | });
334 |
335 | it('should handle asset transaction by ID URI', async () => {
336 | const uri = `algorand://asset/${mockAssetId}/transactions/${mockTxId}`;
337 | const result = await handleAssetResources(uri);
338 | expect(result).toHaveLength(1);
339 | expect(JSON.parse(result[0].text)).toHaveProperty('transaction');
340 | expect(JSON.parse(result[0].text)).toHaveProperty('currentRound');
341 | });
342 |
343 | it('should return empty array for unknown URI', async () => {
344 | const uri = 'algorand://unknown';
345 | const result = await handleAssetResources(uri);
346 | expect(result).toHaveLength(0);
347 | });
348 |
349 | it('should handle errors with McpError', async () => {
350 | const error = new Error('Network error');
351 | (indexerClient.lookupAssetByID as jest.Mock).mockReturnValue({
352 | do: jest.fn().mockRejectedValue(error)
353 | });
354 |
355 | const uri = `algorand://asset/${mockAssetId}`;
356 | await expect(handleAssetResources(uri))
357 | .rejects
358 | .toThrow(new McpError(ErrorCode.InternalError, 'Failed to get asset details: Network error'));
359 | });
360 | });
361 | });
362 |
```
--------------------------------------------------------------------------------
/packages/server/src/resources/knowledge/taxonomy/ARCs:specs:arc-0019.md:
--------------------------------------------------------------------------------
```markdown
1 | ---
2 | arc: 19
3 | title: Templating of NFT ASA URLs for mutability
4 | description: Templating mechanism of the URL so that changeable data in an asset can be substituted by a client, providing a mutable URL.
5 | author: Patrick Bennett / TxnLab Inc. (@pbennett)
6 | discussions-to: https://github.com/algorandfoundation/ARCs/issues/73
7 | status: Final
8 | type: Standards Track
9 | category: ARC
10 | sub-category: Asa
11 | created: 2021-01-23
12 | ---
13 |
14 | ## Abstract
15 | This ARC describes a template substitution for URLs in ASAs, initially for ipfs:// scheme URLs allowing mutable CID replacement in rendered URLs.
16 |
17 | The proposed template-XXX scheme has substitutions like:
18 | ```
19 | template-ipfs://{ipfscid:<version>:<multicodec>:<field name containing 32-byte digest, ie reserve>:<hash type>}[/...]
20 | ```
21 |
22 | This will allow modifying the 32-byte 'Reserve address' in an ASA to represent a new IPFS content-id hash. Changing of the reserve address via an asset-config transaction will be all that is needed to point an ASA URL to new IPFS content. The client reading this URL, will compose a fully formed IPFS Content-ID based on the version, multicodec, and hash arguments provided in the ipfscid substitution.
23 |
24 | ## Motivation
25 |
26 | While immutability for many NFTs is appropriate (see [ARC-3](./arc-0003.md) link), there are cases where some type of mutability is desired for NFT metadata and/or digital media. The data being referenced by the pointer should be immutable but the pointer may be updated to provide a kind of mutability. The data being referenced may be of any size.
27 |
28 | Algorand ASAs support mutation of several parameters, namely the role address fields (Manager, Clawback, Freeze, and Reserve addresses), unless previously cleared. These are changed via an asset-config transaction from the Manager account. An asset-config transaction may include a note, but it is limited to 1KB and accessing this value requires clients to use an indexer to iterate/retrieve the values.
29 |
30 | Of the parameters that are mutable, the Reserve address is somewhat distinct in that it is not used for anything directly as part of the protocol. It is used solely for determining what is in/out of circulation (by subtracting supply from that held by the reserve address). With a (pure) NFT, the Reserve address is irrelevant as it is a 1 of 1 unit. Thus, the Reserve address may be repurposed as a 32-byte 'bitbucket'.
31 |
32 | These 32-bytes can, for example, hold a SHA2-256 hash uniquely referencing the desired content for the ASA (ARC-3-like metadata for example)
33 |
34 | Using the reserve address in this way means that what an ASA 'points to' for metadata can be changed with a single asset config transaction, changing only the 32-bytes of the reserve address. The new value is accessible via even non-archival nodes with a single call to the `/v2/assets/xxx` REST endpoint.
35 |
36 | ## Specification
37 |
38 | The key words "**MUST**", "**MUST NOT**", "**REQUIRED**", "**SHALL**", "**SHALL NOT**", "**SHOULD**", "**SHOULD NOT**", "**RECOMMENDED**", "**MAY**", and "**OPTIONAL**" in this document are to be interpreted as described in <a href="https://www.ietf.org/rfc/rfc2119.txt">RFC-2119</a>.
39 |
40 | This proposal specifies a method to provide mutability for IPFS hosted content-ids. The intention is that FUTURE ARCs could define additional template substitutions, but this is not meant to be a kitchen sink of templates, only to establish a possible baseline of syntax.
41 |
42 | An indication that this ARC is in use is defined by an ASA URL's "scheme" having the prefix "**template-**".
43 |
44 | An Asset conforming this specification **MUST** have:
45 |
46 | 1. **URL Scheme of "template-ipfs"**
47 |
48 | The URL of the asset must be of the form:
49 | ```plain
50 | template-ipfs://(...)
51 | ```
52 |
53 | > The ipfs:// scheme is already somewhat of a meta scheme in that clients interpret the ipfs scheme as referencing an IPFS CID (version 0/base58 or 1/base32 currently) followed by optional path within certain types of IPFS DAG content (IPLD CAR content for example). The clients take the CID and use to fetch directly from the IPFS network directly via IPFS nodes, or via various IPFS gateways (https://ipfs.io/ipfs/CID[/...], pinata, etc.)).
54 |
55 | 2. **An "ipfscid" _template_ argument in place of the normal CID.**
56 |
57 | Where the format of templates are `{<template type>:<parameters...>])`
58 |
59 | The ipfscid template definitions is based on properties within the IPFS CID spec: https://github.com/multiformats/cid
60 |
61 | ```
62 | ipfscid:<version>:<multicodec content-type name>:<field name (containing 32-byte digest, i.e., "reserve")>:<hash type>
63 | ```
64 |
65 | > The intent is to recompose a complete CID based on the content-hash contained within the 32-byte reserve address, but using the correct multicodec content type, ipfs content-id version, and hash type to match how the asset creator will seed the IPFS content. If a single file is added using the 'ipfs' CLI via `ipfs add --cid-version=1 metadata.json` then the resulting content will be encoded using the 'raw' multicodec type. If a directory is added containing one or more files, then it will be encoded using the dag-pb multicodec. CAR content will also be dag-pb. Thus based on the method used to post content to IPFS, the ipfscid template should match.
66 |
67 | The parameters to the template ipfscid are:
68 | 1. IPFS `<version>`, **MUST** a valid IPFS CID version. Client implementation **MUST** support '0' or '1' and **SHOULD** support future version.
69 | 2. `<multicodec content-type name>` **MUST** be an IPFS multicodec name. Client implementations **MUST** support 'raw' or 'dag-pb'. Other codecs **SHOULD** be supported but are beyond the scope of this proposal.
70 | 3. `<field name>` **MUST** be 'reserve'.
71 | > This is to represent the reserve address is used for the 32-byte hash. It is specified here so future iterations of the specification may allow other fields or syntaxes to reference other mutable field types.
72 | 4. `<hash type>` **MUST** be the multihash hash function type (as defined in https://multiformats.io/multihash/). Client implementations **MUST** support 'sha2-256' and **SHOULD** support future hash types when introduced by IPFS.
73 |
74 | > IPFS may add future versions of the cid spec, and add additional multicodec types or hash types.
75 |
76 | Implementations **SHOULD** use IPFS libraries where possible that accept multicodec and hash types as named values and allow a CID to be composed generically.
77 |
78 | ### Examples
79 |
80 | > This whole section is non-normative.
81 |
82 | * ASA URL: `template-ipfs://{ipfscid:0:dag-pb:reserve:sha2-256}/arc3.json`
83 | * ASA URL: `template-ipfs://{ipfscid:1:raw:reserve:sha2-256}`
84 | * ASA URL: `template-ipfs://{ipfscid:1:dag-pb:reserve:sha2-256}/metadata.json`
85 |
86 | #### Deployed Testnet Example
87 |
88 | An example was pushed to TestNet, converting from an existing ARC-3 MainNet ASA (asset ID 560421434, https://algoexplorer.io/asset/560421434)
89 |
90 | With IPFS URL:
91 | ```
92 | ipfs://QmQZyq4b89RfaUw8GESPd2re4hJqB8bnm4kVHNtyQrHnnK
93 | ```
94 |
95 | The TestNet ASA was minted with the URL:
96 | ```
97 | template-ipfs://{ipfscid:0:dag-pb:reserve:sha2-256}
98 | ```
99 | as the original CID is a V0 / dag-pb CID.
100 |
101 | A helpful link to 'visualize' CIDs and for this specific id, is https://cid.ipfs.io/#QmQZyq4b89RfaUw8GESPd2re4hJqB8bnm4kVHNtyQrHnnK
102 |
103 | Using the example encoding implementation, results in virtual 'reserve address' of
104 | ```
105 | EEQYWGGBHRDAMTEVDPVOSDVX3HJQIG6K6IVNR3RXHYOHV64ZWAEISS4CTI
106 | ```
107 | which is the address (with checksum) corresponding to the 32-byte with hexadecimal value:
108 | ```
109 | 21218B18C13C46064C951BEAE90EB7D9D3041BCAF22AD8EE373E1C7AFB99B008
110 | ```
111 | (Transformation from a 32-byte public key to an address can be found there on the developer website https://developer.algorand.org/docs/get-details/accounts/#transformation-public-key-to-algorand-address.)
112 |
113 | The resulting ASA can be seen on https://testnet.algoexplorer.io/asset/66753108
114 |
115 | Using the forked <a href="https://github.com/TxnLab/arc3.xyz">repo</a>, with testnet selected, and the /nft/66753108 url - the browser will display the original content as-is, using only the Reserve address as the source of the content hash.
116 |
117 | ### Interactions with ARC-3
118 |
119 | This ARC is compatible with [ARC-3](./arc-0003.md) with the following notable exception:
120 | the ASA Metadata Hash (`am`) is no more necessarily a valid hash of the JSON Metadata File pointed by the URL.
121 |
122 | As such, clients cannot be strictly compatible to both ARC-3 and [ARC-19](./arc-0019.md). An ARC-3 and ARC-19 client **SHOULD** ignore validation of the ASA Metadata Hash when the Asset URL is following ARC-19.
123 |
124 | ARC-3 clients **SHOULD** clearly indicate to the user when displaying an ARC-19 ASA, as contrary to a strict ARC-3 ASA, the asset may arbitrarily change over time (even after being bought).
125 |
126 | ASA that follow both ARC-3 and ARC-19 **MUST NOT** use extra metadata hash (from ARC-3).
127 |
128 | ## Rationale
129 |
130 | See the motivation section above for the general rationale.
131 |
132 | ### Backwards Compatibility
133 |
134 | The 'template-' prefix of the scheme is intended to break clients reading these ASA URLs outright. Clients interpreting these URLs as-is would likely yield unusual errors. Code checking for an explicit 'ipfs' scheme for example will not see this as compatible with any of the default processing and should treat the URL as if it were simply unknown/empty.
135 |
136 | ## Reference Implementation
137 |
138 | ### Encoding
139 |
140 | #### Go implementation
141 |
142 | ```go
143 | import (
144 | "github.com/algorand/go-algorand-sdk/types"
145 | "github.com/ipfs/go-cid"
146 | "github.com/multiformats/go-multihash"
147 | )
148 | // ...
149 | func ReserveAddressFromCID(cidToEncode cid.Cid) (string, error) {
150 | decodedMultiHash, err := multihash.Decode(cidToEncode.Hash())
151 | if err != nil {
152 | return "", fmt.Errorf("failed to decode ipfs cid: %w", err))
153 | }
154 | return types.EncodeAddress(decodedMultiHash.Digest)
155 | }
156 | // ....
157 | ```
158 |
159 | ### Decoding
160 |
161 | #### Go implementation
162 |
163 | ```go
164 | import (
165 | "errors"
166 | "fmt"
167 | "regexp"
168 | "strings"
169 |
170 | "github.com/algorand/go-algorand-sdk/types"
171 | "github.com/ipfs/go-cid"
172 | "github.com/multiformats/go-multicodec"
173 | "github.com/multiformats/go-multihash"
174 | )
175 |
176 | var (
177 | ErrUnknownSpec = errors.New("unsupported template-ipfs spec")
178 | ErrUnsupportedField = errors.New("unsupported ipfscid field, only reserve is currently supported")
179 | ErrUnsupportedCodec = errors.New("unknown multicodec type in ipfscid spec")
180 | ErrUnsupportedHash = errors.New("unknown hash type in ipfscid spec")
181 | ErrInvalidV0 = errors.New("cid v0 must always be dag-pb and sha2-256 codec/hash type")
182 | ErrHashEncoding = errors.New("error encoding new hash")
183 | templateIPFSRegexp = regexp.MustCompile(`template-ipfs://{ipfscid:(?P<version>[01]):(?P<codec>[a-z0-9\-]+):(?P<field>[a-z0-9\-]+):(?P<hash>[a-z0-9\-]+)}`)
184 | )
185 |
186 | func ParseASAUrl(asaUrl string, reserveAddress types.Address) (string, error) {
187 | matches := templateIPFSRegexp.FindStringSubmatch(asaUrl)
188 | if matches == nil {
189 | if strings.HasPrefix(asaUrl, "template-ipfs://") {
190 | return "", ErrUnknownSpec
191 | }
192 | return asaUrl, nil
193 | }
194 | if matches[templateIPFSRegexp.SubexpIndex("field")] != "reserve" {
195 | return "", ErrUnsupportedField
196 | }
197 | var (
198 | codec multicodec.Code
199 | multihashType uint64
200 | hash []byte
201 | err error
202 | cidResult cid.Cid
203 | )
204 | if err = codec.Set(matches[templateIPFSRegexp.SubexpIndex("codec")]); err != nil {
205 | return "", ErrUnsupportedCodec
206 | }
207 | multihashType = multihash.Names[matches[templateIPFSRegexp.SubexpIndex("hash")]]
208 | if multihashType == 0 {
209 | return "", ErrUnsupportedHash
210 | }
211 |
212 | hash, err = multihash.Encode(reserveAddress[:], multihashType)
213 | if err != nil {
214 | return "", ErrHashEncoding
215 | }
216 | if matches[templateIPFSRegexp.SubexpIndex("version")] == "0" {
217 | if codec != multicodec.DagPb {
218 | return "", ErrInvalidV0
219 | }
220 | if multihashType != multihash.SHA2_256 {
221 | return "", ErrInvalidV0
222 | }
223 | cidResult = cid.NewCidV0(hash)
224 | } else {
225 | cidResult = cid.NewCidV1(uint64(codec), hash)
226 | }
227 | return fmt.Sprintf("ipfs://%s", strings.ReplaceAll(asaUrl, matches[0], cidResult.String())), nil
228 | }
229 | ```
230 |
231 | #### Typescript Implementation
232 |
233 | A modified version of a simple ARC-3 viewer can be found <a href="https://github.com/TxnLab/arc3.xyz">here</a> specifically the code segment at <a href="https://github.com/TxnLab/arc3.xyz/blob/main/src/lib/nft.ts#L41">nft.ts#L41</a>
234 |
235 | This is a fork of <a href="https://github.com/barnjamin/arc3.xyz">ar3.xyz</a>
236 |
237 | ## Security Considerations
238 |
239 | There should be no specific security issues beyond those of any client accessing any remote content and the risks linked to assets changing (even after the ASA is bought).
240 |
241 | The later is handled in the section "Interactions with ARC-3" above.
242 |
243 | Regarding the former, URLs within ASAs could point to malicious content, whether that is an http/https link or whether fetched through ipfs protocols or ipfs gateways. As the template changes nothing other than the resulting URL and also defines nothing more than the generation of an IPFS CID hash value, no security concerns derived from this specific proposal are known.
244 |
245 | ## Copyright
246 | Copyright and related rights waived via <a href="https://creativecommons.org/publicdomain/zero/1.0/">CCO</a>.
247 |
```
--------------------------------------------------------------------------------
/packages/server/tests/tools/transactionManager/appTransactionManager.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
2 | import { AppTransactionManager, appTransactionTools } from '../../../src/tools/transactionManager/appTransactions.js';
3 | import { algodClient } from '../../../src/algorand-client.js';
4 | import algosdk, { OnApplicationComplete } from 'algosdk';
5 |
6 | // Mock algosdk
7 | jest.mock('algosdk', () => ({
8 | makeApplicationCreateTxnFromObject: jest.fn(),
9 | makeApplicationUpdateTxnFromObject: jest.fn(),
10 | makeApplicationDeleteTxnFromObject: jest.fn(),
11 | makeApplicationOptInTxnFromObject: jest.fn(),
12 | makeApplicationCloseOutTxnFromObject: jest.fn(),
13 | makeApplicationClearStateTxnFromObject: jest.fn(),
14 | makeApplicationNoOpTxnFromObject: jest.fn(),
15 | OnApplicationComplete: {
16 | NoOpOC: 0,
17 | OptInOC: 1,
18 | CloseOutOC: 2,
19 | ClearStateOC: 3,
20 | UpdateApplicationOC: 4,
21 | DeleteApplicationOC: 5,
22 | },
23 | }));
24 |
25 | // Mock algodClient
26 | jest.mock('../../../src/algorand-client.js', () => ({
27 | algodClient: {
28 | getTransactionParams: jest.fn().mockReturnValue({
29 | do: jest.fn().mockResolvedValue({
30 | firstRound: 1000,
31 | lastRound: 2000,
32 | genesisHash: 'hash',
33 | genesisID: 'testnet-v1.0',
34 | fee: 1000,
35 | flatFee: true,
36 | }),
37 | }),
38 | },
39 | }));
40 |
41 | describe('AppTransactionManager', () => {
42 | const mockSuggestedParams = {
43 | firstRound: 1000,
44 | lastRound: 2000,
45 | genesisHash: 'hash',
46 | genesisID: 'testnet-v1.0',
47 | fee: 1000,
48 | flatFee: true,
49 | };
50 |
51 | beforeEach(() => {
52 | jest.clearAllMocks();
53 | (algodClient.getTransactionParams as jest.Mock)().do.mockResolvedValue(mockSuggestedParams);
54 | });
55 |
56 | describe('Tool Schemas', () => {
57 | it('should have valid tool schemas', () => {
58 | expect(appTransactionTools).toHaveLength(7);
59 | expect(appTransactionTools.map((t: { name: string }) => t.name)).toEqual([
60 | 'make_app_create_txn',
61 | 'make_app_update_txn',
62 | 'make_app_delete_txn',
63 | 'make_app_optin_txn',
64 | 'make_app_closeout_txn',
65 | 'make_app_clear_txn',
66 | 'make_app_call_txn',
67 | ]);
68 | });
69 | });
70 |
71 | describe('Application Creation', () => {
72 | const mockAppCreateTxn = { type: 'appl', from: 'sender' };
73 |
74 | beforeEach(() => {
75 | (algosdk.makeApplicationCreateTxnFromObject as jest.Mock).mockReturnValue(mockAppCreateTxn);
76 | });
77 |
78 | it('should create a basic application creation transaction', async () => {
79 | const args = {
80 | from: 'sender',
81 | approvalProgram: '#pragma version 6\nint 1',
82 | clearProgram: '#pragma version 6\nint 1',
83 | globalByteSlices: 1,
84 | globalInts: 1,
85 | localByteSlices: 1,
86 | localInts: 1,
87 | };
88 |
89 | const result = await AppTransactionManager.handleTool('make_app_create_txn', args);
90 |
91 | expect(result).toEqual({
92 | content: [{
93 | type: 'text',
94 | text: JSON.stringify(mockAppCreateTxn, null, 2),
95 | }],
96 | });
97 |
98 | expect(algosdk.makeApplicationCreateTxnFromObject).toHaveBeenCalledWith(
99 | expect.objectContaining({
100 | from: 'sender',
101 | approvalProgram: new TextEncoder().encode(args.approvalProgram),
102 | clearProgram: new TextEncoder().encode(args.clearProgram),
103 | globalByteSlices: 1,
104 | globalInts: 1,
105 | localByteSlices: 1,
106 | localInts: 1,
107 | onComplete: 0,
108 | suggestedParams: expect.objectContaining(mockSuggestedParams),
109 | })
110 | );
111 | });
112 |
113 | it('should create an application with optional parameters', async () => {
114 | const args = {
115 | from: 'sender',
116 | approvalProgram: '#pragma version 6\nint 1',
117 | clearProgram: '#pragma version 6\nint 1',
118 | globalByteSlices: 1,
119 | globalInts: 1,
120 | localByteSlices: 1,
121 | localInts: 1,
122 | extraPages: 1,
123 | note: 'test note',
124 | lease: 'test lease',
125 | rekeyTo: 'rekey-addr',
126 | appArgs: ['arg1', 'arg2'],
127 | accounts: ['acc1', 'acc2'],
128 | foreignApps: [1, 2],
129 | foreignAssets: [3, 4],
130 | };
131 |
132 | const result = await AppTransactionManager.handleTool('make_app_create_txn', args);
133 |
134 | expect(algosdk.makeApplicationCreateTxnFromObject).toHaveBeenCalledWith(
135 | expect.objectContaining({
136 | from: 'sender',
137 | approvalProgram: new TextEncoder().encode(args.approvalProgram),
138 | clearProgram: new TextEncoder().encode(args.clearProgram),
139 | globalByteSlices: 1,
140 | globalInts: 1,
141 | localByteSlices: 1,
142 | localInts: 1,
143 | extraPages: 1,
144 | note: new TextEncoder().encode('test note'),
145 | lease: new TextEncoder().encode('test lease'),
146 | rekeyTo: 'rekey-addr',
147 | appArgs: args.appArgs.map(arg => new TextEncoder().encode(arg)),
148 | accounts: ['acc1', 'acc2'],
149 | foreignApps: [1, 2],
150 | foreignAssets: [3, 4],
151 | onComplete: 0,
152 | suggestedParams: expect.objectContaining(mockSuggestedParams),
153 | })
154 | );
155 | });
156 | });
157 |
158 | describe('Application Update', () => {
159 | const mockAppUpdateTxn = { type: 'appl', from: 'sender' };
160 |
161 | beforeEach(() => {
162 | (algosdk.makeApplicationUpdateTxnFromObject as jest.Mock).mockReturnValue(mockAppUpdateTxn);
163 | });
164 |
165 | it('should create an application update transaction', async () => {
166 | const args = {
167 | from: 'sender',
168 | appID: 123,
169 | approvalProgram: '#pragma version 6\nint 1',
170 | clearProgram: '#pragma version 6\nint 1',
171 | };
172 |
173 | const result = await AppTransactionManager.handleTool('make_app_update_txn', args);
174 |
175 | expect(algosdk.makeApplicationUpdateTxnFromObject).toHaveBeenCalledWith(
176 | expect.objectContaining({
177 | from: 'sender',
178 | appID: 123,
179 | approvalProgram: new TextEncoder().encode(args.approvalProgram),
180 | clearProgram: new TextEncoder().encode(args.clearProgram),
181 | onComplete: 4,
182 | suggestedParams: expect.objectContaining(mockSuggestedParams),
183 | })
184 | );
185 | });
186 | });
187 |
188 | describe('Application Delete', () => {
189 | const mockAppDeleteTxn = { type: 'appl', from: 'sender' };
190 |
191 | beforeEach(() => {
192 | (algosdk.makeApplicationDeleteTxnFromObject as jest.Mock).mockReturnValue(mockAppDeleteTxn);
193 | });
194 |
195 | it('should create an application delete transaction', async () => {
196 | const args = {
197 | from: 'sender',
198 | appID: 123,
199 | };
200 |
201 | const result = await AppTransactionManager.handleTool('make_app_delete_txn', args);
202 |
203 | expect(algosdk.makeApplicationDeleteTxnFromObject).toHaveBeenCalledWith(
204 | expect.objectContaining({
205 | from: 'sender',
206 | appID: 123,
207 | onComplete: 5,
208 | suggestedParams: expect.objectContaining(mockSuggestedParams),
209 | })
210 | );
211 | });
212 | });
213 |
214 | describe('Application Opt-In', () => {
215 | const mockAppOptInTxn = { type: 'appl', from: 'sender' };
216 |
217 | beforeEach(() => {
218 | (algosdk.makeApplicationOptInTxnFromObject as jest.Mock).mockReturnValue(mockAppOptInTxn);
219 | });
220 |
221 | it('should create an application opt-in transaction', async () => {
222 | const args = {
223 | from: 'sender',
224 | appID: 123,
225 | };
226 |
227 | const result = await AppTransactionManager.handleTool('make_app_optin_txn', args);
228 |
229 | expect(algosdk.makeApplicationOptInTxnFromObject).toHaveBeenCalledWith(
230 | expect.objectContaining({
231 | from: 'sender',
232 | appID: 123,
233 | onComplete: 1,
234 | suggestedParams: expect.objectContaining(mockSuggestedParams),
235 | })
236 | );
237 | });
238 | });
239 |
240 | describe('Application Close-Out', () => {
241 | const mockAppCloseOutTxn = { type: 'appl', from: 'sender' };
242 |
243 | beforeEach(() => {
244 | (algosdk.makeApplicationCloseOutTxnFromObject as jest.Mock).mockReturnValue(mockAppCloseOutTxn);
245 | });
246 |
247 | it('should create an application close-out transaction', async () => {
248 | const args = {
249 | from: 'sender',
250 | appID: 123,
251 | };
252 |
253 | const result = await AppTransactionManager.handleTool('make_app_closeout_txn', args);
254 |
255 | expect(algosdk.makeApplicationCloseOutTxnFromObject).toHaveBeenCalledWith(
256 | expect.objectContaining({
257 | from: 'sender',
258 | appID: 123,
259 | onComplete: 2,
260 | suggestedParams: expect.objectContaining(mockSuggestedParams),
261 | })
262 | );
263 | });
264 | });
265 |
266 | describe('Application Clear State', () => {
267 | const mockAppClearTxn = { type: 'appl', from: 'sender' };
268 |
269 | beforeEach(() => {
270 | (algosdk.makeApplicationClearStateTxnFromObject as jest.Mock).mockReturnValue(mockAppClearTxn);
271 | });
272 |
273 | it('should create an application clear state transaction', async () => {
274 | const args = {
275 | from: 'sender',
276 | appID: 123,
277 | };
278 |
279 | const result = await AppTransactionManager.handleTool('make_app_clear_txn', args);
280 |
281 | expect(algosdk.makeApplicationClearStateTxnFromObject).toHaveBeenCalledWith(
282 | expect.objectContaining({
283 | from: 'sender',
284 | appID: 123,
285 | onComplete: 3,
286 | suggestedParams: expect.objectContaining(mockSuggestedParams),
287 | })
288 | );
289 | });
290 | });
291 |
292 | describe('Application Call', () => {
293 | const mockAppCallTxn = { type: 'appl', from: 'sender' };
294 |
295 | beforeEach(() => {
296 | (algosdk.makeApplicationNoOpTxnFromObject as jest.Mock).mockReturnValue(mockAppCallTxn);
297 | });
298 |
299 | it('should create an application call transaction', async () => {
300 | const args = {
301 | from: 'sender',
302 | appID: 123,
303 | appArgs: ['method', 'arg1'],
304 | accounts: ['acc1'],
305 | foreignApps: [1],
306 | foreignAssets: [2],
307 | };
308 |
309 | const result = await AppTransactionManager.handleTool('make_app_call_txn', args);
310 |
311 | expect(algosdk.makeApplicationNoOpTxnFromObject).toHaveBeenCalledWith(
312 | expect.objectContaining({
313 | from: 'sender',
314 | appID: 123,
315 | appArgs: args.appArgs.map(arg => new TextEncoder().encode(arg)),
316 | accounts: ['acc1'],
317 | foreignApps: [1],
318 | foreignAssets: [2],
319 | onComplete: 0,
320 | suggestedParams: expect.objectContaining(mockSuggestedParams),
321 | })
322 | );
323 | });
324 | });
325 |
326 | describe('Error Handling', () => {
327 | it('should throw error for unknown tool', async () => {
328 | await expect(AppTransactionManager.handleTool('unknown_tool', {}))
329 | .rejects
330 | .toThrow(new McpError(ErrorCode.MethodNotFound, 'Unknown application transaction tool: unknown_tool'));
331 | });
332 |
333 | it('should throw error for missing application creation parameters', async () => {
334 | await expect(AppTransactionManager.handleTool('make_app_create_txn', {}))
335 | .rejects
336 | .toThrow(new McpError(ErrorCode.InvalidParams, 'Invalid application creation parameters'));
337 | });
338 |
339 | it('should throw error for missing application update parameters', async () => {
340 | await expect(AppTransactionManager.handleTool('make_app_update_txn', {}))
341 | .rejects
342 | .toThrow(new McpError(ErrorCode.InvalidParams, 'Invalid application update parameters'));
343 | });
344 |
345 | it('should throw error for missing application delete parameters', async () => {
346 | await expect(AppTransactionManager.handleTool('make_app_delete_txn', {}))
347 | .rejects
348 | .toThrow(new McpError(ErrorCode.InvalidParams, 'Invalid application delete parameters'));
349 | });
350 |
351 | it('should throw error for missing application opt-in parameters', async () => {
352 | await expect(AppTransactionManager.handleTool('make_app_optin_txn', {}))
353 | .rejects
354 | .toThrow(new McpError(ErrorCode.InvalidParams, 'Invalid application opt-in parameters'));
355 | });
356 |
357 | it('should throw error for missing application close-out parameters', async () => {
358 | await expect(AppTransactionManager.handleTool('make_app_closeout_txn', {}))
359 | .rejects
360 | .toThrow(new McpError(ErrorCode.InvalidParams, 'Invalid application close out parameters'));
361 | });
362 |
363 | it('should throw error for missing application clear state parameters', async () => {
364 | await expect(AppTransactionManager.handleTool('make_app_clear_txn', {}))
365 | .rejects
366 | .toThrow(new McpError(ErrorCode.InvalidParams, 'Invalid application clear state parameters'));
367 | });
368 |
369 | it('should throw error for missing application call parameters', async () => {
370 | await expect(AppTransactionManager.handleTool('make_app_call_txn', {}))
371 | .rejects
372 | .toThrow(new McpError(ErrorCode.InvalidParams, 'Invalid application call parameters'));
373 | });
374 |
375 | it('should handle algod client errors', async () => {
376 | const error = new Error('Network error');
377 | (algodClient.getTransactionParams as jest.Mock)().do.mockRejectedValue(error);
378 |
379 | await expect(AppTransactionManager.handleTool('make_app_create_txn', {
380 | from: 'sender',
381 | approvalProgram: '#pragma version 6\nint 1',
382 | clearProgram: '#pragma version 6\nint 1',
383 | globalByteSlices: 1,
384 | globalInts: 1,
385 | localByteSlices: 1,
386 | localInts: 1,
387 | }))
388 | .rejects
389 | .toThrow(error);
390 | });
391 | });
392 | });
393 |
```
--------------------------------------------------------------------------------
/packages/server/src/resources/knowledge/taxonomy/puya:docs:principles.md:
--------------------------------------------------------------------------------
```markdown
1 | # Principles & Background
2 | ## Background
3 |
4 | **Smart contracts** on the Algorand blockchain run on the Algorand Virtual Machine ([AVM](https://developer.algorand.org/docs/get-details/dapps/avm/)).
5 | This is a stack based virtual machine, which executes AVM bytecode as part of an [Application Call transaction](https://developer.algorand.org/docs/get-details/transactions/#application-call-transaction).
6 | The official mechanism for generating this bytecode is by submitting TEAL (Transaction Execution Approval Language) to
7 | an Algorand Node to compile.
8 |
9 | **Smart signatures** have the same basis in the AVM and TEAL, but have a different execution model, one not involving
10 | Application Call transactions. Our focus will primarily be on smart contracts, since they are strictly more powerful
11 | in terms of available AVM functions.
12 |
13 | TEAL is a [non-structured](https://en.wikipedia.org/wiki/Non-structured_programming)
14 | [imperative language](https://en.wikipedia.org/wiki/Procedural_programming#Imperative_programming)
15 | (albeit one with support for procedure calls that can isolate stack changes since v8 with `proto`). Writing TEAL is very
16 | similar to writing assembly code. It goes without saying that this is a particularly common or well-practiced model for
17 | programming these days.
18 |
19 | As it stands today, developers wanting to write smart contracts specifically for Algorand have the option of writing
20 | TEAL directly, or using some other mechanism of generating TEAL such as the officially supported [PyTEAL](https://pyteal.readthedocs.io/en/stable/)
21 | or the community supported [tealish](https://tealish.tinyman.org/en/latest/).
22 |
23 | PyTEAL follows a [generative programming](https://en.wikipedia.org/wiki/Automatic_programming#Generative_programming) paradigm,
24 | which is a form of metaprogramming. Naturally, writing programs to generate programs presents an additional hurdle for
25 | developers looking to pick up smart contract development. Tooling support for this is also suboptimal, for example, many
26 | classes of errors resulting from the interaction between the procedural elements of the Python language and the PyTEAL
27 | expression-building framework go unnoticed until the point of TEAL generation, or worse go completely unnoticed, and even
28 | when PyTEAL can/does provide an error it can be difficult to understand.
29 |
30 | Tealish provides a higher level procedural language, bearing a passing resemblance to Python, than compiles down to TEAL.
31 | However, it's still lower level than most developers are used to.
32 | For example, the expression `1 + 2 + 3`is [not valid in tealish](https://tealish.tinyman.org/en/latest/language.html#math-logic).
33 | Another difference vs a higher level language such as Python is that [functions can only be declared after the program
34 | entry point logic](https://tealish.tinyman.org/en/latest/language.html#functions).
35 | In essence, tealish abstracts away many difficulties with writing plain TEAL,
36 | but it is still essentially more of a transpiler than a compiler.
37 | Furthermore, whilst appearing to have syntax inspired by Python, it both adds and removes many fundamental syntax elements,
38 | presenting an additional learning curve to developers looking to learn blockchain development on Algorand.
39 | Being a bespoke language also means it has a much smaller ecosystem of tooling built around it compared to languages like
40 | Python or JavaScript.
41 |
42 | To most developers, the Python programming language needs no introduction. First released in 1991, it's popularity has
43 | grown steadily over the decades, and as of June 2023 it is consistently ranked as either the most popular langauge,
44 | or second most popular following JavaScript:
45 |
46 | - [GitHub 2022](https://octoverse.github.com/2022/top-programming-languages)
47 | - [StackOverflow 2023](https://stackoverflow.blog/2023/06/13/developer-survey-results-are-in/)
48 | - [Tiobe](https://www.tiobe.com/tiobe-index/)
49 | - [PYPL](https://pypl.github.io/PYPL.html)
50 |
51 | The AlgoKit project is an Algorand Foundation initiative to improve the developer experience on Algorand. Within this
52 | broad remit, two of the key [principles](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md#guiding-principles)
53 | are to "meet developers where they are" and "leverage existing ecosystem".
54 | Building a compiler that allows developers to write smart contracts using an idiomatic subset of a high level language
55 | such as Python would make great strides towards both of these goals.
56 |
57 | Wyvern was the original internal code name for just such a compiler (now called Puya), one that will transform Python code into valid TEAL
58 | smart contracts. In line with the principle of meeting developers where they are, and recognising the popularity of
59 | JavaScript and TypeScript, a parallel initiative to build a TypeScript to TEAL compiler is [also underway](https://tealscript.netlify.app).
60 |
61 | ## Principles
62 |
63 | The principles listed here should form the basis of our decision-making, both in the design and implementation.
64 |
65 | ### Least surprise
66 |
67 | Our primary objective is to assist developers in creating accurate smart contracts right from the
68 | start. The often immutable nature of these contracts - although not always the case - and the
69 | substantial financial value they frequently safeguard, underlines the importance of this goal.
70 |
71 | This principle ensures that the code behaves as anticipated by the developer. Specifically, if
72 | you're a Python developer writing Python smart contract code, you can expect the code to behave
73 | identically to its execution in a standard Python environment.
74 |
75 | Furthermore, we believe in promoting explicitness and correctness in contract code and its
76 | associated typing. This approach reduces potential errors and enhances the overall integrity of our
77 | smart contracts. Our commitment is to provide a user-friendly platform that aligns with the
78 | developer's intuition and experience, ultimately simplifying their work and minimizing the
79 | potential for mistakes.
80 |
81 | ### Inherited from AlgoKit
82 |
83 | As a part of the AlgoKit project, the principles outlined [there](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md#guiding-principles)
84 | also apply - to the extent that this project is just one component of AlgoKit.
85 |
86 | #### "Leverage existing ecosystem"
87 |
88 | > AlgoKit functionality gets into the hands of Algorand developers quickly by building on top of the
89 | > existing ecosystem wherever possible and aligned to these principles.
90 |
91 | In order to leverage as much existing Python tooling as possible, we should strive to maintain the highest level of
92 | compatibility with the Python language (and the reference implementation: CPython).
93 |
94 | #### "Meet developers where they are"
95 |
96 | > Make Blockchain development mainstream by giving all developers an idiomatic development experience in the operating
97 | > system, IDE and language they are comfortable with so they can dive in quickly and have less they need to learn before
98 | > being productive.
99 |
100 | Python is a very idiomatic language. We should embrace accepted patterns and practices as much as possible,
101 | such as those listed in [PEP-20](https://peps.python.org/pep-0020/) (aka "The Zen of Python").
102 |
103 | #### "Extensible"
104 |
105 | > Be extensible for community contribution rather than stifling innovation, bottle-necking all changes through the
106 | > Algorand Foundation and preventing the opportunity for other ecosystems being represented (e.g. Go, Rust, etc.).
107 | > This helps make developers feel welcome and is part of the developer experience, plus it makes it easier to add
108 | > features sustainably
109 |
110 | One way to support this principle in the broader AlgoKit context is by building in a mechanism for reusing
111 | common code between smart contracts, to allow the community to build their own Python packages.
112 |
113 | #### "Sustainable"
114 |
115 | > AlgoKit should be built in a flexible fashion with long-term maintenance in mind. Updates to latest patches in
116 | > dependencies, Algorand protocol development updates, and community contributions and feedback will all feed in to the
117 | > evolution of the software.
118 |
119 | Taking this principle further, ensuring the compiler is well-designed (e.g. frontend backend separation,
120 | with a well-thought-out IR) will help with maintaining and improving the implementation over time. For example,
121 | adding in new TEAL language features will be easier, same for implementing new optimisation strategies.
122 |
123 | Looking to the future, best practices for smart contract development are rapidly evolving. We shouldn't tie the
124 | implementation too tightly to a current standard such as ARC-4 - although in that specific example, we would still
125 | aim for first class support, but it shouldn't be assumed as the only way to write smart contracts.
126 |
127 |
128 | #### "Modular components"
129 |
130 | > Solution components should be modular and loosely coupled to facilitate efficient parallel development by small,
131 | > effective teams, reduced architectural complexity and allowing developers to pick and choose the specific tools and
132 | > capabilities they want to use based on their needs and what they are comfortable with.
133 |
134 | We will focus on the language and compiler design itself.
135 |
136 | An example of a very useful feature, that is strongly related but could be implemented separately instead,
137 | is the ability to run the users code in a unit-testing context, without compilation+deployment first.
138 | This would require implementing in Python some level of simulation of Algorand Nodes / AVM behaviour.
139 |
140 | #### "Secure by default"
141 |
142 | > Include defaults, patterns and tooling that help developers write secure code and reduce the likelihood of security
143 | > incidents in the Algorand ecosystem. This solution should help Algorand be the most secure Blockchain ecosystem.
144 |
145 | Enforcing security (which is multi-faceted) at a compiler level is difficult, and is some cases impossible.
146 | The best application of this principle here is to support auditing, which is important and nuanced enough to be
147 | listed below as a separate principle.
148 |
149 | #### "Cohesive developer tool suite" + "Seamless onramp"
150 |
151 | > Cohesive developer tool suite: Using AlgoKit should feel professional and cohesive, like it was designed to work
152 | > together, for the developer; not against them. Developers are guided towards delivering end-to-end, high quality
153 | > outcomes on MainNet so they and Algorand are more likely to be successful.
154 |
155 | > Seamless onramp: New developers have a seamless experience to get started and they are guided into a pit of success
156 | > with best practices, supported by great training collateral; you should be able to go from nothing to debugging code
157 | > in 5 minutes.
158 |
159 | These principles relate more to AlgoKit as a whole, so we can respect them by considering the impacts of our decisions
160 | there more broadly.
161 |
162 | ### Abstraction without obfuscation
163 |
164 | Algorand Python is a high level language, with support for things such as branching logic, operator precedence, etc.,
165 | and not a set of "macros" for generating TEAL. As such, developers will not be able to directly influence specific TEAL
166 | output, if this is desirable a language such as [Tealish](https://tealish.tinyman.org) is more appropriate.
167 |
168 | Whilst this will abstract away certain aspects of the underlying TEAL language, there are certain AVM concerns
169 | (such as op code budgets) that should not be abstracted away. That said, we should strive to generate code this is
170 | cost-effective and unsurprising. Python mechanisms such as dynamic (runtime) dispatch, and also many of its builtin
171 | functions on types such as `str` that are taken for granted, would require large amounts of ops compared to the
172 | Python code it represents.
173 |
174 | ### Support auditing
175 |
176 | Auditing is a critical part of the security process for deploying smart contracts. We want to support this function,
177 | and can do so in two ways:
178 |
179 | 1. By ensuring the same Python code as input generates identical output each time the compiler
180 | is run regardless of the system it's running on. This is what might be termed [Output stability](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/articles/output_stability.md).
181 | Ensuring a consistent output regardless of the system it's run on (assuming the same compiler version), means that
182 | auditing the lower level (ie TEAL) code is possible.
183 |
184 | 2. Although auditing the TEAL code should be possible, being able to easily identify and relate it back to the higher level
185 | code can make auditing the contract logic simpler and easier.
186 |
187 | ### Revolution, not evolution
188 |
189 | This is a new and groundbreaking way of developing for Algorand, and not a continuation of the PyTEAL/Beaker approach.
190 | By allowing developers to write procedural code, as opposed to constructing an expression tree,
191 | we can (among other things) significantly reduce the barrier to entry for developing smart contracts for the Algorand platform.
192 |
193 | Since the programming paradigm will be fundamentally different, providing a smooth migration experience from PyTEAL
194 | to this new world is not an intended goal, and shouldn't be a factor in our decisions. For example, it is not a goal of
195 | this project to produce a step-by-step "migrating from PyTEAL" document, as it is not a requirement for users to
196 | switch to this new paradigm in the short to medium term - support for PyTEAL should continue in parallel.
197 |
```