This is page 39 of 74. Use http://codebase.md/goplausible/algorand-mcp?lines=false&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/algokit:utils:typescript:capabilities:app.md:
--------------------------------------------------------------------------------
```markdown
# App management
App management is a higher-order use case capability provided by AlgoKit Utils that builds on top of the core capabilities. It allows you to create, update, delete, call (ABI and otherwise) smart contract apps and the metadata associated with them (including state and boxes).
## `AppManager`
The [`AppManager`](../code/classes/types_app_manager.AppManager.md) is a class that is used to manage app information.
To get an instance of `AppManager` you can use either [`AlgorandClient`](./algorand-client.md) via `algorand.app` or instantiate it directly (passing in an algod client instance):
```typescript
import { AppManager } from '@algorandfoundation/algokit-utils/types/app-manager'
const appManager = new AppManager(algod)
```
## Calling apps
### App Clients
The recommended way of interacting with apps is via [Typed app clients](./typed-app-clients.md) or if you can't use a typed app client then an [untyped app client](./app-client.md). The methods shown on this page are the underlying mechanisms that app clients use and are for advanced use cases when you want more control.
### Calling an app
When calling an app there are two types of transactions:
- Raw app transactions - Constructing a raw Algorand transaction to call the method; you have full control and are dealing with binary values directly
- ABI method calls - Constructing a call to an [ABI method](https://developer.algorand.org/docs/get-details/dapps/smart-contracts/ABI)
Calling an app involves providing some [common parameters](#common-app-parameters) and some parameters that will depend on the type of app call (create vs update vs other) per below sections.
When [sending transactions directly via AlgorandClient](./algorand-client.md#sending-a-single-transaction) the `SingleSendTransactionResult` return value is expanded with extra fields depending on the type of app call:
- All app calls extend [`SendAppTransactionResult`](../code/modules/types_transaction.md#sendapptransactionresult), which has:
- `return?: ABIReturn` - Which will contain an ABI return value if a non-void ABI method was called:
- `rawReturnValue: Uint8Array` - The raw binary of the return value
- `returnValue: ABIValue` - The decoded value in the appropriate JavaScript object
- `decodeError: Error` - If there was a decoding error the above 2 values will be `undefined` and this will have the error
- Update and create calls extend [`SendAppUpdateTransactionResult`](../code/modules/types_transaction.md#sendappupdatetransactionresult), which has:
- `compiledApproval: CompiledTeal | undefined` - The compilation result of approval, if approval program was supplied as a string and thus compiled by algod
- `compiledClear: CompiledTeal | undefined` - The compilation result of clear state, if clear state program was supplied as a string and thus compiled by algod
- Create calls extend [`SendAppCreateTransactionResult`](../code/modules/types_transaction.md#sendappcreatetransactionresult), which has:
- `appId: bigint` - The id of the created app
- `appAddress: string` - The Algorand address of the account associated with the app
There is a static method on [`AppManager`](#appmanager) that allows you to parse an ABI return value from an algod transaction confirmation:
```typescript
const confirmation = modelsv2.PendingTransactionResponse.from_obj_for_encoding(
await algod.pendingTransactionInformation(transactionId).do(),
)
const abiReturn = AppManager.getABIReturn(confirmation, abiMethod)
```
### Creation
To create an app via a raw app transaction you can use `algorand.send.appCreate(params)` (immediately send a single app creation transaction), `algorand.createTransaction.appCreate(params)` (construct an app creation transaction), or `algorand.newGroup().addAppCreate(params)` (add app creation to a group of transactions) per [`AlgorandClient`](./algorand-client.md) [transaction semantics](./algorand-client.md#creating-and-issuing-transactions).
To create an app via an ABI method call you can use `algorand.send.appCreateMethodCall(params)` (immediately send a single app creation transaction), `algorand.createTransaction.appCreateMethodCall(params)` (construct an app creation transaction), or `algorand.newGroup().addAppCreateMethodCall(params)` (add app creation to a group of transactions).
The base type for specifying an app creation transaction is [`AppCreateParams`](../code/modules/types_composer.md#appcreateparams) (extended as [`AppCreateMethodCall`](../code/modules/types_composer.md#appcreatemethodcall) for ABI method call version), which has the following parameters in addition to the [common parameters](#common-app-parameters):
- `onComplete?: Exclude<algosdk.OnApplicationComplete, algosdk.OnApplicationComplete.ClearStateOC>` - The on-completion action to specify for the call; defaults to NoOp and allows any on-completion apart from clear state.
- `approvalProgram: Uint8Array | string` - The program to execute for all OnCompletes other than ClearState as raw teal that will be compiled (string) or compiled teal (encoded as a byte array (Uint8Array)).
- `clearStateProgram: Uint8Array | string` - The program to execute for ClearState OnComplete as raw teal that will be compiled (string) or compiled teal (encoded as a byte array (Uint8Array)).
- `schema?` - The storage schema to request for the created app. This is immutable once the app is created. It is an object with:
- `globalInts: number` - The number of integers saved in global state.
- `globalByteSlices: number` - The number of byte slices saved in global state.
- `localInts: number` - The number of integers saved in local state.
- `localByteSlices: number` - The number of byte slices saved in local state.
- `extraProgramPages?: number` - Number of extra pages required for the programs. This is immutable once the app is created.
If you pass in `approvalProgram` or `clearStateProgram` as a string then it will automatically be compiled using Algod and the compilation result will be available via [`algorand.app.getCompilationResult`](../code/classes/types_app_manager.AppManager.md#getcompilationresult) (including the source map). To skip this behaviour you can pass in the compiled TEAL as `Uint8Array`.
```typescript
// Basic raw example
const result = await algorand.send.appCreate({ sender: 'CREATORADDRESS', approvalProgram: 'TEALCODE', clearStateProgram: 'TEALCODE' })
const createdAppId = result.appId
// Advanced raw example
await algorand.send.appCreate({
sender: 'CREATORADDRESS',
approvalProgram: "TEALCODE",
clearStateProgram: "TEALCODE",
schema: {
globalInts: 1,
globalByteSlices: 2,
localInts: 3,
localByteSlices: 4
},
extraProgramPages: 1,
onComplete: algosdk.OnApplicationComplete.OptInOC,
args: [new Uint8Array(1, 2, 3, 4)]
accountReferences: ["ACCOUNT_1"]
appReferences: [123n, 1234n]
assetReferences: [12345n]
boxReferences: ["box1", {appId: 1234n, name: "box2"}]
lease: 'lease',
note: 'note',
// You wouldn't normally set this field
firstValidRound: 1000n,
validityWindow: 10,
extraFee: (1000).microAlgo(),
staticFee: (1000).microAlgo(),
// Max fee doesn't make sense with extraFee AND staticFee
// already specified, but here for completeness
maxFee: (3000).microAlgo(),
// Signer only needed if you want to provide one,
// generally you'd register it with AlgorandClient
// against the sender and not need to pass it in
signer: transactionSigner,
maxRoundsToWaitForConfirmation: 5,
suppressLog: true,
})
// Basic ABI call example
const method = new ABIMethod({
name: 'method',
args: [{ name: 'arg1', type: 'string' }],
returns: { type: 'string' },
})
const result = await algorand.send.appCreateMethodCall({
sender: 'CREATORADDRESS',
approvalProgram: 'TEALCODE',
clearStateProgram: 'TEALCODE',
method: method,
args: ["arg1_value"]
})
const createdAppId = result.appId
```
### Updating
To update an app via a raw app transaction you can use `algorand.send.appUpdate(params)` (immediately send a single app update transaction), `algorand.createTransaction.appUpdate(params)` (construct an app update transaction), or `algorand.newGroup().addAppUpdate(params)` (add app update to a group of transactions) per [`AlgorandClient`](./algorand-client.md) [transaction semantics](./algorand-client.md#creating-and-issuing-transactions).
To create an app via an ABI method call you can use `algorand.send.appUpdateMethodCall(params)` (immediately send a single app update transaction), `algorand.createTransaction.appUpdateMethodCall(params)` (construct an app update transaction), or `algorand.newGroup().addAppUpdateMethodCall(params)` (add app update to a group of transactions).
The base type for specifying an app update transaction is [`AppUpdateParams`](../code/modules/types_composer.md#appupdateparams) (extended as [`AppUpdateMethodCall`](../code/modules/types_composer.md#appupdatemethodcall) for ABI method call version), which has the following parameters in addition to the [common parameters](#common-app-parameters):
- `onComplete?: algosdk.OnApplicationComplete.UpdateApplicationOC` - On Complete can either be omitted or set to update
- `approvalProgram: Uint8Array | string` - The program to execute for all OnCompletes other than ClearState as raw teal that will be compiled (string) or compiled teal (encoded as a byte array (Uint8Array)).
- `clearStateProgram: Uint8Array | string` - The program to execute for ClearState OnComplete as raw teal that will be compiled (string) or compiled teal (encoded as a byte array (Uint8Array)).
If you pass in `approvalProgram` or `clearStateProgram` as a string then it will automatically be compiled using Algod and the compilation result will be available via [`algorand.app.getCompilationResult`](../code/classes/types_app_manager.AppManager.md#getcompilationresult) (including the source map). To skip this behaviour you can pass in the compiled TEAL as `Uint8Array`.
```typescript
// Basic raw example
await algorand.send.appUpdate({ sender: 'SENDERADDRESS', approvalProgram: 'TEALCODE', clearStateProgram: 'TEALCODE' })
// Advanced raw example
await algorand.send.appUpdate({
sender: 'SENDERADDRESS',
approvalProgram: "TEALCODE",
clearStateProgram: "TEALCODE",
onComplete: algosdk.OnApplicationComplete.UpdateApplicationOC,
args: [new Uint8Array(1, 2, 3, 4)]
accountReferences: ["ACCOUNT_1"]
appReferences: [123n, 1234n]
assetReferences: [12345n]
boxReferences: ["box1", {appId: 1234n, name: "box2"}]
lease: 'lease',
note: 'note',
// You wouldn't normally set this field
firstValidRound: 1000n,
validityWindow: 10,
extraFee: (1000).microAlgo(),
staticFee: (1000).microAlgo(),
// Max fee doesn't make sense with extraFee AND staticFee
// already specified, but here for completeness
maxFee: (3000).microAlgo(),
// Signer only needed if you want to provide one,
// generally you'd register it with AlgorandClient
// against the sender and not need to pass it in
signer: transactionSigner,
maxRoundsToWaitForConfirmation: 5,
suppressLog: true,
})
// Basic ABI call example
const method = new ABIMethod({
name: 'method',
args: [{ name: 'arg1', type: 'string' }],
returns: { type: 'string' },
})
await algorand.send.appUpdateMethodCall({
sender: 'SENDERADDRESS',
approvalProgram: 'TEALCODE',
clearStateProgram: 'TEALCODE',
method: method,
args: ["arg1_value"]
})
```
### Deleting
To delete an app via a raw app transaction you can use `algorand.send.appDelete(params)` (immediately send a single app deletion transaction), `algorand.createTransaction.appDelete(params)` (construct an app deletion transaction), or `algorand.newGroup().addAppDelete(params)` (add app deletion to a group of transactions) per [`AlgorandClient`](./algorand-client.md) [transaction semantics](./algorand-client.md#creating-and-issuing-transactions).
To delete an app via an ABI method call you can use `algorand.send.appDeleteMethodCall(params)` (immediately send a single app deletion transaction), `algorand.createTransaction.appDeleteMethodCall(params)` (construct an app deletion transaction), or `algorand.newGroup().addAppDeleteMethodCall(params)` (add app deletion to a group of transactions).
The base type for specifying an app deletion transaction is [`AppDeleteParams`](../code/modules/types_composer.md#appdeleteparams) (extended as [`AppDeleteMethodCall`](../code/modules/types_composer.md#appdeletemethodcall) for ABI method call version), which has the following parameters in addition to the [common parameters](#common-app-parameters):
- `onComplete?: algosdk.OnApplicationComplete.DeleteApplicationOC` - On Complete can either be omitted or set to delete
```typescript
// Basic raw example
await algorand.send.appDelete({ sender: 'SENDERADDRESS' })
// Advanced raw example
await algorand.send.appDelete({
sender: 'SENDERADDRESS',
onComplete: algosdk.OnApplicationComplete.DeleteApplicationOC,
args: [new Uint8Array(1, 2, 3, 4)]
accountReferences: ["ACCOUNT_1"]
appReferences: [123n, 1234n]
assetReferences: [12345n]
boxReferences: ["box1", {appId: 1234n, name: "box2"}]
lease: 'lease',
note: 'note',
// You wouldn't normally set this field
firstValidRound: 1000n,
validityWindow: 10,
extraFee: (1000).microAlgo(),
staticFee: (1000).microAlgo(),
// Max fee doesn't make sense with extraFee AND staticFee
// already specified, but here for completeness
maxFee: (3000).microAlgo(),
// Signer only needed if you want to provide one,
// generally you'd register it with AlgorandClient
// against the sender and not need to pass it in
signer: transactionSigner,
maxRoundsToWaitForConfirmation: 5,
suppressLog: true,
})
// Basic ABI call example
const method = new ABIMethod({
name: 'method',
args: [{ name: 'arg1', type: 'string' }],
returns: { type: 'string' },
})
await algorand.send.appDeleteMethodCall({
sender: 'SENDERADDRESS',
method: method,
args: ["arg1_value"]
})
```
## Calling
To call an app via a raw app transaction you can use `algorand.send.appCall(params)` (immediately send a single app call transaction), `algorand.createTransaction.appCall(params)` (construct an app call transaction), or `algorand.newGroup().addAppCall(params)` (add app deletion to a group of transactions) per [`AlgorandClient`](./algorand-client.md) [transaction semantics](./algorand-client.md#creating-and-issuing-transactions).
To call an app via an ABI method call you can use `algorand.send.appCallMethodCall(params)` (immediately send a single app call transaction), `algorand.createTransaction.appCallMethodCall(params)` (construct an app call transaction), or `algorand.newGroup().addAppCallMethodCall(params)` (add app call to a group of transactions).
The base type for specifying an app call transaction is [`AppCallParams`](../code/modules/types_composer.md#appcallparams) (extended as [`AppCallMethodCall`](../code/modules/types_composer.md#appcallmethodcall) for ABI method call version), which has the following parameters in addition to the [common parameters](#common-app-parameters):
- `onComplete?: Exclude<algosdk.OnApplicationComplete, algosdk.OnApplicationComplete.UpdateApplicationOC>` - On Complete can either be omitted (which will result in no-op) or set to any on-complete apart from update
```typescript
// Basic raw example
await algorand.send.appCall({ sender: 'SENDERADDRESS' })
// Advanced raw example
await algorand.send.appCall({
sender: 'SENDERADDRESS',
onComplete: algosdk.OnApplicationComplete.OptInOC,
args: [new Uint8Array(1, 2, 3, 4)]
accountReferences: ["ACCOUNT_1"]
appReferences: [123n, 1234n]
assetReferences: [12345n]
boxReferences: ["box1", {appId: 1234n, name: "box2"}]
lease: 'lease',
note: 'note',
// You wouldn't normally set this field
firstValidRound: 1000n,
validityWindow: 10,
extraFee: (1000).microAlgo(),
staticFee: (1000).microAlgo(),
// Max fee doesn't make sense with extraFee AND staticFee
// already specified, but here for completeness
maxFee: (3000).microAlgo(),
// Signer only needed if you want to provide one,
// generally you'd register it with AlgorandClient
// against the sender and not need to pass it in
signer: transactionSigner,
maxRoundsToWaitForConfirmation: 5,
suppressLog: true,
})
// Basic ABI call example
const method = new ABIMethod({
name: 'method',
args: [{ name: 'arg1', type: 'string' }],
returns: { type: 'string' },
})
await algorand.send.appCallMethodCall({
sender: 'SENDERADDRESS',
method: method,
args: ["arg1_value"]
})
```
## Accessing state
### Global state
To access local state you can use the following method from an [`AppManager`](#appmanager) instance:
- [`algorand.app.getLocalState(appId, address)`](../code/classes/types_app_manager.AppManager.md#getlocalstate) - Returns the current local state for the given app ID and account address decoded into an object keyed by the UTF-8 representation of the state key with various parsed versions of the value (base64, UTF-8 and raw binary)
```typescript
const globalState = await algorand.app.getGlobalState(12345n)
```
Global state is parsed from the underlying algod response via the following static method from [`AppManager`](#appmanager):
- [`AppManager.decodeAppState(state)`](../code/classes/types_app_manager.AppManager.md#decodeappstate) - Takes the raw response from the algod API for global state and returns a friendly generic object keyed by the UTF-8 value of the key
```typescript
const globalAppState = /* value from algod */
const appState = AppManager.decodeAppState(globalAppState)
const keyAsBinary = appState['value1'].keyRaw
const keyAsBase64 = appState['value1'].keyBase64
if (typeof appState['value1'].value === 'string') {
const valueAsString = appState['value1'].value
const valueAsBinary = appState['value1'].valueRaw
const valueAsBase64 = appState['value1'].valueBase64
} else {
const valueAsNumberOrBigInt = appState['value1'].value
}
```
### Local state
To access local state you can use the following method from an [`AppManager`](#appmanager) instance:
- [`algorand.app.getLocalState(appId, address)`](../code/classes/types_app_manager.AppManager.md#getlocalstate) - Returns the current local state for the given app ID and account address decoded into an object keyed by the UTF-8 representation of the state key with various parsed versions of the value (base64, UTF-8 and raw binary)
```typescript
const localState = await algorand.app.getLocalState(12345n, 'ACCOUNTADDRESS')
```
### Boxes
To access and parse box values and names for an app you can use the following methods from an [`AppManager`](#appmanager) instance:
- [`algorand.app.getBoxNames(appId: bigint)`](../code/modules/index.md#getboxnames) - Returns the current [box names](#boxname) for the given app ID
- [`algorand.app.getBoxValue(appId: bigint, boxName: BoxIdentifier)`](../code/modules/index.md#getboxvalue) - Returns the binary value of the given box name for the given app ID
- [`algorand.app.getBoxValues(appId: bigint, boxNames: BoxIdentifier[])`](../code/modules/index.md#getboxvalues) - Returns the binary values of the given box names for the given app ID
- [`algorand.app.getBoxValueFromABIType(request: {appId: bigint, boxName: BoxIdentifier, type: algosdk.ABIType}})`](../code/modules/index.md#getboxvaluefromabitype) - Returns the parsed ABI value of the given box name for the given app ID for the provided ABI type
- [`algorand.app.getBoxValuesFromABIType(request: {appId: bigint, boxNames: BoxIdentifier[], type: algosdk.ABIType})`](../code/modules/index.md#getboxvaluesfromabitype) - Returns the parsed ABI values of the given box names for the given app ID for the provided ABI type
- [`AppManager.getBoxReference(boxId)`](../code/modules/index.md#getboxreference) - Returns a `algosdk.BoxReference` representation of the given [box identifier / reference](#box-references), which is useful when constructing a raw `algosdk.Transaction`
```typescript
const appId = 12345n
const boxName: BoxReference = 'my-box'
const boxName2: BoxReference = 'my-box2'
const boxNames = algorand.app.getBoxNames(appId)
const boxValue = algorand.app.getBoxValue(appId, boxName)
const boxValues = algorand.app.getBoxValues(appId, [boxName, boxName2])
const boxABIValue = algorand.app.getBoxValueFromABIType(appId, boxName, algosdk.ABIStringType)
const boxABIValues = algorand.app.getBoxValuesFromABIType(appId, [boxName, boxName2], algosdk.ABIStringType)
```
## Getting app information
To get reference information and metadata about an existing app you can use the following methods:
- [`algorand.app.getById(appId)`](../code/classes/types_app_manager.AppManager.md#getbyid) - Returns current app information by app ID from an [`AppManager`](#appmanager) instance
- [`indexer.lookupAccountCreatedApplicationByAddress(indexer, address, getAll?, paginationLimit?)`](../code/modules/index.indexer.md#lookupaccountcreatedapplicationbyaddress) - Returns all apps created by a given account from [indexer](./indexer.md)
## Common app parameters
When interacting with apps (creating, updating, deleting, calling), there are some [`CommonAppCallParams`](../code/modules/types_composer.md#commonappcallparams) that you will be able to pass in to all calls in addition to the [common transaction parameters](./algorand-client.md#transaction-parameters):
- `appId: bigint` - ID of the application; only specified if the application is not being created.
- `onComplete?: algosdk.OnApplicationComplete` - The [on-complete](https://developer.algorand.org/docs/get-details/dapps/avm/teal/specification/#oncomplete) action of the call (noting each call type will have restrictions that affect this value).
- `args?: Uint8Array[]` - Any [arguments to pass to the smart contract call](https://developer.algorand.org/docs/get-details/dapps/avm/teal/#argument-passing).
- `accountReferences?: string[]` - Any account addresses to add to the [accounts array](https://developer.algorand.org/docs/get-details/dapps/smart-contracts/apps/#reference-arrays).
- `appReferences?: bigint[]` - The ID of any apps to load to the [foreign apps array](https://developer.algorand.org/docs/get-details/dapps/smart-contracts/apps/#reference-arrays).
- `assetReferences?: bigint[]` - The ID of any assets to load to the [foreign assets array](https://developer.algorand.org/docs/get-details/dapps/smart-contracts/apps/#reference-arrays).
- `boxReferences?: (BoxReference | BoxIdentifier)[]` - Any [boxes](#box-references) to load to the [boxes array](https://developer.algorand.org/docs/get-details/dapps/smart-contracts/apps/#reference-arrays)
When making an ABI call, the `args` parameter is replaced with a different type and there is also a `method` parameter per the [`AppMethodCall`](../code/modules/types_composer.md#appmethodcall) type:
- `method: algosdk.ABIMethod`
- `args: ABIArgument[]` The arguments to pass to the ABI call, which can be one of:
- `algosdk.ABIValue` - Which can be one of:
- `boolean`
- `number`
- `bigint`
- `string`
- `Uint8Array`
- An array of one of the above types
- `algosdk.TransactionWithSigner`
- `algosdk.Transaction`
- `Promise<Transaction>` - which allows you to use an AlgorandClient call that [returns a transaction](./algorand-client.md#creating-single-transactions) without needing to await the call
- `AppMethodCall` - parameters that define another (nested) ABI method call, which will in turn get resolved to one or more transactions
## Box references
Referencing boxes can by done by either `BoxIdentifier` (which identifies the name of the box and app ID `0` will be used (i.e. the current app)) or `BoxReference`:
```typescript
/**
* Something that identifies an app box name - either a:
* * `Uint8Array` (the actual binary of the box name)
* * `string` (that will be encoded to a `Uint8Array`)
* * `TransactionSignerAccount` (that will be encoded into the
* public key address of the corresponding account)
*/
export type BoxIdentifier = string | Uint8Array | TransactionSignerAccount
/**
* A grouping of the app ID and name identifier to reference an app box.
*/
export interface BoxReference {
/**
* A unique application id
*/
appId: bigint
/**
* Identifier for a box name
*/
name: BoxIdentifier
}
```
## Compilation
The [`AppManager`](#appmanager) class allows you to compile TEAL code with caching semantics that allows you to avoid duplicate compilation and keep track of source maps from compiled code.
If you call `algorand.app.compileTeal(tealCode)` then the compilation result will be stored and retrievable from `algorand.app.getCompilationResult(tealCode)`.
```typescript
const tealCode = 'return 1'
const compilationResult = await algorand.app.compileTeal(tealCode)
// ...
const previousCompilationResult = algorand.app.getCompilationResult(tealCode)
```
```
--------------------------------------------------------------------------------
/packages/server/src/resources/knowledge/taxonomy/algokit:utils:typescript:capabilities:app-client.md:
--------------------------------------------------------------------------------
```markdown
# App client and App factory
> [!NOTE]
> This page covers the untyped app client, but we recommend using [typed clients](./typed-app-clients.md), which will give you a better developer experience with strong typing and intellisense specific to the app itself.
App client and App factory are higher-order use case capabilities provided by AlgoKit Utils that builds on top of the core capabilities, particularly [App deployment](./app-deploy.md) and [App management](./app.md). They allow you to access high productivity application clients that work with [ARC-56](https://github.com/algorandfoundation/ARCs/pull/258) and [ARC-32](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0032.md) application spec defined smart contracts, which you can use to create, update, delete, deploy and call a smart contract and access state data for it.
> ![NOTE]
>
> If you are confused about when to use the factory vs client the mental model is: use the client if you know the app ID, use the factory if you don't know the app ID (deferred knowledge or the instance doesn't exist yet on the blockchain) or you have multiple app IDs
## `AppFactory`
The [`AppFactory`](../code/classes/types_app_factory.AppFactory.md) is a class that, for a given app spec, allows you to create and deploy one or more app instances and to create one or more app clients to interact with those (or other) app instances.
To get an instance of `AppFactory` you can use either [`AlgorandClient`](./algorand-client.md) via `algorand.client.getAppFactory` or instantiate it directly (passing in an app spec, an `AlgorandClient` instance and other optional parameters):
```typescript
// Minimal example
const factory = algorand.client.getAppFactory({
appSpec: '{/* ARC-56 or ARC-32 compatible JSON */}',
})
// Advanced example
const factory = algorand.client.getAppFactory({
appSpec: parsedArc32OrArc56AppSpec,
defaultSender: 'SENDERADDRESS',
appName: 'OverriddenAppName',
version: '2.0.0',
updatable: true,
deletable: false,
deployTimeParams: { ONE: 1, TWO: 'value' },
})
```
## `AppClient`
The [`AppClient`](../code/classes/types_app_client.AppClient.md) is a class that, for a given app spec, allows you to manage calls and state for a specific deployed instance of an app (with a known app ID).
To get an instance of `AppClient` you can use either [`AlgorandClient`](./algorand-client.md) via `algorand.client.getAppClient*` or instantiate it directly (passing in an app ID, app spec, `AlgorandClient` instance and other optional parameters):
```typescript
// Minimal examples
const appClient = algorand.client.getAppClientByCreatorAndName({
appSpec: '{/* ARC-56 or ARC-32 compatible JSON */}',
// appId resolved by looking for app ID of named app by this creator
creatorAddress: 'CREATORADDRESS',
})
const appClient = algorand.client.getAppClientById({
appSpec: '{/* ARC-56 or ARC-32 compatible JSON */}',
appId: 12345n,
})
const appClient = algorand.client.getAppClientByNetwork({
appSpec: '{/* ARC-56 or ARC-32 compatible JSON */}',
// appId resolved by using ARC-56 spec to find app ID for current network
})
// Advanced example
const appClient = algorand.client.getAppClientById({
appSpec: parsedAppSpec_AppSpec_or_Arc56Contract,
appId: 12345n,
appName: 'OverriddenAppName',
defaultSender: 'SENDERADDRESS',
approvalSourceMap: approvalTealSourceMap,
clearSourceMap: clearTealSourceMap,
})
```
You can get the `appId` and `appAddress` at any time as properties on the `AppClient` along with `appName` and `appSpec`.
## Dynamically creating clients for a given app spec
As well as allowing you to control creation and deployment of apps, the `AppFactory` allows you to conveniently create multiple `AppClient` instances on-the-fly with information pre-populated.
This is possible via two methods on the app factory:
- [`factory.getAppClientById(params)`](../code/classes/types_app_factory.AppFactory.md#getappclientbyid) - Returns a new `AppClient` client for an app instance of the given ID. Automatically populates appName, defaultSender and source maps from the factory if not specified in the params.
- [`factory.getAppClientByCreatorAndName(params)`](../code/classes/types_app_factory.AppFactory.md#getappclientbycreatorandname) - Returns a new `AppClient` client, resolving the app by creator address and name using AlgoKit app deployment semantics (i.e. looking for the app creation transaction note). Automatically populates appName, defaultSender and source maps from the factory if not specified in the params.
```typescript
const appClient1 = factory.getAppClientById({ appId: 12345n })
const appClient2 = factory.getAppClientById({ appId: 12346n })
const appClient3 = factory.getAppClientById({ appId: 12345n, defaultSender: 'SENDER2ADDRESS' })
const appClient4 = factory.getAppClientByCreatorAndName({
creatorAddress: 'CREATORADDRESS',
})
const appClient5 = factory.getAppClientByCreatorAndName({
creatorAddress: 'CREATORADDRESS',
appName: 'NonDefaultAppName',
})
const appClient6 = factory.getAppClientByCreatorAndName({
creatorAddress: 'CREATORADDRESS',
appName: 'NonDefaultAppName',
ignoreCache: true, // Perform fresh indexer lookups
defaultSender: 'SENDER2ADDRESS',
})
```
## Creating and deploying an app
Once you have an [app factory](#appfactory) you can perform the following actions:
- [`factory.create(params?)`](../code/classes/types_app_factory.AppFactory.md#create) - Signs and sends a transaction to create an app and returns the [result of that call](./app.md#creation) and an [`AppClient`](#appclient) instance for the created app
- [`factory.deploy(params)`](../code/classes/types_app_factory.AppFactory.md#deploy) - Uses the [creator address and app name pattern](./app-deploy.md#lookup-deployed-apps-by-name) to find if the app has already been deployed or not and either creates, updates or replaces that app based on the [deployment rules](./app-deploy.md#performing-a-deployment) (i.e. it's an idempotent deployment) and returns the [result of the deployment](./app-deploy.md#return-value) and an [`AppClient`](#appclient) instance for the created/updated/existing app
### Create
The create method is a wrapper over the `appCreate` (bare calls) and `appCreateMethodCall` (ABI method calls) [methods](./app.md#creation), with the following differences:
- You don't need to specify the `approvalProgram`, `clearStateProgram`, or `schema` because these are all specified or calculated from the app spec (noting you can override the `schema`)
- `sender` is optional and if not specified then the `defaultSender` from the `AppFactory` constructor is used (if it was specified, otherwise an error is thrown)
- `deployTimeParams`, `updatable` and `deletable` can be passed in to control [deploy-time parameter replacements and deploy-time immutability and permanence control](./app-deploy.md#compilation-and-template-substitution); these values can also be passed into the `AppFactory` constructor instead and if so will be used if not defined in the params to the create call
```typescript
// Use no-argument bare-call
const { result, appClient } = factory.send.bare.create()
// Specify parameters for bare-call and override other parameters
const { result, appClient } = factory.send.bare.create({
args: [new Uint8Array(1, 2, 3, 4)],
staticFee: (3000).microAlgo(),
onComplete: algosdk.OnApplicationComplete.OptIn,
deployTimeParams: {
ONE: 1,
TWO: 'two',
},
updatable: true,
deletable: false,
populateAppCallResources: true,
})
// Specify parameters for ABI method call
const { result, appClient } = factory.send.create({
method: 'create_application',
args: [1, 'something'],
})
```
If you want to construct a custom create call, use the underlying [`algorand.send.appCreate` / `algorand.createTransaction.appCreate` / `algorand.send.appCreateMethodCall` / `algorand.createTransaction.appCreateMethodCall` methods](./app.md#creation) then you can get params objects:
- `factory.params.create(params)` - ABI method create call for deploy method or an underlying [`appCreateMethodCall` call](./app.md#creation)
- `factory.params.bare.create(params)` - Bare create call for deploy method or an underlying [`appCreate` call](./app.md#creation)
### Deploy
The deploy method is a wrapper over the [`AppDeployer`'s `deploy` method](./app-deploy.md#performing-a-deployment), with the following differences:
- You don't need to specify the `approvalProgram`, `clearStateProgram`, or `schema` in the `createParams` because these are all specified or calculated from the app spec (noting you can override the `schema`)
- `sender` is optional for `createParams`, `updateParams` and `deleteParams` and if not specified then the `defaultSender` from the `AppFactory` constructor is used (if it was specified, otherwise an error is thrown)
- You don't need to pass in `metadata` to the deploy params - it's calculated from:
- `updatable` and `deletable`, which you can optionally pass in directly to the method params
- `version` and `name`, which are optionally passed into the `AppFactory` constructor
- `deployTimeParams`, `updatable` and `deletable` can all be passed into the `AppFactory` and if so will be used if not defined in the params to the deploy call for the [deploy-time parameter replacements and deploy-time immutability and permanence control](./app-deploy.md#compilation-and-template-substitution)
- `createParams`, `updateParams` and `deleteParams` are optional, if they aren't specified then default values are used for everything and a no-argument bare call will be made for any create/update/delete calls
- If you want to call an ABI method for create/update/delete calls then you can pass in a string for `method` (as opposed to an `ABIMethod` object), which can either be the method name, or if you need to disambiguate between multiple methods of the same name it can be the ABI signature (see example below)
```typescript
// Use no-argument bare-calls to deploy with default behaviour
// for when update or schema break detected (fail the deployment)
const { result, appClient } = factory.deploy({})
// Specify parameters for bare-calls and override the schema break behaviour
const { result, appClient } = factory.deploy({
createParams: {
args: [new Uint8Array(1, 2, 3, 4)],
staticFee: (3000).microAlgo(),
onComplete: algosdk.OnApplicationComplete.OptIn:
},
updateParams: {
args: [new Uint8Array(1, 2, 3)],
},
deleteParams: {
args: [new Uint8Array(1, 2)],
},
deployTimeParams: {
ONE: 1,
TWO: 'two',
},
onUpdate: 'update',
onSchemaBreak: 'replace',
updatable: true,
deletable: true,
})
// Specify parameters for ABI method calls
const { result, appClient } = factory.deploy({
createParams: {
method: "create_application",
args: [1, "something"],
},
updateParams: {
method: "update",
},
deleteParams: {
method: "delete_app(uint64,uint64,uint64)uint64",
args: [1, 2, 3]
}
})
```
If you want to construct a custom deploy call, use the underlying [`algorand.appDeployer.deploy` method](./app-deploy.md#performing-a-deployment) then you can get params objects for the `createParams`, `updateParams` and `deleteParams`:
- `factory.params.create(params)` - ABI method create call for deploy method or an underlying [`appCreateMethodCall` call](./app.md#creation)
- `factory.params.deployUpdate(params)` - ABI method update call for deploy method
- `factory.params.deployDelete(params)` - ABI method delete call for deploy method
- `factory.params.bare.create(params)` - Bare create call for deploy method or an underlying [`appCreate` call](./app.md#creation)
- `factory.params.bare.deployUpdate(params)` - Bare update call for deploy method
- `factory.params.bare.deployDelete(params)` - Bare delete call for deploy method
## Updating and deleting an app
Deploy method aside, the ability to make update and delete calls happens after there is an instance of an app so are done via `AppClient`. The semantics of this are no different than [other calls](#calling-the-app), with the caveat that the update call is a bit different to the others since the code will be compiled when constructing the update params (making it an async method) and the update calls thus optionally takes compilation parameters (`deployTimeParams`, `updatable` and `deletable`) for [deploy-time parameter replacements and deploy-time immutability and permanence control](./app-deploy.md#compilation-and-template-substitution).
## Calling the app
You can construct a params object, transaction(s) and sign and send a transaction to call the app that a given `AppClient` instance is pointing to.
This is done via the following properties:
- `appClient.params.{onComplete}(params)` - Params for an ABI method call
- `appClient.params.bare.{onComplete}(params)` - Params for a bare call
- `appClient.createTransaction.{onComplete}(params)` - Transaction(s) for an ABI method call
- `appClient.createTransaction.bare.{onComplete}(params)` - Transaction for a bare call
- `appClient.send.{onComplete}(params)` - Sign and send an ABI method call
- `appClient.send.bare.{onComplete}(params)` - Sign and send a bare call
To make one of these calls `{onComplete}` needs to be swapped with the [on complete action](https://developer.algorand.org/docs/get-details/dapps/smart-contracts/apps/#the-lifecycle-of-a-smart-contract) that should be made:
- `update` - An update call
- `optIn` - An opt-in call
- `delete` - A delete application call
- `clearState` - A clear state call (note: calls the clear program and only applies to bare calls)
- `closeOut` - A close-out call
- `call` - A no-op call (or other call if `onComplete` is specified to anything other than update)
The input payload for all of these calls is the same as the [underlying app methods](./app.md#calling-apps) with the caveat that the `appId` is not passed in (since the `AppClient` already knows the app ID), `sender` is optional (it uses `defaultSender` from the `AppClient` constructor if it was specified) and `method` (for ABI method calls) is a string rather than an `ABIMethod` object (which can either be the method name, or if you need to disambiguate between multiple methods of the same name it can be the ABI signature).
The return payload for all of these is the same as the [underlying methods](./app.md#calling-apps).
```typescript
const call1 = await appClient.send.update({
method: 'update_abi',
args: ['string_io'],
deployTimeParams,
})
const call2 = await appClient.send.delete({
method: 'delete_abi',
args: ['string_io'],
})
const call3 = await appClient.send.optIn({ method: 'opt_in' })
const call4 = await appClient.send.bare.clearState()
const transaction = await appClient.createTransaction.bare.closeOut({
args: [new Uint8Array(1, 2, 3)],
})
const params = appClient.params.optIn({ method: 'optin' })
```
### Nested ABI Method Call Transactions
The ARC4 ABI specification supports ABI method calls as arguments to other ABI method calls, enabling some interesting use cases. While this conceptually resembles a function call hierarchy, in practice, the transactions are organized as a flat, ordered transaction group. Unfortunately, this logically hierarchical structure cannot always be correctly represented as a flat transaction group, making some scenarios impossible.
To illustrate this, let's consider an example of two ABI methods with the following signatures:
- `myMethod(pay,appl)void`
- `myOtherMethod(pay)void`
These signatures are compatible, so `myOtherMethod` can be passed as an ABI method call argument to `myMethod`, which would look like:
Hierarchical method call
```
myMethod(pay, myOtherMethod(pay))
```
Flat transaction group
```
pay (pay)
appl (myOtherMethod)
appl (myMethod)
```
An important limitation to note is that the flat transaction group representation does not allow having two different pay transactions. This invariant is represented in the hierarchical call interface of the app client by passing an `undefined` value. This acts as a placeholder and tells the app client that another ABI method call argument will supply the value for this argument. For example:
```typescript
const payment = algorand.createTransaction.payment({
sender: alice.addr,
receiver: alice.addr,
amount: microAlgo(1),
})
const myOtherMethodCall = await appClient.params.call({
method: 'myOtherMethod',
args: [payment],
})
const myMethodCall = await appClient.send.call({
method: 'myMethod',
args: [undefined, myOtherMethodCall],
})
```
`myOtherMethodCall` supplies the pay transaction to the transaction group and, by association, `myOtherMethodCall` has access to it as defined in its signature.
To ensure the app client builds the correct transaction group, you must supply a value for every argument in a method call signature.
## Funding the app account
Often there is a need to fund an app account to cover minimum balance requirements for boxes and other scenarios. There is an app client method that will do this for you `fundAppAccount(params)`.
The input parameters are:
- A [`FundAppParams`](../code/modules/types_app_client.md#fundappparams), which has the same properties as a [payment transaction](./transfer.md#payment) except `receiver` is not required and `sender` is optional (if not specified then it will be set to the app client's default sender if configured).
Note: If you are passing the funding payment in as an ABI argument so it can be validated by the ABI method then you'll want to get the funding call as a transaction, e.g.:
```typescript
const result = await appClient.send.call({
method: 'bootstrap',
args: [
appClient.createTransaction.fundAppAccount({
amount: microAlgo(200_000),
}),
],
boxReferences: ['Box1'],
})
```
You can also get the funding call as a params object via `appClient.params.fundAppAccount(params)`.
## Reading state
`AppClient` has a number of mechanisms to read state (global, local and box storage) from the app instance.
### App spec methods
The ARC-56 app spec can specify detailed information about the encoding format of state values and as such allows for a more advanced ability to automatically read state values and decode them as their high-level language types rather than the limited `bigint` / `bytes` / `string` ability that the [generic methods](#generic-methods) give you.
You can access this functionality via:
- `appClient.state.global.{method}()` - Global state
- `appClient.state.local(address).{method}()` - Local state
- `appClient.state.box.{method}()` - Box storage
Where `{method}` is one of:
- `getAll()` - Returns all single-key state values in a record keyed by the key name and the value a decoded ABI value.
- `getValue(name)` - Returns a single state value for the current app with the value a decoded ABI value.
- `getMapValue(mapName, key)` - Returns a single value from the given map for the current app with the value a decoded ABI value. Key can either be a `Uint8Array` with the binary value of the key value on-chain (without the map prefix) or the high level (decoded) value that will be encoded to bytes for the app spec specified `keyType`
- `getMap(mapName)` - Returns all map values for the given map in a key=>value record. It's recommended that this is only done when you have a unique `prefix` for the map otherwise there's a high risk that incorrect values will be included in the map.
```typescript
const values = appClient.state.global.getAll()
const value = appClient.state.local('ADDRESS').getValue('value1')
const mapValue = appClient.state.box.getMapValue('map1', 'mapKey')
const map = appClient.state.global.getMap('myMap')
```
### Generic methods
There are various methods defined that let you read state from the smart contract app:
- `getGlobalState()` - Gets the current global state using [`algorand.app.getGlobalState`](./app.md#global-state)
- `getLocalState(address: string)` - Gets the current local state for the given account address using [`algorand.app.getLocalState`](./app.md#local-state).
- `getBoxNames()` - Gets the current box names using [`algorand.app.getBoxNames`](./app.md#boxes)
- `getBoxValue(name)` - Gets the current value of the given box using [`algorand.app.getBoxValue`](./app.md#boxes)
- `getBoxValueFromABIType(name)` - Gets the current value of the given box from an ABI type using [`algorand.app.getBoxValueFromABIType`](./app.md#boxes)
- `getBoxValues(filter)` - Gets the current values of the boxes using [`algorand.app.getBoxValues`](./app.md#boxes)
- `getBoxValuesFromABIType(type, filter)` - Gets the current values of the boxes from an ABI type using [`algorand.app.getBoxValuesFromABIType`](./app.md#boxes)
```typescript
const globalState = await appClient.getGlobalState()
const localState = await appClient.getLocalState('ACCOUNTADDRESS')
const boxName: BoxReference = 'my-box'
const boxName2: BoxReference = 'my-box2'
const boxNames = appClient.getBoxNames()
const boxValue = appClient.getBoxValue(boxName)
const boxValues = appClient.getBoxValues([boxName, boxName2])
const boxABIValue = appClient.getBoxValueFromABIType(boxName, algosdk.ABIStringType)
const boxABIValues = appClient.getBoxValuesFromABIType([boxName, boxName2], algosdk.ABIStringType)
```
## Handling logic errors and diagnosing errors
Often when calling a smart contract during development you will get logic errors that cause an exception to throw. This may be because of a failing assertion, a lack of fees, exhaustion of opcode budget, or any number of other reasons.
When this occurs, you will generally get an error that looks something like: `TransactionPool.Remember: transaction {TRANSACTION_ID}: logic eval error: {ERROR_MESSAGE}. Details: pc={PROGRAM_COUNTER_VALUE}, opcodes={LIST_OF_OP_CODES}`.
The information in that error message can be parsed and when combined with the [source map from compilation](./app-deploy.md#compilation-and-template-substitution) you can expose debugging information that makes it much easier to understand what's happening. The ARC-56 app spec, if provided, can also specify human-readable error messages against certain program counter values and further augment the error message.
The app client and app factory automatically provide this functionality for all smart contract calls. They also expose a function that can be used for any custom calls you manually construct and need to add into your own try/catch `exposeLogicError(e: Error, isClear?: boolean)`.
When an error is thrown then the resulting error that is re-thrown will be a [`LogicError` object](../code/classes/types_logic_error.LogicError.md), which has the following fields:
- `message: string` - The formatted error message `{ERROR_MESSAGE}. at:{TEAL_LINE}. {ERROR_DESCRIPTION}`
- `stack: string` - A stack trace of the TEAL code showing where the error was with the 5 lines either side of it
- `led: LogicErrorDetails` - The parsed [logic error details](../code/interfaces/types_logic_error.LogicErrorDetails.md) from the error message, with the following properties:
- `txId: string` - The transaction ID that triggered the error
- `pc: number` - The program counter
- `msg: string` - The raw error message
- `desc: string` - The full error description
- `traces: Record<string, unknown>[]` - Any traces that were included in the error
- `program: string[]` - The TEAL program split by line
- `teal_line: number` - The line number in the TEAL program that triggered the error
Note: This information will only show if the app client / app factory has a source map. This will occur if:
- You have called `create`, `update` or `deploy`
- You have called `importSourceMaps(sourceMaps)` and provided the source maps (which you can get by calling `exportSourceMaps()` after variously calling `create`, `update`, or `deploy` and it returns a serialisable value)
- You had source maps present in an app factory and then used it to [create an app client](#dynamically-creating-clients-for-a-given-app-spec) (they are automatically passed through)
If you want to go a step further and automatically issue a [simulated transaction](https://algorand.github.io/js-algorand-sdk/classes/modelsv2.SimulateTransactionResult.html) and get trace information when there is an error when an ABI method is called you can turn on debug mode:
```typescript
Config.configure({ debug: true })
```
If you do that then the exception will have the `traces` property within the underlying exception will have key information from the simulation within it and this will get populated into the `led.traces` property of the thrown error.
When this debug flag is set, it will also emit debugging symbols to allow break-point debugging of the calls if the [project root is also configured](./debugging.md).
## Default arguments
If an ABI method call specifies default argument values for any of its arguments you can pass in `undefined` for the value of that argument for the default value to be automatically populated.
```
--------------------------------------------------------------------------------
/packages/server/src/resources/knowledge/taxonomy/algokit:cli:architecture-decisions:2023-01-11_beaker_productionisation_review.md:
--------------------------------------------------------------------------------
```markdown
# Beaker productionisation review
- **Status**: Approved
- **Owners:** Rob Moore, Adam Chidlow
- **Deciders**: Anne Kenyon (Algorand Inc.), Alessandro Cappellato (Algorand Foundation), Jason Weathersby (Algorand Foundation), Benjamin Guidarelli (Algorand Foundation), Bob Broderick (Algorand Inc.)
- **Date created**: 2023-01-11
- **Date decided:** 2023-02-04
- **Date updated**: 2023-02-04
## Context
Beaker is a smart contract development framework for [Algorand](https://www.algorand.com/) that provides a wrapper over [PyTeal](https://pyteal.readthedocs.io/en/stable/) that focusses on providing a great developer experience through terse, expressive language constructs and making common tasks easier. Beaker is useful because it creates a higher level programming construct from PyTEAL that is easier to get started when learning and results in code that is terser and easier to read and write.
Beaker is an important part of the [AlgoKit strategy](https://github.com/algorandfoundation/algokit-cli/#algokit-cli). It helps create a more seamless onramp to Algorand development by providing an easier starting point for developers. As part of the lead up to releasing AlgoKit, it was desired to perform a v1.0 release of Beaker and explicitly mark it as being production ready. In order to provide confidence a productionisation review was conducted by [MakerX](https://www.makerx.com.au/); this document summarises the recommendations from that review.
An architecture decision was made in the lead up to this review on a [testing strategy for Beaker](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/architecture-decisions/2022-11-22_beaker-testing-strategy.md).
## Goal
The goals of this productionisation review are to:
- Get Beaker ready for production use
- Gain confidence in Beaker's software architecture and maintainability
- Reduce the likelihood of need for breaking changes soon after release by getting key recommended breaking changes identified now
## Findings summary
The Beaker codebase is well factored and had a decent initial test coverage (albeit some of that test coverage is via a series of examples that while they provide high code coverage, don't actually validate all of the functionality).
A series of changes have been landed to improve some of the fundamentals of Beaker in preparation for production launch:
- [Various improvements](https://github.com/algorand-devrel/beaker/pull/142) - Improved test coverage, improved dev experience (setup + ongoing) via Poetry, improvements to the code quality setup (linting, automatic formatting, typing), allowed Windows development on Beaker itself, significantly improved CI/CD pipeline speed, removing the examples directory and tests from being distributed wit hthe PyPi package
- [Typing improvements](https://github.com/algorand-devrel/beaker/pull/147)
- [Removed inline imports](https://github.com/algorand-devrel/beaker/pull/148)
- [Removed dead code](https://github.com/algorand-devrel/beaker/pull/149/files)
- [Added automated release management and versioning](https://github.com/algorand-devrel/beaker/pull/161)
In addition, there are a remaining set of more major (breaking) changes that are recommended. The recommendations are split into 2 categories, recommendations for immediate improvement (i.e. included in v1.0) and future suggestions that can be addressed post v1.0 launch.
The recommended additional areas for immediate improvement are:
- **Replace the class-based structure with an instance based one** - remove some areas of potential surprise for developers and simplify the Beaker codebase by moving to a composable instance-based structure rather than a static class-based structure
- **Defer PyTEAL compilation** - improve flexibility and future contract output stability by deferring PyTEAL compilation (i.e. Beaker -> TEAL transpilation) to not happen when the Beaker contract is initialised
- **Renamings** - There are some clear parameters that make sense to rename for various reasons
- **Key decorator improvements** - Refactor some of the Beaker decorators to fix some bugs and improve user experience
- **Beaker state refactor** - Refactor of the Beaker state interfaces to improve user extensibility and significantly simplify the Beaker codebase to improve maintainability
The recommended areas for future improvement are:
- Typed client generation from app spec to improve deploy-time and run-time dev experience
- `Tmpl` values in app spec so you can have type-safe deployment clients that substitute any template values reliably at contract deploy time
- Refactor storage types (blob, reserved, etc.) to allow use of in-built Python types and operators (terser, more intuitive)
- Box storage implementation improved to match local/global behaviour and also automatically delete itself on contract deletion
- Composable and stackable authorization and `@authorize` as a standalone decorator
- PyTEAL typings to be improved to support types beyond `Expr` where a more explicit type can be specified (improves typing and extensibility)
- Support referencing an app/lsig via ID/address (deployed separately, potentially automatically as part reading a Directed Acyclic Graph (DAG) in application.json of application dependencies) or bytes (deployed inline, what was previously called precompile, noting this would be deploy-time substitution, not smart contract run-time substitute like `TemplateVariable`), this also may allow precompile to be deprecated (it's a very complex implementation for what we believe to be an advanced edge case)
## Immediate recommendations
### (1) Replace the class-based structure with an instance based one
#### What?
Beaker is currently structured around users sub-classing the `beaker.Application` class. They then hold state variables (from `beaker.state.*`) as [class variables](https://pynative.com/python-class-variables/) and also contain methods which are forwarded to the `pyteal.abi.Router` instance created during `Application.compile(...)` based on decorators from `beaker.decorators.*`. We propose replacing this with an "instance based structure", drawing inspiration from highly popular Python web frameworks such as `flask` ([example](https://flask.palletsprojects.com/en/2.2.x/quickstart/#a-minimal-application)).
This change will simplify Beaker's code (improving maintainability) and, more importantly, reduce the potential for end-user error and confusion.
#### Why?
**User-facing benefits**
1. The current structure, by encouraging and supporting [bound instance methods](https://www.geeksforgeeks.org/bound-methods-python/) alongside [class variables](https://pynative.com/python-class-variables/), is a potential source of confusion for users new to writing smart contracts or PyTEAL. The distinction between what runs on `beaker.Application` instantiation, evaluation by PyTeal during compile, and finally what runs on-chain, can be difficult to grasp at first. One might assume (wrongly) that Beaker is somehow maintaining the state of `self.*` between methods, but this is not the case. Contrast this with Solidity, for example, where state can be directly manipulated because it's help within the class instance.
2. Currently, actually using `self.*` can easily lead to problems, since if they are not defined before calling `super().__init__(...)` they won't be defined when compiling. This can be fixed by not automatically compiling in `Application.__init__()` (which is also proposed in (2) below) for simple constants, however another issue is that using `self.foo = <Some beaker.state object>`, would not currently work with the introspection beaker is performing. This could potentially be fixed by itself, but developers will still need to define these values _before_ calling `super().__init__()` which is a source of confusion. Usually, idiomatic Python will call super init sooner rather than later so this is something that can trip up experienced Python developers.
3. In order to compose applications together, say if there were two ARC standard implementations that we wanted to combine into the same contract, the user doesn't need to understand Python's multiple-inheritance idiosyncrasies like [Method Resolution Order](https://www.geeksforgeeks.org/method-resolution-order-in-python-inheritance/). Additionally, by taking a functional composition approach, we can have easy to understand entry points where you can check any pre-conditions.
4. Since state variables are currently defined as class variables, this makes them "globals", which can lead to errors/bugs that are non-obvious.
For instance, consider:
```python
class MyBaseApp(beaker.Application):
counter = beaker.ApplicationStateValue(stack_type=pyteal.TealType.uint64)
@beaker.create
def create(self) -> pyteal.Expr:
return self.initialize_application_state()
class MyApp(MyBaseApp):
pass
MyApp.counter.default = pyteal.Int(10)
class MyOtherApp(MyBaseApp):
pass
app1 = MyApp()
app2 = MyOtherApp()
assert app1.approval_program != app2.approval_program # fails
```
5. Setting parameters that control the program creation is awkward with the current approach of extending `beaker.Application`, currently this impacts just the `version` parameter (which specifies the TEAL version), but there are clear examples we can see for other variables that are useful to define at this point in the future (e.g. a state allocation override if you know that the state a contract will need grows in the future).
6. There are bugs in beaker which are directly caused by the class-based structure. For example, bare methods are currently evaluated as a subroutine only once:
```python
class MyApp(beaker.Application):
price = beaker.ApplicationStateValue(stack_type=pyteal.TealType.uint64)
def __init__(self, default_price: int, version: int = pyteal.MAX_TEAL_VERSION):
self.price.default = pyteal.Int(default_price)
super().__init__(version=version)
class CorrectApp(MyApp):
@beaker.create
def create(self, *, output: pyteal.abi.Uint64) -> pyteal.Expr:
return pyteal.Seq(self.initialize_application_state(), output.set(self.price))
class IncorrectApp(MyApp):
@beaker.create
def create(self) -> pyteal.Expr:
return self.initialize_application_state()
correct_app1 = CorrectApp(default_price=123)
correct_app2 = CorrectApp(default_price=456)
incorrect_app1 = IncorrectApp(default_price=123)
incorrect_app2 = IncorrectApp(default_price=456)
assert correct_app1.approval_program != correct_app2.approval_program # success
assert incorrect_app1.approval_program != incorrect_app2.approval_program # failure
```
**Beaker maintainability benefit**
The main benefit to Beaker is the removal the complex code that modifies function signatures to remove `self` before passing to PyTEAL. Removing the instance method implementation will significantly reduce the complexity of the code and likelihood of unknown bugs surfacing from that part of the codebase.
#### Before & After (user's perspective)
While the proposed changes are fairly substantial internally, and propose a radically different architecture conceptually for beaker Applications, the migration should actually be relatively straight forward for users with existing Beaker code:
The following examples assume the import of relevant names from `beaker` and/or `pyteal` are present to simplify the code.
**Before:**
```python
class CounterApp(Application):
counter = ApplicationStateValue(
stack_type=TealType.uint64,
descr="A counter for showing how to use application state",
)
@create
def create(self):
return self.initialize_application_state()
@external(authorize=Authorize.only(Global.creator_address()))
def increment(self, *, output: abi.Uint64):
"""increment the counter"""
return Seq(
self.counter.set(self.counter + Int(1)),
output.set(self.counter),
)
@external(authorize=Authorize.only(Global.creator_address()))
def decrement(self, *, output: abi.Uint64):
"""decrement the counter"""
return Seq(
self.counter.set(self.counter - Int(1)),
output.set(self.counter),
)
```
**After:**
The changes are:
- State is moved into a dedicated class `CounterState`
- `beaker.Application` is directly instantiated (along with the state, and optionally the teal `version`)
- Class methods are de-indented, `self` is removed and the decorator is prefixed with `app.` (which in turn reduces the number of imports needed from the `beaker` namespace and provides better exploratory intellisense for users)
```python
class CounterState(beaker.State):
counter = ApplicationStateValue(
stack_type=TealType.uint64,
descr="A counter for showing how to use application state",
)
app = beaker.Application(state=CounterState())
@app.create
def create():
return app.state.initialize_application_state()
@app.external(authorize=Authorize.only(Global.creator_address()))
def increment(*, output: abi.Uint64):
"""increment the counter"""
return Seq(
app.state.counter.set(app.state.counter + Int(1)),
output.set(app.state.counter),
)
@app.external(authorize=Authorize.only(Global.creator_address()))
def decrement(*, output: abi.Uint64):
"""decrement the counter"""
return Seq(
app.state.counter.set(app.state.counter - Int(1)),
output.set(app.state.counter),
)
```
### (2) Defer PyTEAL compilation
#### What?
Currently, `beaker.Application.compile()` is called as part of `__init__()`, assuming there are no `precompiles` defined. We recommend that `compile()` always be deferred to a later point, and further that `compile()` does not mutate `Application` in any way, but instead returns a new object.
#### Why?
The deferment of the `compile()` call is actually a necessary part of recommendation #1 that we have skipped over thus far, but would be recommended anyway.
The immediate `compile()` has issues such as requiring implementors (i.e. subclasses) to call `super().__init__()` as a **final** step in their own `__init__` method - any code that runs after the super init call will have no effect on the application produced!
Immediate compilation also reduces the control the user has over the output. Although currently the only parameter that `compile` takes is a `client`, it might be useful to add (optional) parameters here to control the compilation. For example, if you can pass in the list of optimisations that should be applied, that allows you to have [output stability](../articles/output_stability.md) of your smart contract code if new optimisations are added in the future.
The separation of compiled state outside of `Application` simplifies the design, and can be done mostly transparently to end-users.
The separation of compiled state will also benefit future interoperability. It allows for more explicit decoupling of PyTEAL compilation (Beaker / PyTEAL transpilation -> TEAL) and deploment (TEAL -> compiled byte code -> Algorand network). Once `beaker.client` is split into a separate package, if the compiled state can be both generated from a beaker Application object _or_ loaded from disk (or similar), this means Beaker's ApplicationClient could be used in more situations, such as for a (say) tealish smart contract, or a C# smart contract, or a raw PyTEAL or TEAL smart contract, etc. This conforms better to the modularity principle in AlgoKit and also vice versa allows for a Beaker smart contract to be deployed by a TypeScript deployer, or C# deployer, etc.
#### Before & After - user's perspective
For most use cases, this should be a relatively small and probably imperceptible change.
We believe there are two common usage scenarios that use the output of PyTEAL compilation currently:
1. Output the `Application` via `Application.dump(...)`
2. Interact with the `Application` by passing it to `ApplicationClient(app=..., ...)`.
We propose maintaining those two scenarios without any immediate external changes, but internally:
1. `Application.dump(...)` will call `Application.compile().dump()`, and potentially trigger a `DeprecationWarning` if we decide that we want users to always explicitly call compile.
2. `ApplicationClient(app=..., ...)` will call `Application.compile()` and not retain any reference to `app`.
To make use of scenarios 1 _and_ 2, or to control compilation parameters, a user should also be able to (for instance):
```python
app = Application(...)
compiled_app: CompiledApplication = app.compile(...)
compiled_app.dump(...)
client = ApplicationClient(app=compiled_app, ...)
```
We suggest also potentially renaming `CompiledApplication.dump()`, perhaps to something along the lines of `serialize()`.
The `compile()` call is actually a transpilation call (Beaker / PyTEAL transpilation -> TEAL), although it's called compile in PyTEAL so consideration should be made to either keep consistency with PyTEAL or use the more accurate `transpile()` (which also reduces confusion around the fact that you then have to call `compile` on algod to compile the TEAL to byte code before deployment).
The exact details of what `CompiledApplication` will look like are TBD, but should be driven by the principles outlined in the "Why?" section above. Broadly, it stands to reason it would contain the approval and clear TEAL, the ABI spec and the app spec though at least.
Finally, there is likely need to use metadata from transpilation such as the mapping of source code to line numbers, but we are confident these use cases will be able to be implemented on top of the proposed change.
### (3) Renamings
Renaming `version` parameter in `Application.__init__(version: int = pyteal.MAX_VERSION)` to (e.g.) `avm_version`, to be more explicit. Otherwise developers may be confused that it's the version of the specific smart contract. It may be desirable to allow `version` to continue to be specified for some time, but to raise a `DeprecationWarning`.
Rename methods in `beaker.lib.*` to start with an uppercase. Although going against PEP-8, this prevents collisions with `builtins` such as `min` and `max`, and also follows the useful convention from PyTeal where methods that produce TEAL code (vs just running Python code at transpilation time) start with uppercase such as `Add`, `Or`, `Concat`, etc.
### (4) Key decorator improvements
Refactor some of the Beaker decorators to fix some bugs and improve user experience.
End state:
```python
# for user convenience, rather than having to import + use MethodConfig
OnCompleteActionName: TypeAlias = Literal[
"no_op",
"opt_in",
"close_out",
"clear_state",
"update_application",
"delete_application",
]
HandlerFunc: TypeAlias = Callable[..., Expr]
DecoratorFunc: TypeAlias = Callable[[HandlerFunc], HandlerFunc]
class Application:
# the main decorator, capable of handling both ABI and Bare method registration
def external(
self,
fn: HandlerFunc | None = None,
/,
*,
# note: retain existing behaviour of if method_config is None, default to no_op with CallConfig.CALL
method_config: MethodConfig | dict[OnCompleteActionName, CallConfig] | None = None,
name: str | None = None,
authorize: SubroutineFnWrapper | None = None,
bare: bool = False,
read_only: bool = False,
override: bool | None = False,
) -> HandlerFunc | DecoratorFunc:
...
# the below are just "shortcuts" to @external for simple/common use cases
def create(
self,
fn: HandlerFunc | None = None,
/,
*,
allow_call: bool = False,
name: str | None = None,
authorize: SubroutineFnWrapper | None = None,
bare: bool = False,
read_only: bool = False,
override: bool | None = False,
) -> HandlerFunc | DecoratorFunc:
...
def <delete|update|opt_in|clear_state|close_out|no_op>(
self,
fn: HandlerFunc | None = None,
/,
*,
allow_call: bool = True,
allow_create: bool = False,
name: str | None = None,
authorize: SubroutineFnWrapper | None = None,
bare: bool = False,
read_only: bool = False,
override: bool | None = False,
) -> HandlerFunc | DecoratorFunc:
...
```
For reference, the current state:
```python
def internal(
return_type_or_handler: TealType | HandlerFunc,
) -> HandlerFunc | DecoratorFunc:
...
def external(
func: HandlerFunc | None = None,
/,
*,
name: str | None = None,
authorize: SubroutineFnWrapper | None = None,
method_config: MethodConfig | None = None,
read_only: bool = False,
) -> HandlerFunc | DecoratorFunc:
...
def bare_external(
no_op: CallConfig | None = None,
opt_in: CallConfig | None = None,
clear_state: CallConfig | None = None,
delete_application: CallConfig | None = None,
update_application: CallConfig | None = None,
close_out: CallConfig | None = None,
) -> Callable[..., HandlerFunc]:
...
def create(
fn: HandlerFunc | None = None,
/,
*,
authorize: SubroutineFnWrapper | None = None,
method_config: Optional[MethodConfig] | None = None,
) -> HandlerFunc | DecoratorFunc:
...
def <delete|update|opt_in|clear_state|close_out|no_op>(
fn: HandlerFunc | None = None, /, *, authorize: SubroutineFnWrapper | None = None
) -> HandlerFunc | DecoratorFunc:
...
```
Changes:
- Remove `@internal`:
- if you don't pass a TealType parameter to it, i.e. intend to create an ABI internal routine, it actually just inlines the code currently due to a bug
- when passing in a TealType parameter to it, i.e. intent to create a normal subroutine, then in combination with (1) it will be unneeded since you can use `Subroutine` from PyTEAL (since the methods don't need to be artificially modified to remove `self` anymore)
- Add `bare: bool` option:
- Currently, this is not able to be controlled by the user - for `<create|delete|update|opt_in|clear_state|close_out|no_op>` decorators, they will create a bare method if the function takes no parameters other than maybe a `self` parameter. This has some down-sides:
1. The user might want an ABI method rather than a bare method. In this case, currently they could use `@external(method_config=...)`, but for simple cases this is not as easy to read/type and is not intuitive to discover in the first place.
2. The user might have more than one method that takes no parameters that is able to be called with a given `OnCompletionAction`, currently this would produce a `BareOverwriteError` in Beaker. Again, the work-around exists of calling `@external` instead, but it would be nicer and more intuitive to add a `bare` option to control this explicitly.
- The above Python methods have `bare: bool = False`. An alternative option would be to make this `bare: bool | None = None`, where `None` would retain the current behaviour of inspecting the method signature to see if it takes parameters or not.
- Remove `@bare_external`:
- Mostly unused, and doesn't provide the same options as the other decorators (e.g. `authorize`)
- Instead, we can replace the case of a single option being passed to it, with the equivalent named method: for example `@bare_external(opt_in=CallConfig.CALL)` becomes `@opt_in(bare=True)`
- For the multi-argument case: `@bare_external(no_op=CallConfig.CREATE, opt_in=CallConfig.CALL)` becomes `@external(method_config={"no_op": CallConfig.CREATE, "opt_in": CallConfig.CALL}, bare=True)`
* Add optional `name` option to all decorators, not just `@external`.
* Add `allow_call` and `allow_create` options to shortcut methods (except `@create` shortcut which should always allow `CallConfig.CREATE`).
* Remove `method_config` from `@create` shortcut - the default behaviour will remain unchanged, but any usages with `method_config` specified would be equivalent to just using `@external` directly.
* Add `override: bool | None = False` parameter.
- If `False` (the suggested default), an error will be raised if an ABI or Bare method would replace one already registered in the Application. For bare methods, this would be keyed on the `OnCompleteAction`, and for ABI methods should be based on the method signature (ie `ABIReturnSubroutine.method_signature()`). This is suggested as the default to prevent unexpected cases of overriding, especially when using blueprints/templates from the future Smart Contracts Library.
- If `True`, then an error will be raised if it _does not_ replace an already registered ABI or Bare method. This is similar to Java's `@Override` annotation, and can allow the user to be explicit and thus prevent unexpectedly _not_ replacing an existing method.
- If `None`, then methods will be overwritten if present, and no error will be raised if not already present. This option is here for maximum flexibility, but should perhaps be discouraged.
### (5) Beaker state refactor
Refactor of the `beaker.state` internal interfaces to simplify Beaker code base, make it easier to add new state wrappers, and to pave the way for future enhancements. This will have a side effect of allowing users to create their own state wrappers without having to modify `beaker` itself, although we recommend marking these interfaces as internal and subject to change - at least initially.
```
--------------------------------------------------------------------------------
/packages/server/src/resources/knowledge/taxonomy/ARCs:specs:arc-0003.md:
--------------------------------------------------------------------------------
```markdown
---
arc: 3
title: Conventions Fungible/Non-Fungible Tokens
description: Parameters Conventions for Algorand Standard Assets (ASAs) for fungible tokens and non-fungible tokens (NFTs).
author: Fabrice Benhamouda (@fabrice102)
discussions-to: https://github.com/algorandfoundation/ARCs/issues/3
status: Final
type: Standards Track
category: ARC
sub-category: Asa
created: 2021-08-07
---
# Algorand Standard Asset Parameters Conventions for Fungible and Non-Fungible Tokens
## Abstract
The goal of these conventions is to make it simpler for block explorers, wallets, exchanges, marketplaces, and more generally, client software to display the properties of a given ASA.
## Specification
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>.
> Comments like this are non-normative.
An [ARC-3](./arc-0003.md) ASA has an associated JSON Metadata file, formatted as specified below, that is stored off-chain.
### ASA Parameters Conventions
The ASA parameters should follow the following conventions:
* *Unit Name* (`un`): no restriction but **SHOULD** be related to the name in the JSON Metadata file
* *Asset Name* (`an`): **MUST** be:
* (**NOT RECOMMENDED**) either exactly `arc3` (without any space)
* (**NOT RECOMMENDED**) or `<name>@arc3`, where `<name>` **SHOULD** be closely related to the name in the JSON Metadata file:
* If the resulting asset name can fit the *Asset Name* field, then `<name>` **SHOULD** be equal to the name in the JSON Metadata file.
* If the resulting asset name cannot fit the *Asset Name* field, then `<name>` **SHOULD** be a reasonable shorten version of the name in the JSON Metadata file.
* (**RECOMMENDED**) or `<name>` where `<name>` is defined as above. In this case, the Asset URL **MUST** end with `#arc3`.
* *Asset URL* (`au`): a URI pointing to a JSON Metadata file.
* This URI as well as any URI in the JSON Metadata file:
* **SHOULD** be persistent and allow to download the JSON Metadata file forever.
* **MAY** contain the string `{id}`. If `{id}` exists in the URI, clients **MUST** replace this with the asset ID in decimal form. The rules below applies after such a replacement.
* **MUST** follow <a href="https://www.ietf.org/rfc/rfc3986.txt">RFC-3986</a> and **MUST NOT** contain any whitespace character
* **SHOULD** use one of the following URI schemes (for compatibility and security): *https* and *ipfs*:
* When the file is stored on IPFS, the `ipfs://...` URI **SHOULD** be used. IPFS Gateway URI (such as `https://ipfs.io/ipfs/...`) **SHOULD NOT** be used.
* **SHOULD NOT** use the following URI scheme: *http* (due to security concerns).
* **MUST** be such that the returned resource includes the CORS header
```
Access-Control-Allow-Origin: *
```
if the URI scheme is *https*
> This requirement is to ensure that client JavaScript can load all resources pointed by *https* URIs inside an ARC-3 ASA.
* **MAY** be a relative URI when inside the JSON Metadata file. In that case, the relative URI is relative to the Asset URL. The Asset URL **SHALL NOT** be relative. Relative URI **MUST** not contain the character `:`. Clients **MUST** consider a URI as relative if and only if it does not contain the character `:`.
* If the Asset Name is neither `arc3` nor of the form `<name>@arc3`, then the Asset URL **MUST** end with `#arc3`.
* If the Asset URL ends with `#arc3`, clients **MUST** remove `#arc3` when linking to the URL. When displaying the URL, they **MAY** display `#arc3` in a different style (e.g., a lighter color).
* If the Asset URL ends with `#arc3`, the full URL with `#arc3` **SHOULD** be valid and point to the same resource as the URL without `#arc3`.
> This recommendation is to ensure backward compatibility with wallets that do not support ARC-3.
* *Asset Metadata Hash* (`am`):
* If the JSON Metadata file specifies extra metadata `e` (property `extra_metadata`), then `am` is defined as:
```plain
am = SHA-512/256("arc0003/am" || SHA-512/256("arc0003/amj" || content of JSON Metadata file) || e)
```
where `||` denotes concatenation and SHA-512/256 is defined in <a href="https://doi.org/10.6028/NIST.FIPS.180-4">NIST FIPS 180-4</a>.
The above definition of `am` **MUST** be used when the property `extra_metadata` is specified, even if its value `e` is the empty string.
Python code to compute the hash and a full example are provided below (see "Sample with Extra Metadata").
> Extra metadata can be used to store data about the asset that needs to be accessed from a smart contract. The smart contract would not be able to directly read the metadata. But, if provided with the hash of the JSON Metadata file and with the extra metadata `e`, the smart contract can check that `e` is indeed valid.
* If the JSON Metadata file does not specify the property `extra_metadata`, then `am` is defined as the SHA-256 digest of the JSON Metadata file as a 32-byte string (as defined in <a href="https://doi.org/10.6028/NIST.FIPS.180-4">NIST FIPS 180-4</a>)
There are no requirements regarding the manager account of the ASA, or its the reserve account, freeze account, or clawback account.
> Clients recognize ARC-3 ASAs by looking at the Asset Name and Asset URL. If the Asset Name is `arc3` or ends with `@arc3`, or if the Asset URL ends with `#arc3`, the ASA is to be considered an ARC-3 ASA.
#### Pure and Fractional NFTs
An ASA is said to be a *pure non-fungible token* (*pure NFT*) if and only if it has the following properties:
* *Total Number of Units* (`t`) **MUST** be 1.
* *Number of Digits after the Decimal Point* (`dc`) **MUST** be 0.
An ASA is said to be a *fractional non-fungible token* (*fractional NFT*) if and only if it has the following properties:
* *Total Number of Units* (`t`) **MUST** be a power of 10 larger than 1: 10, 100, 1000, ...
* *Number of Digits after the Decimal Point* (`dc`) **MUST** be equal to the logarithm in base 10 of total number of units.
> In other words, the total supply of the ASA is exactly 1.
### JSON Metadata File Schema
> The JSON Medata File schema follow the Ethereum Improvement Proposal <a href="https://eips.ethereum.org/EIPS/eip-1155"> ERC-1155 Metadata URI JSON Schema</a> with the following main differences:
> * Support for integrity fields for any file pointed by any URI field as well as for localized JSON Metadata files.
> * Support for mimetype fields for any file pointed by any URI field.
> * Support for extra metadata that is hashed as part of the Asset Metadata Hash (`am`) of the ASA.
> * Adding the fields `external_url`, `background_color`, `animation_url` used by <a href="https://docs.opensea.io/docs/metadata-standards">OpenSea metadata format</a>.
Similarly to ERC-1155, the URI does support ID substitution. If the URI contains `{id}`, clients **MUST** substitute it by the asset ID in *decimal*.
> Contrary to ERC-1155, the ID is represented in decimal (instead of hexadecimal) to match what current APIs and block explorers use on the Algorand blockchain.
The JSON Metadata schema is as follows:
```json
{
"title": "Token Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this token represents"
},
"decimals": {
"type": "integer",
"description": "The number of decimal places that the token amount should display - e.g. 18, means to divide the token amount by 1000000000000000000 to get its user representation."
},
"description": {
"type": "string",
"description": "Describes the asset to which this token represents"
},
"image": {
"type": "string",
"description": "A URI pointing to a file with MIME type image/* representing the asset to which this token represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
},
"image_integrity": {
"type": "string",
"description": "The SHA-256 digest of the file pointed by the URI image. The field value is a single SHA-256 integrity metadata as defined in the W3C subresource integrity specification (https://w3c.github.io/webappsec-subresource-integrity)."
},
"image_mimetype": {
"type": "string",
"description": "The MIME type of the file pointed by the URI image. MUST be of the form 'image/*'."
},
"background_color": {
"type": "string",
"description": "Background color do display the asset. MUST be a six-character hexadecimal without a pre-pended #."
},
"external_url": {
"type": "string",
"description": "A URI pointing to an external website presenting the asset."
},
"external_url_integrity": {
"type": "string",
"description": "The SHA-256 digest of the file pointed by the URI external_url. The field value is a single SHA-256 integrity metadata as defined in the W3C subresource integrity specification (https://w3c.github.io/webappsec-subresource-integrity)."
},
"external_url_mimetype": {
"type": "string",
"description": "The MIME type of the file pointed by the URI external_url. It is expected to be 'text/html' in almost all cases."
},
"animation_url": {
"type": "string",
"description": "A URI pointing to a multi-media file representing the asset."
},
"animation_url_integrity": {
"type": "string",
"description": "The SHA-256 digest of the file pointed by the URI external_url. The field value is a single SHA-256 integrity metadata as defined in the W3C subresource integrity specification (https://w3c.github.io/webappsec-subresource-integrity)."
},
"animation_url_mimetype": {
"type": "string",
"description": "The MIME type of the file pointed by the URI animation_url. If the MIME type is not specified, clients MAY guess the MIME type from the file extension or MAY decide not to display the asset at all. It is STRONGLY RECOMMENDED to include the MIME type."
},
"properties": {
"type": "object",
"description": "Arbitrary properties (also called attributes). Values may be strings, numbers, object or arrays."
},
"extra_metadata": {
"type": "string",
"description": "Extra metadata in base64. If the field is specified (even if it is an empty string) the asset metadata (am) of the ASA is computed differently than if it is not specified."
},
"localization": {
"type": "object",
"required": ["uri", "default", "locales"],
"properties": {
"uri": {
"type": "string",
"description": "The URI pattern to fetch localized data from. This URI should contain the substring `{locale}` which will be replaced with the appropriate locale value before sending the request."
},
"default": {
"type": "string",
"description": "The locale of the default data within the base JSON"
},
"locales": {
"type": "array",
"description": "The list of locales for which data is available. These locales should conform to those defined in the Unicode Common Locale Data Repository (http://cldr.unicode.org/)."
},
"integrity": {
"type": "object",
"patternProperties": {
".*": { "type": "string" }
},
"description": "The SHA-256 digests of the localized JSON files (except the default one). The field name is the locale. The field value is a single SHA-256 integrity metadata as defined in the W3C subresource integrity specification (https://w3c.github.io/webappsec-subresource-integrity)."
}
}
}
}
}
```
All the fields are **OPTIONAL**. But if provided, they **MUST** match the description in the JSON schema.
The field `decimals` is **OPTIONAL**. If provided, it **MUST** match the ASA parameter `dt`.
URI fields (`image`, `external_url`, `animation_url`, and `localization.uri`) in the JSON Metadata file are defined similarly as the Asset URL parameter `au`.
However, contrary to the Asset URL, they **MAY** be relative (to the Asset URL).
See Asset URL above.
#### Integrity Fields
Compared to ERC-1155, the JSON Metadata schema allows to indicate digests of the files pointed by any URI field.
This is to ensure the integrity of all the files referenced by the ASA.
Concretly, every URI field `xxx` is allowed to have an optional associated field `xxx_integrity` that specifies the digest of the file pointed by the URI.
The digests are represented as a single SHA-256 integrity metadata as defined in the <a href="https://w3c.github.io/webappsec-subresource-integrity">W3C subresource integrity specification</a>.
Details on how to generate those digests can be found on the <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity">MDN Web Docs</a> (where `sha384` or `384` are to be replaced by `sha256` and `256` respectively as only SHA-256 is supported by this ARC).
It is **RECOMMENDED** to specify all the `xxx_integrity` fields of all the `xxx` URI fields, except for `external_url_integrity` when it points to a potentially mutable website.
Any field with a name ending with `_integrity` **MUST** match a corresponding field containing a URI to a file with a matching digest.
For example, if the field `hello_integrity` is specified, the field `hello` **MUST** exist and **MUST** be a URI pointing to a file with a digest equal to the digest specified by `hello_integrity`.
#### MIME Type Files
Compared to ERC-1155, the JSON Metadata schema allows to indicate the MIME type of the files pointed by any URI field.
This is to allow clients to display appropriately the resource without having to first query it to find out the MIME type.
Concretly, every URI field `xxx` is allowed to have an optional associated field `xxx_integrity` that specifies the digest of the file pointed by the URI.
It is **STRONGLY RECOMMENDED** to specify all the `xxx_mimetype` fields of all the `xxx` URI fields, except for `external_url_mimetype` when it points to a website. If the MIME type is not specified, clients **MAY** guess the MIME type from the file extension or **MAY** decide not to display the asset at all.
Clients **MUST NOT** rely on the `xxx_mimetype` fields from a security perspective and **MUST NOT** break or fail if the fields are incorrect (beyond not displaying the asset image or animation correctly). In particular, clients **MUST** take all necessary security measures to protect users against remote code execution or cross-site scripting attacks, even when the MIME type looks innocuous (like `image/png`).
> The above restriction is to protect clients and users against malformed or malicious ARC-3.
Any field with a name ending with `_mimetype` **MUST** match a corresponding field containing a URI to a file with a matching digest.
For example, if the field `hello_mimetype` is specified, the field `hello` **MUST** exist and **MUST** be a URI pointing to a file with a digest equal to the digest specified by `hello_mimetype`.
#### Localization
If the JSON Metadata file contains a `localization` attribute, its content **MAY** be used to provide localized values for fields that need it. The `localization` attribute should be a sub-object with three **REQUIRED** attributes: `uri`, `default`, `locales`, and one **RECOMMENDED** attribute: `integrity`. If the string `{locale}` exists in any URI, it **MUST** be replaced with the chosen locale by all client software.
> Compared to ERC-1155, the `localization` attribute contains an additional optional `integrity` field that specify the digests of the localized JSON files.
It is **RECOMMENDED** that `integrity` contains the digests of all the locales but the default one.
#### Examples
##### Basic Example
An example of an ARC-3 JSON Metadata file for a song follows. The properties array proposes some **SUGGESTED** formatting for token-specific display properties and metadata.
```json
{
"name": "My Song",
"description": "My first and best song!",
"image": "https://s3.amazonaws.com/your-bucket/song/cover/mysong.png",
"image_integrity": "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"image_mimetype": "image/png",
"external_url": "https://mysongs.com/song/mysong",
"animation_url": "https://s3.amazonaws.com/your-bucket/song/preview/mysong.ogg",
"animation_url_integrity": "sha256-LwArA6xMdnFF3bvQjwODpeTG/RVn61weQSuoRyynA1I=",
"animation_url_mimetype": "audio/ogg",
"properties": {
"simple_property": "example value",
"rich_property": {
"name": "Name",
"value": "123",
"display_value": "123 Example Value",
"class": "emphasis",
"css": {
"color": "#ffffff",
"font-weight": "bold",
"text-decoration": "underline"
}
},
"array_property": {
"name": "Name",
"value": [1,2,3,4],
"class": "emphasis"
}
}
}
```
In the example, the `image` field **MAY** be the album cover, while the `animation_url` **MAY** be the full song or may just be a small preview.
In the latter case, the full song **MAY** be specified by three additional properties inside the `properties` field:
```json
{
...
"properties": {
...
"file_url": "https://s3.amazonaws.com/your-bucket/song/full/mysong.ogg",
"file_url_integrity": "sha256-7IGatqxLhUYkruDsEva52Ku43up6774yAmf0k98MXnU=",
"file_url_mimetype": "audio/ogg"
}
}
```
An example of possible ASA parameters would be:
* *Asset Unit*: `mysong` for example
* *Asset Name*: `My Song`
* *Asset URL*: `https://example.com/mypict#arc3` or `https://arweave.net/MAVgEMO3qlqe-qHNVs00qgwwbCb6FY2k15vJP3gBLW4#arc3`
* *Metadata Hash*: the 32 bytes of the SHA-256 digest of the above JSON file
* *Total Number of Units*: 100
* *Number of Digits after the Decimal Point*: 2
> IPFS urls of the form `ipfs://QmWS1VAdMD353A6SDk9wNyvkT14kyCiZrNDYAad4w1tKqT#arc3` may be used too but may cause issue with clients that do not support ARC-3 and that do not handle fragments in IPFS URLs.
Example of alternative versions for *Asset Name* and *Asset URL*:
* *Asset Name*: `My Song@arc3` or `arc3`
* *Asset URL*: `ipfs://QmWS1VAdMD353A6SDk9wNyvkT14kyCiZrNDYAad4w1tKqT` or `https://example.com/mypict` or `https://arweave.net/MAVgEMO3qlqe-qHNVs00qgwwbCb6FY2k15vJP3gBLW4`
> These alternative versions are less recommended as they make the asset name harder to read for clients that do not support ARC-3.
The above parameters define a fractional NFT with 100 shares.
The JSON Metadata file **MAY** contain the field `decimals: 2`:
```json
{
...
"decimals": 2
}
```
##### Example with Relative URI and IPFS
> When using IPFS, it is convenient to bundle the JSON Metadata file with other files references by the JSON Metadata file.
> In this case, because of circularity, it is necessary to use relative URI
An example of an ARC-3 JSON Metadata file using IPFS and relative URI is provided below:
```json
{
"name": "My Song",
"description": "My first and best song!",
"image": "mysong.png",
"image_integrity": "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"image_mimetype": "image/png",
"external_url": "https://mysongs.com/song/mysong",
"animation_url": "mysong.ogg",
"animation_url_integrity": "sha256-LwArA6xMdnFF3bvQjwODpeTG/RVn61weQSuoRyynA1I=",
"animation_url_mimetype": "audio/ogg"
}
```
If the Asset URL is `ipfs://QmWS1VAdMD353A6SDk9wNyvkT14kyCiZrNDYAad4w1tKqT/metadata.json`:
* the `image` URI is `ipfs://QmWS1VAdMD353A6SDk9wNyvkT14kyCiZrNDYAad4w1tKqT/mysong.png`.
* the `animation_url` URI is `ipfs://QmWS1VAdMD353A6SDk9wNyvkT14kyCiZrNDYAad4w1tKqT/mysong.ogg`.
##### Example with Extra Metadata and `{id}`
An example of an ARC-3 JSON Metadata file with extra metadata and `{id}` is provided below.
```json
{
"name": "My Picture",
"description": "Lorem ipsum...",
"image": "https://s3.amazonaws.com/your-bucket/images/{id}.png",
"image_integrity": "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"image_mimetype": "image/png",
"external_url": "https://mysongs.com/song/{id}",
"extra_metadata": "iHcUslDaL/jEM/oTxqEX++4CS8o3+IZp7/V5Rgchqwc="
}
```
The possible ASA parameters are the same as with the basic example, except for the metadata hash that would be the 32-byte string corresponding to the base64 string `xsmZp6lGW9ktTWAt22KautPEqAmiXxow/iIuJlRlHIg=`.
> For completeness, we provide below a Python program that computes this metadata hash:
```python
import base64
import hashlib
extra_metadata_base64 = "iHcUslDaL/jEM/oTxqEX++4CS8o3+IZp7/V5Rgchqwc="
extra_metadata = base64.b64decode(extra_metadata_base64)
json_metadata = """{
"name": "My Picture",
"description": "Lorem ipsum...",
"image": "https://s3.amazonaws.com/your-bucket/images/{id}.png",
"image_integrity": "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
"image_mimetype": "image/png",
"external_url": "https://mysongs.com/song/{id}",
"extra_metadata": "iHcUslDaL/jEM/oTxqEX++4CS8o3+IZp7/V5Rgchqwc="
}"""
h = hashlib.new("sha512_256")
h.update(b"arc0003/amj")
h.update(json_metadata.encode("utf-8"))
json_metadata_hash = h.digest()
h = hashlib.new("sha512_256")
h.update(b"arc0003/am")
h.update(json_metadata_hash)
h.update(extra_metadata)
am = h.digest()
print("Asset metadata in base64: ")
print(base64.b64encode(am).decode("utf-8"))
```
#### Localized Example
An example of an ARC-3 JSON Metadata file with localized metadata is presented below.
Base metadata file:
```json
{
"name": "Advertising Space",
"description": "Each token represents a unique Ad space in the city.",
"localization": {
"uri": "ipfs://QmWS1VAdMD353A6SDk9wNyvkT14kyCiZrNDYAad4w1tKqT/{locale}.json",
"default": "en",
"locales": [
"en",
"es",
"fr"
],
"integrity": {
"es": "sha256-T0UofLOqdamWQDLok4vy/OcetEFzD8dRLig4229138Y=",
"fr": "sha256-UUM89QQlXRlerdzVfatUzvNrEI/gwsgsN/lGkR13CKw="
}
}
}
```
File `es.json`:
```json
{
"name": "Espacio Publicitario",
"description": "Cada token representa un espacio publicitario único en la ciudad."
}
```
File `fr.json`:
```json
{
"name": "Espace Publicitaire",
"description": "Chaque jeton représente un espace publicitaire unique dans la ville."
}
```
Note that if the base metadata file URI (i.e., the Asset URL) is `ipfs://QmWS1VAdMD353A6SDk9wNyvkT14kyCiZrNDYAad4w1tKqT/metadata.json`, then the `uri` field inside the `localization` field may be the relative URI `{locale}.json`.
## Rationale
These conventions are heavily based on Ethereum Improvement Proposal <a href="https://eips.ethereum.org/EIPS/eip-1155"> ERC-1155 Metadata URI JSON Schema</a> to facilitate interoperobility.
The main differences are highlighted below:
* Asset Name and Asset Unit can be optionally specified in the ASA parameters. This is to allow wallets that are not aware of ARC-3 or that are not able to retrieve the JSON file to still display meaningful information.
* A digest of the JSON Metadata file is included in the ASA parameters to ensure integrity of this file. This is redundant with the URI when IPFS is used. But this is important to ensure the integrity of the JSON file when IPFS is not used.
* Similarly, the JSON Metadata schema is changed to allow to specify the SHA-256 digests of the localized versions as well as the SHA-256 digests of any file pointed by a URI property.
* MIME type fields are added to help clients know how to display the files pointed by URI.
* When extra metadata are provided, the Asset Metadata Hash parameter is computed using SHA-512/256 with prefix for proper domain separation. SHA-512/256 is the hash function used in Algorand in general (see the list of prefixes in https://github.com/algorand/go-algorand/blob/master/protocol/hash.go). Domain separation is especially important in this case to avoid mixing hash of the JSON Metadata file with extra metadata. However, since SHA-512/256 is less common and since not every tool or library allows to compute SHA-512/256, when no extra metadata is specified, SHA-256 is used instead.
* Support for relative URI is added to allow storing both the JSON Metadata files and the files it refers to in the same IPFS directory.
Valid JSON Metadata files for ERC-1155 are valid JSON Metadata files for ARC-3.
However, it is highly recommended that users always include the additional RECOMMENDED fields, such as the integrity fields.
The asset name is either `arc3` or suffixed by `@arc3` to allow client software to know when an asset follows the conventions.
## Security Considerations
> Not Applicable
## Copyright
Copyright and related rights waived via <a href="https://creativecommons.org/publicdomain/zero/1.0/">CCO</a>.
```