This is page 38 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/ARCs:specs:arc-0020.md:
--------------------------------------------------------------------------------
```markdown
---
arc: 20
title: Smart ASA
description: An ARC for an ASA controlled by an Algorand Smart Contract
author: Cosimo Bassi (@cusma), Adriano Di Luzio (@aldur), John Jannotti (@jannotti)
discussions-to: https://github.com/algorandfoundation/ARCs/issues/109
status: Final
type: Standards Track
category: Interface
sub-category: Asa
created: 2022-04-27
requires: 4, 22
---
## Abstract
A "Smart ASA" is an Algorand Standard Asset (ASA) controlled by a Smart Contract
that exposes methods to create, configure, transfer, freeze, and destroy the
asset.
This ARC defines the ABI interface of such Smart Contract, the required
metadata, and suggests a reference implementation.
## Motivation
The Algorand Standard Asset (ASA) is an excellent building block for on-chain
applications. It is battle-tested and widely supported by SDKs, wallets, and
dApps.
However, the ASA lacks in flexibility and configurability. For instance, once
issued it can't be re-configured (its unit name, decimals, maximum supply).
Also, it is freely transferable (unless frozen). This prevents developers from
specifying additional business logic to be checked while transferring it (think
of royalties or vesting https://en.wikipedia.org/wiki/Vesting).
Enforcing transfer conditions require freezing the asset and transferring it
through a clawback operation -- which results in a process that is opaque to
users and wallets and a bad experience for the users.
The Smart ASA defined by this ARC extends the ASA to increase its expressiveness
and its flexibility. By introducing this as a standard both developers, users
(marketplaces, wallets, dApps, etc.) and SDKs can confidently and consistently
recognize Smart ASAs and adjust their flows and user experiences accordingly.
## Specification
The keywords "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
interpreted as described in RFC
2119 https://datatracker.ietf.org/doc/html/rfc2119.
The following sections describe:
- The ABI interface for a controlling Smart Contract (the Smart Contract that
controls a Smart ASA).
- The metadata required to denote a Smart ASA and define the association between
an ASA and its controlling Smart Contract.
### ABI Interface
The ABI interface specified here draws inspiration from the transaction
reference https://developer.algorand.org/docs/get-details/asa/#asset-functions
of an Algorand Standard Asset (ASA).
To provide a unified and familiar interface between the Algorand Standard Asset
and the Smart ASA, method names and parameters have been adapted to the ABI
types but left otherwise unchanged.
#### Asset Creation
```json
{
"name": "asset_create",
"args": [
{ "type": "uint64", "name": "total" },
{ "type": "uint32", "name": "decimals" },
{ "type": "bool", "name": "default_frozen" },
{ "type": "string", "name": "unit_name" },
{ "type": "string", "name": "name" },
{ "type": "string", "name": "url" },
{ "type": "byte[]", "name": "metadata_hash" },
{ "type": "address", "name": "manager_addr" },
{ "type": "address", "name": "reserve_addr" },
{ "type": "address", "name": "freeze_addr" },
{ "type": "address", "name": "clawback_addr" }
],
"returns": { "type": "uint64" }
}
```
Calling `asset_create` creates a new Smart ASA and returns the identifier of the
ASA. The [metadata section](#metadata) describes its required properties.
> Upon a call to `asset_create`, a reference implementation SHOULD:
>
> - Mint an Algorand Standard Asset (ASA) that MUST specify the properties
> defined in the [metadata section](#metadata). In addition:
> - The `manager`, `reserve` and `freeze` addresses SHOULD be set to the
> account of the controlling Smart Contract.
> - The remaining fields are left to the implementation, which MAY set `total`
> to `2 ** 64 - 1` to enable dynamically increasing the max circulating
> supply of the Smart ASA.
> - `name` and `unit_name` MAY be set to `SMART-ASA` and `S-ASA`, to
> denote that this ASA is Smart and has a controlling application.
> - Persist the `total`, `decimals`, `default_frozen`, etc. fields for later
> use/retrieval.
> - Return the ID of the created ASA.
>
> It is RECOMMENDED for calls to this method to be permissioned, e.g. to only
> approve transactions issued by the controlling Smart Contract creator.
#### Asset Configuration
```json
[
{
"name": "asset_config",
"args": [
{ "type": "asset", "name": "config_asset" },
{ "type": "uint64", "name": "total" },
{ "type": "uint32", "name": "decimals" },
{ "type": "bool", "name": "default_frozen" },
{ "type": "string", "name": "unit_name" },
{ "type": "string", "name": "name" },
{ "type": "string", "name": "url" },
{ "type": "byte[]", "name": "metadata_hash" },
{ "type": "address", "name": "manager_addr" },
{ "type": "address", "name": "reserve_addr" },
{ "type": "address", "name": "freeze_addr" },
{ "type": "address", "name": "clawback_addr" }
],
"returns": { "type": "void" }
},
{
"name": "get_asset_config",
"readonly": true,
"args": [{ "type": "asset", "name": "asset" }],
"returns": {
"type": "(uint64,uint32,bool,string,string,string,byte[],address,address,address,address)",
"desc": "`total`, `decimals`, `default_frozen`, `unit_name`, `name`, `url`, `metadata_hash`, `manager_addr`, `reserve_addr`, `freeze_addr`, `clawback`"
}
}
]
```
Calling `asset_config` configures an existing Smart ASA.
> Upon a call to `asset_config`, a reference implementation SHOULD:
>
> - Fail if `config_asset` does not correspond to an ASA controlled by this smart
> contract.
> - Succeed iff the `sender` of the transaction corresponds to the `manager_addr`
> that was previously persisted for `config_asset` by a previous call to this
> method or, if never caller, to `asset_create`.
> - Update the persisted `total`, `decimals`, `default_frozen`, etc. fields for later
> use/retrieval.
>
> The business logic associated to the update of the other parameters is left to
> the implementation. An implementation that maximizes similarities with ASAs,
> SHOULD NOT allow modifying the `clawback_addr` or `freeze_addr` after they
> have been set to the special value `ZeroAddress`.
>
> The implementation MAY provide flexibility on the fields of an ASA that
> cannot be updated after initial configuration. For instance, it MAY update the
> `total` parameter to enable minting of new units or restricting the maximum
> supply; when doing so, the implementation SHOULD ensure that the updated
> `total` is not lower than the current circulating supply of the asset.
Calling `get_asset_config` reads and returns the `asset`'s configuration as specified in:
- The most recent invocation of `asset_config`; or
- if `asset_config` was never invoked for `asset`, the invocation of
`asset_create` that originally created it.
> Upon a call to `get_asset_config`, a reference implementation SHOULD:
>
> - Fail if `asset` does not correspond to an ASA controlled by this smart
> contract (see `asset_config`).
> - Return `total`, `decimals`, `default_frozen`, `unit_name`, `name`, `url`,
> `metadata_hash`, `manager_addr`, `reserve_addr`, `freeze_addr`, `clawback`
> as persisted by `asset_create` or `asset_config`.
#### Asset Transfer
```json
{
"name": "asset_transfer",
"args": [
{ "type": "asset", "name": "xfer_asset" },
{ "type": "uint64", "name": "asset_amount" },
{ "type": "account", "name": "asset_sender" },
{ "type": "account", "name": "asset_receiver" }
],
"returns": { "type": "void" }
}
```
Calling `asset_transfer` transfers a Smart ASA.
> Upon a call to `asset_transfer`, a reference implementation SHOULD:
>
> - Fail if `xfer_asset` does not correspond to an ASA controlled by this smart
> contract.
> - Succeed if:
> - the `sender` of the transaction is the `asset_sender` and
> - `xfer_asset` is not in a frozen state
> (see [Asset Freeze below](#asset-freeze)) and
> - `asset_sender` and `asset_receiver` are not in a frozen state
> (see [Asset Freeze below](#asset-freeze))
> - Succeed if the `sender` of the transaction corresponds to the `clawback_addr`,
> as persisted by the controlling Smart Contract. This enables clawback
> operations on the Smart ASA.
>
> Internally, the controlling Smart Contract SHOULD issue a clawback inner
> transaction that transfers the `asset_amount` from `asset_sender` to
> `asset_receiver`. The inner transaction will fail on the usual conditions (e.g.
> not enough balance).
>
> Note that the method interface does not specify `asset_close_to`, because
> holders of a Smart ASA will need two transactions (RECOMMENDED in an Atomic
> Transfer) to close their position:
>
> - A call to this method to transfer their outstanding balance (possibly as a
> `CloseOut` operation if the controlling Smart Contract required opt in); and
> - an additional transaction to close out of the ASA.
#### Asset Freeze
```json
[
{
"name": "asset_freeze",
"args": [
{ "type": "asset", "name": "freeze_asset" },
{ "type": "bool", "name": "asset_frozen" }
],
"returns": { "type": "void" }
},
{
"name": "account_freeze",
"args": [
{ "type": "asset", "name": "freeze_asset" },
{ "type": "account", "name": "freeze_account" },
{ "type": "bool", "name": "asset_frozen" }
],
"returns": { "type": "void" }
}
]
```
Calling `asset_freeze` prevents any transfer of a Smart ASA. Calling
`account_freeze` prevents a specific account from transferring or receiving a
Smart ASA.
> Upon a call to `asset_freeze` or `account_freeze`, a reference implementation
> SHOULD:
>
> - Fail if `freeze_asset` does not correspond to an ASA controlled by this smart
> contract.
> - Succeed iff the `sender` of the transaction corresponds to the `freeze_addr`,
> as persisted by the controlling Smart Contract.
>
> In addition:
>
> - Upon a call to `asset_freeze`, the controlling Smart Contract SHOULD persist
> the tuple `(freeze_asset, asset_frozen)` (for instance, by setting a `frozen`
> flag in _global_ storage).
> - Upon a call to `account_freeze` the controlling Smart Contract SHOULD persist
> the tuple `(freeze_asset, freeze_account, asset_frozen)` (for instance by
> setting a `frozen` flag in the _local_ storage of the `freeze_account`). See
> the [security considerations section](#security-considerations) for how to
> ensure that Smart ASA holders cannot reset their `frozen` flag by clearing out
> their state at the controlling Smart Contract.
```json
[
{
"name": "get_asset_is_frozen",
"readonly": true,
"args": [{ "type": "asset", "name": "freeze_asset" }],
"returns": { "type": "bool" }
},
{
"name": "get_account_is_frozen",
"readonly": true,
"args": [
{ "type": "asset", "name": "freeze_asset" },
{ "type": "account", "name": "freeze_account" }
],
"returns": { "type": "bool" }
}
]
```
The value return by `get_asset_is_frozen` (respectively,
`get_account_is_frozen`) tells whether any account (respectively
`freeze_account`) can transfer or receive `freeze_asset`. A `false` value
indicates that the transfer will be rejected.
> Upon a call to `get_asset_is_frozen`, a reference implementation SHOULD
> retrieve the tuple `(freeze_asset, asset_frozen)` as stored on `asset_freeze`
> and return the value corresponding to `asset_frozen`.
> Upon a call to `get_account_is_frozen`, a reference implementation SHOULD
> retrieve the tuple `(freeze_asset, freeze_account, asset_frozen)` as
> stored on `account_freeze` and return the value corresponding to
> `asset_frozen`.
#### Asset Destroy
```json
{
"name": "asset_destroy",
"args": [{ "type": "asset", "name": "destroy_asset" }],
"returns": { "type": "void" }
}
```
Calling `asset_destroy` destroys a Smart ASA.
> Upon a call to `asset_destroy`, a reference implementation SHOULD:
>
> - Fail if `destroy_asset` does not correspond to an ASA controlled by this smart
> contract.
>
> It is RECOMMENDED for calls to this method to be permissioned (see
> `asset_create`).
>
> The controlling Smart Contract SHOULD perform an asset destroy operation on
> the ASA with ID `destroy_asset`. The operation will fail if the asset is still
> in circulation.
#### Circulating Supply
```json
{
"name": "get_circulating_supply",
"readonly": true,
"args": [{ "type": "asset", "name": "asset" }],
"returns": { "type": "uint64" }
}
```
Calling `get_circulating_supply` returns the circulating supply of a Smart ASA.
> Upon a call to `get_circulating_supply`, a reference implementation SHOULD:
>
> - Fail if `asset` does not correspond to an ASA controlled by this smart
> contract.
> - Return the circulating supply of `asset`, defined by the difference between
> the ASA `total` and the balance held by its `reserve_addr` (see [Asset
> Creation](#asset-creation)).
#### Full ABI Spec
```json
{
"name": "arc-0020",
"methods": [
{
"name": "asset_create",
"args": [
{
"type": "uint64",
"name": "total"
},
{
"type": "uint32",
"name": "decimals"
},
{
"type": "bool",
"name": "default_frozen"
},
{
"type": "string",
"name": "unit_name"
},
{
"type": "string",
"name": "name"
},
{
"type": "string",
"name": "url"
},
{
"type": "byte[]",
"name": "metadata_hash"
},
{
"type": "address",
"name": "manager_addr"
},
{
"type": "address",
"name": "reserve_addr"
},
{
"type": "address",
"name": "freeze_addr"
},
{
"type": "address",
"name": "clawback_addr"
}
],
"returns": {
"type": "uint64"
}
},
{
"name": "asset_config",
"args": [
{
"type": "asset",
"name": "config_asset"
},
{
"type": "uint64",
"name": "total"
},
{
"type": "uint32",
"name": "decimals"
},
{
"type": "bool",
"name": "default_frozen"
},
{
"type": "string",
"name": "unit_name"
},
{
"type": "string",
"name": "name"
},
{
"type": "string",
"name": "url"
},
{
"type": "byte[]",
"name": "metadata_hash"
},
{
"type": "address",
"name": "manager_addr"
},
{
"type": "address",
"name": "reserve_addr"
},
{
"type": "address",
"name": "freeze_addr"
},
{
"type": "address",
"name": "clawback_addr"
}
],
"returns": {
"type": "void"
}
},
{
"name": "get_asset_config",
"readonly": true,
"args": [
{
"type": "asset",
"name": "asset"
}
],
"returns": {
"type": "(uint64,uint32,bool,string,string,string,byte[],address,address,address,address)",
"desc": "`total`, `decimals`, `default_frozen`, `unit_name`, `name`, `url`, `metadata_hash`, `manager_addr`, `reserve_addr`, `freeze_addr`, `clawback`"
}
},
{
"name": "asset_transfer",
"args": [
{
"type": "asset",
"name": "xfer_asset"
},
{
"type": "uint64",
"name": "asset_amount"
},
{
"type": "account",
"name": "asset_sender"
},
{
"type": "account",
"name": "asset_receiver"
}
],
"returns": {
"type": "void"
}
},
{
"name": "asset_freeze",
"args": [
{
"type": "asset",
"name": "freeze_asset"
},
{
"type": "bool",
"name": "asset_frozen"
}
],
"returns": {
"type": "void"
}
},
{
"name": "account_freeze",
"args": [
{
"type": "asset",
"name": "freeze_asset"
},
{
"type": "account",
"name": "freeze_account"
},
{
"type": "bool",
"name": "asset_frozen"
}
],
"returns": {
"type": "void"
}
},
{
"name": "get_asset_is_frozen",
"readonly": true,
"args": [
{
"type": "asset",
"name": "freeze_asset"
}
],
"returns": {
"type": "bool"
}
},
{
"name": "get_account_is_frozen",
"readonly": true,
"args": [
{
"type": "asset",
"name": "freeze_asset"
},
{
"type": "account",
"name": "freeze_account"
}
],
"returns": {
"type": "bool"
}
},
{
"name": "asset_destroy",
"args": [
{
"type": "asset",
"name": "destroy_asset"
}
],
"returns": {
"type": "void"
}
},
{
"name": "get_circulating_supply",
"readonly": true,
"args": [
{
"type": "asset",
"name": "asset"
}
],
"returns": {
"type": "uint64"
}
}
]
}
```
### Metadata
#### ASA Metadata
The ASA underlying a Smart ASA:
- MUST be `DefaultFrozen`.
- MUST specify the ID of the controlling Smart Contract (see below); and
- MUST set the `ClawbackAddr` to the account of such Smart Contract.
The metadata **MUST** be immutable.
#### Specifying the controlling Smart Contract
A Smart ASA MUST specify the ID of its controlling Smart Contract.
If the Smart ASA also conforms to any ARC that supports additional `properties`
([ARC-3](./arc-0003.md), [ARC-69](./arc-0069.md)), then it MUST include a
`arc-20` key and set the corresponding value to a map, including the ID of the
controlling Smart Contract as a value for the key `application-id`. For example:
```javascript
{
//...
"properties": {
//...
"arc-20": {
"application-id": 123
}
}
//...
}
```
> To avoid ecosystem fragmentation this ARC does NOT propose any new method to
> specify the metadata of an ASA. Instead, it only extends already
> existing standards.
### Handling opt in and close out
A Smart ASA MUST require users to opt to the ASA and MAY require them to opt in
to the controlling Smart Contract. This MAY be performed at two separate times.
The reminder of this section is non-normative.
> Smart ASAs SHOULD NOT require users to opt in to the controlling Smart
> Contract, unless the implementation requires storing information into their
> local schema (for instance, to implement [freezing](#asset-freeze); also see
> [security considerations](#security-considerations)).
>
> Clients MAY inspect the local state schema of the controlling Smart Contract
> to infer whether opt in is required.
>
> If a Smart ASA requires opt in, then clients SHOULD prevent users from closing
> out the controlling Smart Contract unless they don't hold a balance for any of
> the ASAs controlled by the Smart Contract.
## Rationale
This ARC builds on the strengths of the ASA to enable a Smart Contract to
control its operations and flexibly re-configure its configuration.
The rationale is to have a "Smart ASA" that is as widely adopted as the ASA both
by the community and by the surrounding ecosystem. Wallets, dApps, and
marketplaces:
- Will display a user's Smart ASA balance out-of-the-box (because of the
underlying ASA).
- SHOULD recognize Smart ASAs and inform the users accordingly by displaying the
name, unit name, URL, etc. from the controlling Smart Contract.
- SHOULD enable users to transfer the Smart ASA by constructing the appropriate
transactions, which call the ABI methods of the controlling Smart Contract.
With this in mind, this standard optimizes for:
- Community adoption, by minimizing the [ASA metadata](#metadata) that need to
be set and the requirements of a conforming implementation.
- Developer adoption, by re-using the familiar ASA transaction reference in the
methods' specification.
- Ecosystem integration, by minimizing the amount of work that a wallet, dApp or
service should perform to support the Smart ASA.
## Backwards Compatibility
Existing ASAs MAY adopt this standard if issued or re-configured to match the
requirements in the [metadata section](#metadata).
This requires:
- The ASA to be `DefaultFrozen`.
- Deploying a Smart Contract that will manage, control and operate on the
asset(s).
- Re-configuring the ASA, by setting its `ClawbackAddr` to the account of the
controlling Smart Contract.
- Associating the ID of the Smart Contract to the ASA (see
[metadata](#metadata)).
### [ARC-18](./arc-0018.md)
Assets implementing [ARC-18](./arc-0018.md) MAY also be compatible with this ARC
if the Smart Contract implementing royalties enforcement exposes the ABI methods
specified here. The corresponding ASA and their metadata are compliant with this
standard.
## Reference Implementation
A reference implementation is available here:
https://github.com/algorandlabs/smart-asa.
## Security Considerations
Keep in mind that the rules governing a Smart ASA are only in place as long as:
- The ASA remains frozen;
- the `ClawbackAddr` of the ASA is set to a controlling Smart Contract, as
specified in the [metadata section](#metadata);
- the controlling Smart Contract is not updatable, nor deletable, nor
re-keyable.
### Local State
If your controlling Smart Contract implementation writes information to a user's
local state, keep in mind that users can close out the application and (worse)
clear their state at all times. This requires careful considerations.
For instance, if you determine a user's [freeze](#asset-freeze) state by reading
a flag from their local state, you should consider the flag _set_ and the user
_frozen_ if the corresponding local state key is _missing_.
For a `default_frozen` Smart ASA this means:
- Set the `frozen` flag (to `1`) at opt in.
- Explicitly verify that a user's `frozen` flag is not set (is `0`) before
approving transfers.
- If the key `frozen` is missing from the user's local state, then considered
the flag to be set and reject all transfers.
This prevents users from resetting their `frozen` flag by clearing their state
and then opting into the controlling Smart Contract again.
## Copyright
Copyright and related rights waived via <a href="https://creativecommons.org/publicdomain/zero/1.0/">CCO</a>.
```
--------------------------------------------------------------------------------
/packages/server/src/resources/knowledge/taxonomy/ARCs:specs:arc-0055.md:
--------------------------------------------------------------------------------
```markdown
---
arc: 55
title: On-Chain storage/transfer for Multisig
description: A smart contract that stores transactions and signatures for simplified multisignature use on Algorand.
author: Steve Ferrigno (@nullun)
discussions-to: https://github.com/algorandfoundation/ARCs/issues/254
status: Final
type: Standards Track
category: Interface
sub-category: Wallet
created: 2023-10-16
requires: 4, 28
---
## Abstract
This ARC proposes the utilization of on-chain smart contracts to facilitate the storage and transfer of Algorand multisignature metadata, transactions, and corresponding signatures for the respective multisignature sub-accounts.
## Motivation
Multisignature (multisig) accounts play a crucial role in enhancing security and control within the Algorand ecosystem. However, the management of multisig accounts often involves intricate off-chain coordination and the distribution of transactions among authorized signers. There exists a pressing need for a more streamlined and simplified approach to multisig utilization, along with an efficient transaction signing workflow.
## 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>.
### ABI
A compliant smart contract, conforming to this ARC, **MUST** implement the following interface:
```json
{
"name": "ARC-55",
"desc": "On-Chain Msig App",
"methods": [
{
"name": "arc55_getThreshold",
"desc": "Retrieve the signature threshold required for the multisignature to be submitted",
"readonly": true,
"args": [],
"returns": {
"type": "uint64",
"desc": "Multisignature threshold"
}
},
{
"name": "arc55_getAdmin",
"desc": "Retrieves the admin address, responsible for calling arc55_setup",
"readonly": true,
"args": [],
"returns": {
"type": "address",
"desc": "Admin address"
}
},
{
"name": "arc55_nextTransactionGroup",
"readonly": true,
"args": [],
"returns": {
"type": "uint64",
"desc": "Next expected Transaction Group nonce"
}
},
{
"name": "arc55_getTransaction",
"desc": "Retrieve a transaction from a given transaction group",
"readonly": true,
"args": [
{
"name": "transactionGroup",
"type": "uint64",
"desc": "Transaction Group nonce"
},
{
"name": "transactionIndex",
"type": "uint8",
"desc": "Index of transaction within group"
}
],
"returns": {
"type": "byte[]",
"desc": "A single transaction at the specified index for the transaction group nonce"
}
},
{
"name": "arc55_getSignatures",
"desc": "Retrieve a list of signatures for a given transaction group nonce and address",
"readonly": true,
"args": [
{
"name": "transactionGroup",
"type": "uint64",
"desc": "Transaction Group nonce"
},
{
"name": "signer",
"type": "address",
"desc": "Address you want to retrieve signatures for"
}
],
"returns": {
"type": "byte[64][]",
"desc": "Array of signatures"
}
},
{
"name": "arc55_getSignerByIndex",
"desc": "Find out which address is at this index of the multisignature",
"readonly": true,
"args": [
{
"name": "index",
"type": "uint64",
"desc": "Address at this index of the multisignature"
}
],
"returns": {
"type": "address",
"desc": "Address at index"
}
},
{
"name": "arc55_isSigner",
"desc": "Check if an address is a member of the multisignature",
"readonly": true,
"args": [
{
"name": "address",
"type": "address",
"desc": "Address to check is a signer"
}
],
"returns": {
"type": "bool",
"desc": "True if address is a signer"
}
},
{
"name": "arc55_mbrSigIncrease",
"desc": "Calculate the minimum balance requirement for storing a signature",
"readonly": true,
"args": [
{
"name": "signaturesSize",
"type": "uint64",
"desc": "Size (in bytes) of the signatures to store"
}
],
"returns": {
"type": "uint64",
"desc": "Minimum balance requirement increase"
}
},
{
"name": "arc55_mbrTxnIncrease",
"desc": "Calculate the minimum balance requirement for storing a transaction",
"readonly": true,
"args": [
{
"name": "transactionSize",
"type": "uint64",
"desc": "Size (in bytes) of the transaction to store"
}
],
"returns": {
"type": "uint64",
"desc": "Minimum balance requirement increase"
}
},
{
"name": "arc55_setup",
"desc": "Setup On-Chain Msig App. This can only be called whilst no transaction groups have been created.",
"args": [
{
"name": "threshold",
"type": "uint8",
"desc": "Initial multisig threshold, must be greater than 0"
},
{
"name": "addresses",
"type": "address[]",
"desc": "Array of addresses that make up the multisig"
}
],
"returns": {
"type": "void"
}
},
{
"name": "arc55_newTransactionGroup",
"desc": "Generate a new transaction group nonce for holding pending transactions",
"args": [],
"returns": {
"type": "uint64",
"desc": "transactionGroup Transaction Group nonce"
}
},
{
"name": "arc55_addTransaction",
"desc": "Add a transaction to an existing group. Only one transaction should be included per call",
"args": [
{
"name": "costs",
"type": "pay",
"desc": "Minimum Balance Requirement for associated box storage costs: (2500) + (400 * (9 + transaction.length))"
},
{
"name": "transactionGroup",
"type": "uint64",
"desc": "Transaction Group nonce"
},
{
"name": "index",
"type": "uint8",
"desc": "Transaction position within atomic group to add"
},
{
"name": "transaction",
"type": "byte[]",
"desc": "Transaction to add"
}
],
"returns": {
"type": "void"
},
"events": [
{
"name": "TransactionAdded",
"args": [
{
"name": "transactionGroup",
"type": "uint64"
},
{
"name": "transactionIndex",
"type": "uint8"
}
],
"desc": "Emitted when a new transaction is added to a transaction group"
}
]
},
{
"name": "arc55_addTransactionContinued",
"args": [
{
"name": "transaction",
"type": "byte[]"
}
],
"returns": {
"type": "void"
}
},
{
"name": "arc55_removeTransaction",
"desc": "Remove transaction from the app. The MBR associated with the transaction will be returned to the transaction sender.",
"args": [
{
"name": "transactionGroup",
"type": "uint64",
"desc": "Transaction Group nonce"
},
{
"name": "index",
"type": "uint8",
"desc": "Transaction position within atomic group to remove"
}
],
"returns": {
"type": "void"
},
"events": [
{
"name": "TransactionRemoved",
"args": [
{
"name": "transactionGroup",
"type": "uint64"
},
{
"name": "transactionIndex",
"type": "uint8"
}
],
"desc": "Emitted when a transaction has been removed from a transaction group"
}
]
},
{
"name": "arc55_setSignatures",
"desc": "Set signatures for a particular transaction group. Signatures must be included as an array of byte-arrays",
"args": [
{
"name": "costs",
"type": "pay",
"desc": "Minimum Balance Requirement for associated box storage costs: (2500) + (400 * (40 + signatures.length))"
},
{
"name": "transactionGroup",
"type": "uint64",
"desc": "Transaction Group nonce"
},
{
"name": "signatures",
"type": "byte[64][]",
"desc": "Array of signatures"
}
],
"returns": {
"type": "void"
},
"events": [
{
"name": "SignatureSet",
"args": [
{
"name": "transactionGroup",
"type": "uint64"
},
{
"name": "signer",
"type": "address"
}
],
"desc": "Emitted when a new signature is added to a transaction group"
}
]
},
{
"name": "arc55_clearSignatures",
"desc": "Clear signatures for an address. Be aware this only removes it from the current state of the ledger, and indexers will still know and could use your signature",
"args": [
{
"name": "transactionGroup",
"type": "uint64",
"desc": "Transaction Group nonce"
},
{
"name": "address",
"type": "address",
"desc": "Address whose signatures to clear"
}
],
"returns": {
"type": "void"
},
"events": [
{
"name": "SignatureSet",
"args": [
{
"name": "transactionGroup",
"type": "uint64"
},
{
"name": "signer",
"type": "address"
}
],
"desc": "Emitted when a new signature is added to a transaction group"
}
]
},
{
"name": "createApplication",
"args": [],
"returns": {
"type": "void"
}
}
],
"events": [
{
"name": "TransactionAdded",
"args": [
{
"name": "transactionGroup",
"type": "uint64"
},
{
"name": "transactionIndex",
"type": "uint8"
}
],
"desc": "Emitted when a new transaction is added to a transaction group"
},
{
"name": "TransactionRemoved",
"args": [
{
"name": "transactionGroup",
"type": "uint64"
},
{
"name": "transactionIndex",
"type": "uint8"
}
],
"desc": "Emitted when a transaction has been removed from a transaction group"
},
{
"name": "SignatureSet",
"args": [
{
"name": "transactionGroup",
"type": "uint64"
},
{
"name": "signer",
"type": "address"
}
],
"desc": "Emitted when a new signature is added to a transaction group"
},
{
"name": "SignatureCleared",
"args": [
{
"name": "transactionGroup",
"type": "uint64"
},
{
"name": "signer",
"type": "address"
}
],
"desc": "Emitted when a signature has been removed from a transaction group"
}
]
}
```
### Usage
The deployment of an [ARC-55](./arc-0055.md)-compliant contract is not covered by the ARC and is instead left to the implementer for their own use-case. An internal function `arc55_setAdmin` **SHOULD** be used to initialize an address which will be administering the setup. If left unset, then the admin defaults to the creator address. Once the application exists on-chain it must be setup before it can be used. The ARC-55 admin is responsible for setting up the multisignature metadata using the `arc55_setup(uint8,address[])void` method, and passing in details about the signature threshold and signer accounts that will make up the multisignature address. After successful deployment and configuration, the application ID **SHOULD** be distributed among the involved parties (signers) as a one-time off-chain exchange. The setup process may be called multiple times to correct any changes to the multisignature metadata, as long as no one has created a new transaction group nonce. Once a transaction group nonce has been generated, the metadata is immutable.
Before any transactions or signatures can be stored, a new "transaction group nonce" must be generated using the `arc55_newTransactionGroup()uint64` method. This returns a unique value which **MUST** be used for all further [ARC-55](./arc-0055.md) interactions. This nonce value allows multiple pending transactions groups to be available simultaneously under the same contract deployment. Do note confuse this value with a transaction group hash. It's entirely possible to add multiple non-grouped, or multiple different groups into a single transaction group nonce, up to a limit of 255 transactions. However it's unlikely ARC-55 clients will facilitate this.
Using a transaction group nonce, the admin or any signer **MAY** add transactions one at a time to that transaction group by providing the transaction data and the index of that transaction within the group using `arc55_addTransaction(pay,uint64,uint8,byte[])void`. A mandatory payment transaction **MUST** be included before the application call and will contain any minimum balance requirements as a result of storing the transaction data. When adding transactions the index **MUST** start at 0. Once a transaction has successfully be used or is no longer needed, any signer **MAY** remove the transaction data from the group using the `arc55_removeTransaction(uint64,uint8)void` method. This will result in the minimum balance requirement being freed up and being sent to the transaction sender.
Signers **MAY** provide their signature for a particular transaction group by using the `arc55_setSignatures(pay,uint64,byte[64][])void` method. This requires paying the minimum balance requirement used to store their signature and will be returned to them once their signature is removed. Any signer **MAY** also remove their own or others signatures from the contract using the `arc55_clearSignatures(uint64)void` method, however this may not prevent someone from using that signature. Once a signature has been shared publicly, anyone can use it assuming they meet the signature threshold to submit the transaction.
Once a transaction receives enough signatures to meet the threshold and falls within the valid rounds of the transaction, anyone **MAY** construct the multisignature transaction, by including all the signatures and submitting it to the network. Subsequently, participants **SHOULD** now clear the signatures and transaction data from the contract.
Whilst it's not part of the ARC, an [ARC-55](./arc-0055.md)-compliant contract **MAY** be destroyed once it is no longer needed. The process **SHOULD** be performed by the admin and/or application creator, by first reclaiming any outstanding Algo funds by removing transactions and clearing signatures, which avoids permanently locking Algo on the network. Then issuing the `DeleteApplication` call and closing out the application address. It's important to note that destroying the application does not render the multisignature account inaccessible, as a new deployment with the same multisignature metadata can be configured and used.
Below is a typical expected lifecycle:
* Creator deploys an ARC-55 compliant smart contract.
* Admin performs setup: Setting threshold to 2, and including 2 signer addresses.
* Either signer can now generate a new transaction group.
* Either signer can add a new transaction to sign to the transaction group, providing the MBR.
* Signer 1 provides their signatures to the transaction group, providing their MBR.
* Signer 2 provides their signatures to the transaction group, providing their MBR.
* Anyone can now submit the transaction to the network.
* Either signer can now clear the signatures of each signer, refunding their MBR to each account.
* Either signer can remove the transaction since it's now committed to the network, refunding the MBR to the transaction sender.
### Storage
```
n = Transaction group nonce (uint64)
i = Transaction index within group (uint8)
addr = signers address (byte[32])
```
| Type | Key | Value | Description |
|--------|-------------------|---------|----------------------------------------------------------------|
| Global | `arc55_threshold` | uint64 | The multisig signature threshold |
| Global | `arc55_nonce` | uint64 | The ARC-55 transaction group nonce |
| Global | `arc55_admin` | Address | The admin responsible for calling `arc55_setup` |
| Box | n+i | byte[] | The ith transaction data for the nth transaction group nonce |
| Box | n+addr | byte[] | The signatures for the nth transaction group |
| Global | uint8 | Address | The signer address index for the multisig |
| Global | Address | uint64 | The number of times this signer appears in the multisig |
Whilst the data can be read directly from the applications storage, there are also read-only method for use with Algod's simulate to retrieve the data. Below is a summary of each piece of data, how and where it's stored, and it's associated method call.
#### Threshold
The threshold is stored in global state of the application as a uint64 value. It's immutable after setup and the first transaction group nonce has been generated.
The associated read-only method is `arc55_getThreshold()uint64`, which will return the signature threshold for the multisignature account.
#### Multisig Signer Addresses
A multisignature address is made up of one or more addresses. The contract stores these addresses in global state twice. Once as the positional index, and a second time to identify how many times they're being used. This allows for simpler on-chain processing within the smart contract to identify 1) if the account is used, and 2) where the account should be used when reconstructing the multisignature.
Their are two associated read-only methods for obtaining and checking multisignature signer addresses. To retrieve a list of index addresses, you **SHOULD** use `arc55_getSignerByIndex(uint64)address`, which will return the signer address at the given multisignature index. This can be done incrementally until you reach the end of the available indexes. To check if an address is a signer for the multisignature account, you **SHOULD** use `arc55_isSigner(address)boolean`, which will return a `true` or `false` value.
#### Transactions
All transactions are stored individually within boxes, where the name of the box are separately identified by their related transaction group nonce. The box names are a concatenation of a uint64 and a uint8, representing the transaction group nonce and transaction index. This allows off-chain services to list all boxes belonging to an application and can quickly group and identify how many transaction groups and transactions are available.
The associated read-only method is `arc55_getTransaction(uint64,uint8)byte[]`, which will return the transaction for a given transaction group nonce and transaction index. Note: To retrieve data larger than 1024 bytes, simulate must be called with `AllowMoreLogging` set to true.
Example
Group Transaction Nonce: `1` (uint64)
Transaction Index: `0` (uint8)
Hex: `000000000000000100`
Box name: `AAAAAAAAAAEA` (base64)
#### Signatures
Signers store their signatures in a single box per transaction group nonce. Where multiple signatures **MUST** be concatenated together in the same order as the transactions within the group. The box name is made up of the transaction group nonce and the signers public key. Which is later used when removing the signatures, to identify where to refund the minimum balance requirement to.
The associated read-only method is `arc55_getSignatures(uint64,address)byte[64][]`, which will return the signatures for a given transaction group nonce and signer address.
Example
Group Transaction Nonce: `1` (uint64)
Signer: `ALICE7Y2JOFGG2VGUC64VINB75PI56O6M2XW233KG2I3AIYJFUD4QMYTJM` (address)
Hex: `000000000000000102d0227f1a4b8a636aa6a0bdcaa1a1ff5e8ef9de66af6d6f6a3691b023092d07`
Box name: `AAAAAAAAAAEC0CJ/GkuKY2qmoL3KoaH/Xo753mavbW9qNpGwIwktBw==` (base64)
## Rationale
Establishing individual deployments for distinct user groups, as opposed to relying on a singular instance accessible to all, presents numerous advantages. Initially, this approach facilitates the implementation and expansion of functionalities well beyond the scope initially envisioned by the ARC. It enables the integration of entirely customized smart contracts that adhere to [ARC-55](./arc-0055.md) while avoiding being constrained by it.
Furthermore, in the context of third-party infrastructures, the management of numerous boxes for a singular monolithic application can become increasingly cumbersome over time. In contrast, empowering small groups to create their own multisig applications, they can subscribe exclusively to their unique application ID streamlining the monitoring of it for new transactions and signatures.
### Limitations and Design Decisions
The available transaction size is the most critical limitation within this implementation. For transactions larger than 2048 bytes (the maximum application argument size), additional transactions using the method `arc55_addTransactionContinued(byte[])void` can be used and sent within the same group as the `arc55_addTransaction(pay,uint64,uint8,byte[])void` call. This will allow the storing of up to 4096 bytes per transaction. Note: The minimum balance requirement must be paid in full by the preceding payment transaction of the `addTransaction` call.
This ARC inherently promotes transparency of transactions and signers. If an additional layer of anonymity is required, an extension to this ARC **SHOULD** be proposed, outlining how to store and share encrypted data.
The current design necessitates that all transactions within the group be exclusively signed by the constituents of the multisig account. If a group transaction requires a separate signature from another account or a logicsig, this design does not support it. An extension to this ARC **SHOULD** be considered to address such scenarios.
## Reference Implementation
A TEALScript reference implementation is available at <a href="https://github.com/nullun/arc55-msig-app">`github.com/nullun/arc55-msig-app`</a>. This version has been written as an inheritable class, so can be included on top of an existing project to give you an ARC-55-compliant interface. It is encouraged for others to implement this standard in their preferred smart contract language of choice and even extend the capabilities whilst adhering to the provided ABI specification.
## Security Considerations
This ARC's design solely involves storing existing data structures and does not have the capability to create or use multisignature accounts. Therefore, the security implications are minimal. End users are expected to review each transaction before generating a signature for it. If a smart contract implementing this ARC lacks proper security checks, the worst-case scenario would involve incorrect transactions and invalid signatures being stored on-chain, along with the potential loss of the minimum balance requirement from the application account.
## Copyright
Copyright and related rights waived via <a href="https://creativecommons.org/publicdomain/zero/1.0/">CCO</a>.
```
--------------------------------------------------------------------------------
/packages/server/src/resources/knowledge/taxonomy/ARCs:specs:arc-0059.md:
--------------------------------------------------------------------------------
```markdown
---
arc: 59
title: ASA Inbox Router
description: An application that can route ASAs to users or hold them to later be claimed
author: Kadir Can Çetin (@kadircancetin), Yigit Guler (@yigitguler), Joe Polny (@joe-p), Kevin Wellenzohn (@k13n), Brian Whippo (@silentrhetoric)
discussions-to: https://github.com/algorandfoundation/ARCs/issues/285
status: Final
type: Standards Track
category: ARC
sub-category: Wallet
created: 2024-03-08
requires: 4
---
## Abstract
The goal of this standard is to establish a standard in the Algorand ecosystem by which ASAs can be sent to an intended receiver even if their account is not opted in to the ASA.
A wallet custodied by an application will be used to custody assets on behalf of a given user, with only that user being able to withdraw assets. A master application will be used to map inbox addresses to user address. This master application can route ASAs to users performing whatever actions are necessary.
If integrated into ecosystem technologies including wallets, explorers, and dApps, this standard can provide enhanced capabilities around ASAs which are otherwise strictly bound at the protocol level to require opting in to be received.
## Motivation
Algorand requires accounts to opt in to receive any ASA, a fact which simultaneously:
1. Grants account holders fine-grained control over their holdings by allowing them to select which assets to allow and preventing receipt of unwanted tokens.
2. Frustrates users and developers when accounting for this requirement especially since other blockchains do not have this requirement.
This ARC lays out a new way to navigate the ASA opt in requirement.
### Contemplated Use Cases
The following use cases help explain how this capability can enhance the possibilities within the Algorand ecosystem.
#### Airdrops
An ASA creator who wants to send their asset to a set of accounts faces the challenge of needing their intended receivers to opt in to the ASA ahead of time, which requires non-trivial communication efforts and precludes the possibility of completing the airdrop as a surprise. This claimable ASA standard creates the ability to send an airdrop out to individual addresses so that the receivers can opt in and claim the asset at their convenience--or not, if they so choose.
#### Reducing New User On-boarding Friction
An application operator who wants to on-board users to their game or business may want to reduce the friction of getting people started by decoupling their application on-boarding process from the process of funding a non-custodial Algorand wallet, if users are wholly new to the Algorand ecosystem. As long as the receiver's address is known, an ASA can be sent to them ahead of them having ALGOs in their wallet to cover the minimum balance requirement and opt in to the asset.
## 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>.
### Router Contract Interface
```json
{
"name": "ARC59",
"desc": "",
"methods": [
{
"name": "createApplication",
"desc": "Deploy ARC59 contract",
"args": [],
"returns": {
"type": "void"
}
},
{
"name": "arc59_optRouterIn",
"desc": "Opt the ARC59 router into the ASA. This is required before this app can be used to send the ASA to anyone.",
"args": [
{
"name": "asa",
"type": "uint64",
"desc": "The ASA to opt into"
}
],
"returns": {
"type": "void"
}
},
{
"name": "arc59_getOrCreateInbox",
"desc": "Gets the existing inbox for the receiver or creates a new one if it does not exist",
"args": [
{
"name": "receiver",
"type": "address",
"desc": "The address to get or create the inbox for"
}
],
"returns": {
"type": "address",
"desc": "The inbox address"
}
},
{
"name": "arc59_getSendAssetInfo",
"args": [
{
"name": "receiver",
"type": "address",
"desc": "The address to send the asset to"
},
{
"name": "asset",
"type": "uint64",
"desc": "The asset to send"
}
],
"returns": {
"type": "(uint64,uint64,bool,bool,uint64)",
"desc": "Returns the following information for sending an asset:The number of itxns required, the MBR required, whether the router is opted in, and whether the receiver is opted in"
}
},
{
"name": "arc59_sendAsset",
"desc": "Send an asset to the receiver",
"args": [
{
"name": "axfer",
"type": "axfer",
"desc": "The asset transfer to this app"
},
{
"name": "receiver",
"type": "address",
"desc": "The address to send the asset to"
},
{
"name": "additionalReceiverFunds",
"type": "uint64",
"desc": "The amount of ALGO to send to the receiver/inbox in addition to the MBR"
}
],
"returns": {
"type": "address",
"desc": "The address that the asset was sent to (either the receiver or their inbox)"
}
},
{
"name": "arc59_claim",
"desc": "Claim an ASA from the inbox",
"args": [
{
"name": "asa",
"type": "uint64",
"desc": "The ASA to claim"
}
],
"returns": {
"type": "void"
}
},
{
"name": "arc59_reject",
"desc": "Reject the ASA by closing it out to the ASA creator. Always sends two inner transactions.All non-MBR ALGO balance in the inbox will be sent to the caller.",
"args": [
{
"name": "asa",
"type": "uint64",
"desc": "The ASA to reject"
}
],
"returns": {
"type": "void"
}
},
{
"name": "arc59_getInbox",
"desc": "Get the inbox address for the given receiver",
"args": [
{
"name": "receiver",
"type": "address",
"desc": "The receiver to get the inbox for"
}
],
"returns": {
"type": "address",
"desc": "Zero address if the receiver does not yet have an inbox, otherwise the inbox address"
}
},
{
"name": "arc59_claimAlgo",
"desc": "Claim any extra algo from the inbox",
"args": [],
"returns": {
"type": "void"
}
}
]
}
```
### Sending an Asset
When sending an asset, the sender **SHOULD** call `ARC59_getSendAssetInfo` to determine relevant information about the receiver and the router. This information is included as a tuple described below
| Index | Object Property | Description | Type |
| ----- | -------------------------- | -------------------------------------------------------------------------------- | ------ |
| 0 | itxns | The number of itxns required | uint64 |
| 1 | mbr | The amount of ALGO the sender **MUST** send the the router contract to cover MBR | uint64 |
| 2 | routerOptedIn | Whether the router is already opted in to the asset | bool |
| 3 | receiverOptedIn | Whether the receiver is already directly opted in to the asset | bool |
| 4 | receiverAlgoNeededForClaim | The amount of ALGO the receiver would currently need to claim the asset | uint64 |
This information can then be used to send the asset. An example of using this information to send an asset is shown in [the reference implementation section](#typescript-send-asset-function).
### Claiming an Asset
When claiming an asset, the claimer **MUST** call `arc59_claim` to claim the asset from their inbox. This will transfer the asset to the claimer and any extra ALGO in the inbox will be sent to the claimer.
Prior to sending the `arc59_claim` app call, a call to `arc59_claimAlgo` **SHOULD** be made to claim any extra ALGO in the inbox if the inbox balance is above its minimum balance.
An example of claiming an asset is shown in [the reference implementation section](#typescript-claim-function).
## Rationale
This design was created to offer a standard mechanism by which wallets, explorers, and dapps could enable users to send, receive, and find claimable ASAs without requiring any changes to the core protocol.
This ARC is intended to replace [ARC-12](./arc-0012.md). This ARC is simpler than [ARC-12](./arc-0012.md), with the main feature lost being senders not getting back MBR. Given the significant reduction in complexity it is considered to be worth the tradeoff. No way to get back MBR is also another way to disincentivize spam.
### Rejection
The initial proposal for this ARC included a method for burning that leveraged [ARC-54](./arc-0054.md). After further consideration though it was decided to remove the burn functionality with a reject method. The reject method does not burn the ASA. It simply closes out to the creator. This decision was made to reduce the additional complexity and potential user friction that [ARC-54](./arc-0054.md) opt-ins introduced.
### Router MBR
It should be noted that the MBR for the router contract itself is non-recoverable. This was an intentional decision that results in more predictable costs for assets that may freuqently be sent through the router, such as stablecoins.
## Test Cases
Test cases for the JavaScript client and the [ARC-59](./arc-0059.md) smart contract implementation can be found <a href="https://github.com/algorandfoundation/ARCs/tree/main/assets/arc-0059/__test__/">here</a>
## Reference Implementation
A project with a the full reference implementation, including the smart contract and JavaScript library (used for testing), can be found <a href="https://github.com/algorandfoundation/ARCs/tree/main/assets/arc-0059/">here</a>.
### Router Contract
This contract is written using TEALScript v0.90.3
```ts
/* eslint-disable max-classes-per-file */
// eslint-disable-next-line import/no-unresolved, import/extensions
import { Contract } from "@algorandfoundation/tealscript";
type SendAssetInfo = {
/**
* The total number of inner transactions required to send the asset through the router.
* This should be used to add extra fees to the app call
*/
itxns: uint64;
/** The total MBR the router needs to send the asset through the router. */
mbr: uint64;
/** Whether the router is already opted in to the asset or not */
routerOptedIn: boolean;
/** Whether the receiver is already directly opted in to the asset or not */
receiverOptedIn: boolean;
/** The amount of ALGO the receiver would currently need to claim the asset */
receiverAlgoNeededForClaim: uint64;
};
class ControlledAddress extends Contract {
@allow.create("DeleteApplication")
new(): Address {
sendPayment({
rekeyTo: this.txn.sender,
});
return this.app.address;
}
}
export class ARC59 extends Contract {
inboxes = BoxMap<Address, Address>();
/**
* Deploy ARC59 contract
*
*/
createApplication(): void {}
/**
* Opt the ARC59 router into the ASA. This is required before this app can be used to send the ASA to anyone.
*
* @param asa The ASA to opt into
*/
arc59_optRouterIn(asa: AssetID): void {
sendAssetTransfer({
assetReceiver: this.app.address,
assetAmount: 0,
xferAsset: asa,
});
}
/**
* Gets the existing inbox for the receiver or creates a new one if it does not exist
*
* @param receiver The address to get or create the inbox for
* @returns The inbox address
*/
arc59_getOrCreateInbox(receiver: Address): Address {
if (this.inboxes(receiver).exists) return this.inboxes(receiver).value;
const inbox = sendMethodCall<typeof ControlledAddress.prototype.new>({
onCompletion: OnCompletion.DeleteApplication,
approvalProgram: ControlledAddress.approvalProgram(),
clearStateProgram: ControlledAddress.clearProgram(),
});
this.inboxes(receiver).value = inbox;
return inbox;
}
/**
*
* @param receiver The address to send the asset to
* @param asset The asset to send
*
* @returns Returns the following information for sending an asset:
* The number of itxns required, the MBR required, whether the router is opted in, whether the receiver is opted in,
* and how much ALGO the receiver would need to claim the asset
*/
arc59_getSendAssetInfo(receiver: Address, asset: AssetID): SendAssetInfo {
const routerOptedIn = this.app.address.isOptedInToAsset(asset);
const receiverOptedIn = receiver.isOptedInToAsset(asset);
const info: SendAssetInfo = {
itxns: 1,
mbr: 0,
routerOptedIn: routerOptedIn,
receiverOptedIn: receiverOptedIn,
receiverAlgoNeededForClaim: 0,
};
if (receiverOptedIn) return info;
const algoNeededToClaim =
receiver.minBalance + globals.assetOptInMinBalance + globals.minTxnFee;
// Determine how much ALGO the receiver needs to claim the asset
if (receiver.balance < algoNeededToClaim) {
info.receiverAlgoNeededForClaim += algoNeededToClaim - receiver.balance;
}
// Add mbr and transaction for opting the router in
if (!routerOptedIn) {
info.mbr += globals.assetOptInMinBalance;
info.itxns += 1;
}
if (!this.inboxes(receiver).exists) {
// Two itxns to create inbox (create + rekey)
// One itxns to send MBR
// One itxn to opt in
info.itxns += 4;
// Calculate the MBR for the inbox box
const preMBR = globals.currentApplicationAddress.minBalance;
this.inboxes(receiver).value = globals.zeroAddress;
const boxMbrDelta = globals.currentApplicationAddress.minBalance - preMBR;
this.inboxes(receiver).delete();
// MBR = MBR for the box + min balance for the inbox + ASA MBR
info.mbr +=
boxMbrDelta + globals.minBalance + globals.assetOptInMinBalance;
return info;
}
const inbox = this.inboxes(receiver).value;
if (!inbox.isOptedInToAsset(asset)) {
// One itxn to opt in
info.itxns += 1;
if (!(inbox.balance >= inbox.minBalance + globals.assetOptInMinBalance)) {
// One itxn to send MBR
info.itxns += 1;
// MBR = ASA MBR
info.mbr += globals.assetOptInMinBalance;
}
}
return info;
}
/**
* Send an asset to the receiver
*
* @param receiver The address to send the asset to
* @param axfer The asset transfer to this app
* @param additionalReceiverFunds The amount of ALGO to send to the receiver/inbox in addition to the MBR
*
* @returns The address that the asset was sent to (either the receiver or their inbox)
*/
arc59_sendAsset(
axfer: AssetTransferTxn,
receiver: Address,
additionalReceiverFunds: uint64
): Address {
verifyAssetTransferTxn(axfer, {
assetReceiver: this.app.address,
});
// If the receiver is opted in, send directly to their account
if (receiver.isOptedInToAsset(axfer.xferAsset)) {
sendAssetTransfer({
assetReceiver: receiver,
assetAmount: axfer.assetAmount,
xferAsset: axfer.xferAsset,
});
if (additionalReceiverFunds !== 0) {
sendPayment({
receiver: receiver,
amount: additionalReceiverFunds,
});
}
return receiver;
}
const inboxExisted = this.inboxes(receiver).exists;
const inbox = this.arc59_getOrCreateInbox(receiver);
if (additionalReceiverFunds !== 0) {
sendPayment({
receiver: inbox,
amount: additionalReceiverFunds,
});
}
if (!inbox.isOptedInToAsset(axfer.xferAsset)) {
let inboxMbrDelta = globals.assetOptInMinBalance;
if (!inboxExisted) inboxMbrDelta += globals.minBalance;
// Ensure the inbox has enough balance to opt in
if (inbox.balance < inbox.minBalance + inboxMbrDelta) {
sendPayment({
receiver: inbox,
amount: inboxMbrDelta,
});
}
// Opt the inbox in
sendAssetTransfer({
sender: inbox,
assetReceiver: inbox,
assetAmount: 0,
xferAsset: axfer.xferAsset,
});
}
// Transfer the asset to the inbox
sendAssetTransfer({
assetReceiver: inbox,
assetAmount: axfer.assetAmount,
xferAsset: axfer.xferAsset,
});
return inbox;
}
/**
* Claim an ASA from the inbox
*
* @param asa The ASA to claim
*/
arc59_claim(asa: AssetID): void {
const inbox = this.inboxes(this.txn.sender).value;
sendAssetTransfer({
sender: inbox,
assetReceiver: this.txn.sender,
assetAmount: inbox.assetBalance(asa),
xferAsset: asa,
assetCloseTo: this.txn.sender,
});
sendPayment({
sender: inbox,
receiver: this.txn.sender,
amount: inbox.balance - inbox.minBalance,
});
}
/**
* Reject the ASA by closing it out to the ASA creator. Always sends two inner transactions.
* All non-MBR ALGO balance in the inbox will be sent to the caller.
*
* @param asa The ASA to reject
*/
arc59_reject(asa: AssetID) {
const inbox = this.inboxes(this.txn.sender).value;
sendAssetTransfer({
sender: inbox,
assetReceiver: asa.creator,
assetAmount: inbox.assetBalance(asa),
xferAsset: asa,
assetCloseTo: asa.creator,
});
sendPayment({
sender: inbox,
receiver: this.txn.sender,
amount: inbox.balance - inbox.minBalance,
});
}
/**
* Get the inbox address for the given receiver
*
* @param receiver The receiver to get the inbox for
*
* @returns Zero address if the receiver does not yet have an inbox, otherwise the inbox address
*/
arc59_getInbox(receiver: Address): Address {
return this.inboxes(receiver).exists
? this.inboxes(receiver).value
: globals.zeroAddress;
}
/** Claim any extra algo from the inbox */
arc59_claimAlgo() {
const inbox = this.inboxes(this.txn.sender).value;
assert(inbox.balance - inbox.minBalance !== 0);
sendPayment({
sender: inbox,
receiver: this.txn.sender,
amount: inbox.balance - inbox.minBalance,
});
}
}
```
### TypeScript Send Asset Function
```ts
/**
* Send an asset to a receiver using the ARC59 router
*
* @param appClient The ARC59 client generated by algokit
* @param assetId The ID of the asset to send
* @param sender The address of the sender
* @param receiver The address of the receiver
* @param algorand The AlgorandClient instance to use to send transactions
* @param sendAlgoForNewAccount Whether to send 201_000 uALGO to the receiver so they can claim the asset with a 0-ALGO balance
*/
async function arc59SendAsset(
appClient: Arc59Client,
assetId: bigint,
sender: string,
receiver: string,
algorand: algokit.AlgorandClient
) {
// Get the address of the ARC59 router
const arc59RouterAddress = (await appClient.appClient.getAppReference())
.appAddress;
// Call arc59GetSendAssetInfo to get the following:
// itxns - The number of transactions needed to send the asset
// mbr - The minimum balance that must be sent to the router
// routerOptedIn - Whether the router has opted in to the asset
// receiverOptedIn - Whether the receiver has opted in to the asset
const [
itxns,
mbr,
routerOptedIn,
receiverOptedIn,
receiverAlgoNeededForClaim,
] = (await appClient.arc59GetSendAssetInfo({ asset: assetId, receiver }))
.return!;
// If the receiver has opted in, just send the asset directly
if (receiverOptedIn) {
await algorand.send.assetTransfer({
sender,
receiver,
assetId,
amount: 1n,
});
return;
}
// Create a composer to form an atomic transaction group
const composer = appClient.compose();
const signer = algorand.account.getSigner(sender);
// If the MBR is non-zero, send the MBR to the router
if (mbr || receiverAlgoNeededForClaim) {
const mbrPayment = await algorand.transactions.payment({
sender,
receiver: arc59RouterAddress,
amount: algokit.microAlgos(Number(mbr + receiverAlgoNeededForClaim)),
});
composer.addTransaction({ txn: mbrPayment, signer });
}
// If the router is not opted in, add a call to arc59OptRouterIn to do so
if (!routerOptedIn) composer.arc59OptRouterIn({ asa: assetId });
/** The box of the receiver's pubkey will always be needed */
const boxes = [algosdk.decodeAddress(receiver).publicKey];
/** The address of the receiver's inbox */
const inboxAddress = (
await appClient.compose().arc59GetInbox({ receiver }, { boxes }).simulate()
).returns[0];
// The transfer of the asset to the router
const axfer = await algorand.transactions.assetTransfer({
sender,
receiver: arc59RouterAddress,
assetId,
amount: 1n,
});
// An extra itxn is if we are also sending ALGO for the receiver claim
const totalItxns = itxns + (receiverAlgoNeededForClaim === 0n ? 0n : 1n);
composer.arc59SendAsset(
{ axfer, receiver, additionalReceiverFunds: receiverAlgoNeededForClaim },
{
sendParams: { fee: algokit.microAlgos(1000 + 1000 * Number(totalItxns)) },
boxes, // The receiver's pubkey
// Always good to include both accounts here, even if we think only the receiver is needed. This is to help protect against race conditions within a block.
accounts: [receiver, inboxAddress],
// Even though the asset is available in the group, we need to explicitly define it here because we will be checking the asset balance of the receiver
assets: [Number(assetId)],
}
);
// Disable resource population to ensure that our manually defined resources are correct
algokit.Config.configure({ populateAppCallResources: false });
// Send the transaction group
await composer.execute();
// Re-enable resource population
algokit.Config.configure({ populateAppCallResources: true });
}
```
### TypeScript Claim Function
```ts
/**
* Claim an asset from the ARC59 inbox
*
* @param appClient The ARC59 client generated by algokit
* @param assetId The ID of the asset to claim
* @param claimer The address of the account claiming the asset
* @param algorand The AlgorandClient instance to use to send transactions
*/
async function arc59Claim(
appClient: Arc59Client,
assetId: bigint,
claimer: string,
algorand: algokit.AlgorandClient
) {
const composer = appClient.compose();
// Check if the claimer has opted in to the asset
let claimerOptedIn = false;
try {
await algorand.account.getAssetInformation(claimer, assetId);
claimerOptedIn = true;
} catch (e) {
// Do nothing
}
const inbox = (
await appClient
.compose()
.arc59GetInbox({ receiver: claimer })
.simulate({ allowUnnamedResources: true })
).returns[0];
let totalTxns = 3;
// If the inbox has extra ALGO, claim it
const inboxInfo = await algorand.account.getInformation(inbox);
if (inboxInfo.minBalance < inboxInfo.amount) {
totalTxns += 2;
composer.arc59ClaimAlgo(
{},
{
sender: algorand.account.getAccount(claimer),
sendParams: { fee: algokit.algos(0) },
}
);
}
// If the claimer hasn't already opted in, add a transaction to do so
if (!claimerOptedIn) {
composer.addTransaction({
txn: await algorand.transactions.assetOptIn({ assetId, sender: claimer }),
signer: algorand.account.getSigner(claimer),
});
}
composer.arc59Claim(
{ asa: assetId },
{
sender: algorand.account.getAccount(claimer),
sendParams: { fee: algokit.microAlgos(1000 * totalTxns) },
}
);
await composer.execute();
}
```
## Security Considerations
The router application controls all user inboxes. If this contract is compromised, user assets might also be compromised.
## Copyright
Copyright and related rights waived via <a href="https://creativecommons.org/publicdomain/zero/1.0/">CCO</a>.
```
--------------------------------------------------------------------------------
/packages/server/src/resources/knowledge/taxonomy/developer:docs:details:stateproofs:light_client.md:
--------------------------------------------------------------------------------
```markdown
title: How To Build An Algorand Light Client - With State Proofs
# What is an Algorand Light Client?
An Algorand light client is a lightweight process that tracks the state of the Algorand network without trust in any intermediary. By cryptographically verifying a chain of proofs produced by the Algorand network itself, each light client can verify any given transaction on the network without running full node operations like consensus. Anyone can run an Algorand light client in any environment, providing a simple, secure interface to Algorand state in low power environments where running a full node is impractical or undesirable.
While State Proofs compress large amounts of Algorand state into a portable format (~600-900 KB), they are not designed to be verified inside blockchain smart contracts. The Algorand research team is currently building ultra-compact zk-SNARK proofs, which will be optimized for on-chain verification in fee-constrained environments like a blockchain smart contract. The rest of this article will focus on how to build a State Proof powered light client. We will discuss how to build a SNARK Proof powered light client as the cryptography work nears completion, although the key difference lies in the proof verification function used to advance the Light Client’s state.
Please note that Algorand light clients let you securely export Algorand state. In order to import state from another blockchain into Algorand, we’ll need to build a light client from that blockchain on Algorand. We will cover how to build other blockchain light clients on Algorand in a future article.
The cryptographic verification functions necessary to build a State Proof powered light client and to verify Algorand transactions are currently available as APIs in the go-algorand-sdk.
# Exporting Algorand State
State proofs use two primary Vector Commitment trees (similar to Merkle trees) to summarize blockchain activity.
1. A Transaction Commitment tree, which summarizes all transactions in a given block, is built into each block header by hashing each signed transaction in that block as tree leaves.
2. A Block Interval Commitment tree, which summarizes all block headers in a given state proof interval (256 rounds), is built once per interval. It is constructed with “light” block headers, which contain the block’s transaction commitment root and can be derived from the main block header, as tree leaves.
Each state proof “message” contains the root of the block interval commitment tree summarizing all transactions in that interval. After validating each state proof, a light client can store as many validated block interval commitments as it desires, creating a window of verified Algorand state. Requestors can then ask the Light Client to verify individual Algorand transactions which occurred in that interval - they simply need to provide proofs (retrieved from the Algorand SDKs) linking a specific transaction to a block interval commitment root.
<center>

</center>
Three new API endpoints to algod are available which are accessible via the Algorand SDKs to support State Proofs.
## GetTransactionProof(txnId, confirmedRound, hashType)
GetTransactionProof returns data that is used to verify that a specific transaction exists in a given transaction commitment tree. This function accepts a transaction ID, the round the transaction was confirmed in, and the hash function used to construct the commitment tree. This method will return a proof, the leaf index of the transaction, the depth of the tree, and the hash of the signed transaction that is being checked. These pieces of information should be all that a Light Client will need to verify a transaction is in the Transaction Commitment tree. Note that both the block interval commitment (inside the State Proof message) and the transaction commitment (inside the light block header) are constructed using SHA256, so you should use this hashType when verifying Algorand transactions on a Light Client.
## GetLightBlockHeaderProof(round)
GetLightBlockHeaderProof returns data that is used to verify that a specific Light Block Header exists in the Block Interval tree. This call takes a round number as a parameter and returns a proof, the leaf index of the light block header, and the tree depth of the block interval tree. These pieces of information should be all that a Light Client needs to verify a Light Block Header exists in a Block Interval Tree. If the State Proof transaction has not been written to the ledger for a specific interval and this method is called for a round in that interval, the call will fail.
## GetStateProof(round)
GetStateProof is used to retrieve a State Proof for the interval that contains a specific round, which is passed as a parameter. The State Proof contains the signatures of the attested State Proof message and the actual message which contains the Block Interval Commitment root for the given round. Note that it is possible that this call will return an error if the chosen round does not have a specific State Proof written on chain yet. This call will be used by Light Clients to track the state of the Algorand Chain, which is described in the next section.
# Light Client PoC Overview
Algorand Light Clients should be built to answer one question: did a specific transaction occur on Algorand’s blockchain at a specified round. To accomplish this, each Light Client will verify State Proofs (or zk-SNARK proofs) in order to store block interval commitments. These verified commitments can be used to validate block interval roots re-computed by the transaction verifier via the proofs returned from the algod API.
The Light Client should have 2 interfaces: one to advance Algorand state by verifying incoming State Proofs, and another to verify specific Algorand transactions against previously-verified block interval commitments. Because State Proofs are not polled by the Light Client itself, external relayers should retrieve State Proofs and their associated messages from the Algorand SDKs as they become available and push them to the Light Client for verification. Similar to the State Proof chain, each Light Client forms a chain by verifying incoming State Proofs against a previously-verified participants’ commitment. Similar to how Proof of Stake blockchains are initialized, the Light Client will need to hard-code the initial participants commitment in order to verify the first State Proof written on-chain.
<center>

</center>
Algorand provides a proof of concept version of a light client which is located on [GitHub](https://github.com/algorand/light-client-poc). Please note that this PoC code is purely informational and does not reflect the ideal way to implement a Light Client. Each section of the Light Client proof of concept will be explained below.
## Oracle
```go
// Oracle is responsible for ingesting State Proofs in chronological order and saving their block interval commitments
// to form a window of verified Algorand history.
// It then allows, given a round, to retrieve the vector commitment root attesting to the interval to which the round
// belongs.
type Oracle struct {
// BlockIntervalCommitmentHistory is a sliding window of verified block interval commitments. Given a round,
// it returns the block interval commitment that contains the specified block.
BlockIntervalCommitmentHistory *CommitmentHistory
// VotersCommitment is the vector commitment root of the top N accounts to sign the next StateProof.
VotersCommitment stateproofcrypto.GenericDigest
// LnProvenWeight is an integer value representing the natural log of the proven weight with 16 bits of precision.
// This value would be used to verify the next state proof.
LnProvenWeight uint64
}
```
The Oracle exposes the following functions:
### InitializeOracle
```go
// InitializeOracle initializes the Oracle using trusted genesis data.
// Parameters:
// firstAttestedRound - the first round to which a state proof message attests.
// intervalSize - represents the number of rounds that occur between each state proof.
// genesisVotersCommitment - the initial genesisVotersCommitment commitment. Real values can be found in the Algorand developer portal.
// genesisLnProvenWeight - the initial LnProvenWeight. Real values can be found in the Algorand developer portal.
// capacity - the maximum number of commitments to hold before discarding the earliest commitment.
func InitializeOracle(firstAttestedRound uint64, intervalSize uint64, genesisVotersCommitment stateproofcrypto.GenericDigest,
genesisLnProvenWeight uint64, capacity uint64) *Oracle {
return &Oracle{
// The BlockIntervalCommitmentHistory is initialized using the first attested round,
// the interval size and its capacity.
BlockIntervalCommitmentHistory: InitializeCommitmentHistory(firstAttestedRound, intervalSize, capacity),
VotersCommitment: genesisVotersCommitment,
LnProvenWeight: genesisLnProvenWeight,
}
}
```
### AdvanceState
```go
// AdvanceState receives a msgpacked state proof, provided by the Algorand node API, and a state proof message that the
// state proof attests to. It verifies the message using the proof given and the VotersCommitment and LnProvenWeight
// from the previous state proof message.
// If successful, it updates the Oracle's VotersCommitment and LnProvenWeight using their values from the new message,
// and saves the block header commitment to the history.
// This method should be called by a relay or some external process that is initiated when new Algorand state proofs are available.
// Parameters:
// stateProof - the decoded state proof, retrieved using the Algorand SDK.
// message - the message to which the state proof attests.
func (o *Oracle) AdvanceState(stateProof *stateproof.StateProof, message types.Message) error {
// verifier is Algorand's implementation of the state proof verifier, exposed by the state proof verification library.
// It uses the previous proven VotersCommitment and LnProvenWeight.
verifier := stateproof.MkVerifierWithLnProvenWeight(o.VotersCommitment, o.LnProvenWeight)
// We hash the state proof message using the Algorand SDK. The resulting hash is of the form
// sha256("spm" || msgpack(stateProofMessage)).
messageHash := stateproofcrypto.MessageHash(crypto.HashStateProofMessage(&message))
// The newly formed verifier verifies the given message using the state proof.
err := verifier.Verify(message.LastAttestedRound, messageHash, stateProof)
if err != nil {
// If the verification failed, for whatever reason, we return the error returned.
return err
}
// Successful verification of the message means we can trust it, so we save the VotersCommitment
// and the LnProvenWeight in the message, for verification of the next message.
o.VotersCommitment = message.VotersCommitment
o.LnProvenWeight = message.LnProvenWeight
var commitmentDigest types.Digest
copy(commitmentDigest[:], message.BlockHeadersCommitment)
// We insert the BlockHeadersCommitment found in the message to our commitment history sliding window.
// A side effect of this, if this commitment were to push our window over its capacity, would be deletion
// of the earliest commitment.
o.BlockIntervalCommitmentHistory.InsertCommitment(commitmentDigest)
return nil
}
```
### GetStateProofCommitment
```go
// GetStateProofCommitment retrieves a saved commitment for a specific round.
// Parameters:
// round - the round to which a commitment will be retrieved.
func (o *Oracle) GetStateProofCommitment(round types.Round) (types.Digest, error) {
// Receiving a commitment that should cover a round requires calculating the round's interval and retrieving the commitment
// for that interval. See BlockIntervalCommitmentHistory.GetCommitment for more details.
return o.BlockIntervalCommitmentHistory.GetCommitment(round)
}
```
## Transaction Verifier
The transaction verifier exposes the following interface:
### VerifyTransaction
```go
// VerifyTransaction receives a sha256 hashed transaction, a proof to compute the transaction's commitment, a proof
// to compute the commitment belonging to the light block header associated with the transaction's commitment,
// and an expected commitment to compare to. The function verifies that the computed commitment using the given proofs
// is identical to the provided commitment.
// Parameters:
// transactionHash - the result of invoking Sha256 on the canonical msgpack encoded transaction.
// transactionProofResponse - the response returned by an Algorand node when queried using GetTransactionProof.
// lightBlockHeaderProofResponse - the response returned by an Algorand node when queried using the GetLightBlockHeaderProof.
// confirmedRound - the round in which the given transaction was confirmed.
// genesisHash - the hash of the genesis block.
// seed - the sortition seed of the block associated with the light block header.
// blockIntervalCommitment - the commitment to compare to, provided by the Oracle.
func VerifyTransaction(transactionHash types.Digest, transactionProofResponse models.TransactionProofResponse,
lightBlockHeaderProofResponse models.LightBlockHeaderProof, confirmedRound types.Round, genesisHash types.Digest, seed types.Seed, blockIntervalCommitment types.Digest) error {
// Verifying attested vector commitment roots is currently exclusively supported with sha256 hashing, both for transactions
// and light block headers.
if transactionProofResponse.Hashtype != "sha256" {
return ErrUnsupportedHashFunction
}
var stibHashDigest types.Digest
copy(stibHashDigest[:], transactionProofResponse.Stibhash[:])
// We first compute the leaf in the vector commitment that attests to the given transaction.
transactionLeaf := computeTransactionLeaf(transactionHash, stibHashDigest)
// We use the transactionLeaf and the given transactionProofResponse to compute the root of the vector commitment
// that attests to the given transaction.
transactionProofRoot, err := computeVectorCommitmentRoot(transactionLeaf, transactionProofResponse.Idx,
transactionProofResponse.Proof, transactionProofResponse.Treedepth)
if err != nil {
return err
}
// We use our computed transaction vector commitment root, saved in transactionProofRoot, and the given data
// to calculate the leaf in the vector commitment that attests to the light block headers.
candidateLightBlockHeaderLeaf := computeLightBlockHeaderLeaf(confirmedRound, transactionProofRoot, genesisHash, seed)
// We use the candidateLightBlockHeaderLeaf and the given lightBlockHeaderProofResponse to compute the root of the vector
// commitment that attests to the candidateLightBlockHeaderLeaf.
lightBlockHeaderProofRoot, err := computeVectorCommitmentRoot(candidateLightBlockHeaderLeaf, lightBlockHeaderProofResponse.Index, lightBlockHeaderProofResponse.Proof,
lightBlockHeaderProofResponse.Treedepth)
if err != nil {
return err
}
// We verify that the given commitment, provided by the Oracle, is identical to the computed commitment
if bytes.Equal(lightBlockHeaderProofRoot[:], blockIntervalCommitment[:]) != true {
return ErrRootMismatch
}
return nil
}
```
Transaction verification uses several utility functions. In depth understanding of the following functions is required:
### computeVectorCommitmentRoot
```go
// computeVectorCommitmentRoot takes a vector commitment leaf, its index, a proof, and a tree depth. it calculates
// the vector commitment root using the provided data. This is done by computing internal nodes using the proof,
// starting from the leaf, until we reach the root. This function uses sha256 - it cannot be used correctly with leaves
// and proofs created using a different hash function.
// Parameters:
// leaf - the node we start computing the vector commitment root from.
// leafIndex - the leaf's index.
// proof - the proof to use in computing the vector commitment root. It holds hashed sibling nodes for each internal node
// calculated.
// treeDepth - the length of the path from the leaf to the root.
func computeVectorCommitmentRoot(leaf types.Digest, leafIndex uint64, proof []byte, treeDepth uint64) (types.Digest, error) {
// An empty proof is only possible when the leaf received is already the root, which means that the treeDepth
// must be 0. In this case, the result is the leaf itself.
if len(proof) == 0 && treeDepth == 0 {
return leaf, nil
}
nodeHashSize := uint64(sha256.New().Size())
// The proof must hold exactly treeDepth node hashes to allow us to compute enough nodes to reach the root.
if treeDepth*nodeHashSize != uint64(len(proof)) {
return types.Digest{}, ErrProofLengthTreeDepthMismatch
}
// See comments on getVectorCommitmentPositions for more details on the contents of the positions variable.
positions, err := getVectorCommitmentPositions(leafIndex, treeDepth)
if err != nil {
return types.Digest{}, err
}
// We start climbing from the leaf.
currentNode := leaf
// When distanceFromLeaf equals treeDepth, currentNode will contain the computed root.
for distanceFromLeaf := uint64(0); distanceFromLeaf < treeDepth; distanceFromLeaf++ {
siblingIndexInProof := distanceFromLeaf * nodeHashSize
// siblingHash is the next node to append to our current node, retrieved from the proof.
siblingHash := proof[siblingIndexInProof : siblingIndexInProof+nodeHashSize]
// Vector commitment nodes are of the form Sha256("MA" || left child || right child). To calculate the internal node,
// we have to use the positions array to determine if our current node is the left or right child.
// Positions[distanceFromLeaf] is the position of the current node at height distanceFromLeaf.
nodeDomainSeparator := []byte(transactionverificationtypes.MerkleArrayNode)
internalNodeData := nodeDomainSeparator
switch positions[distanceFromLeaf] {
case leftChild:
internalNodeData = append(internalNodeData, currentNode[:]...)
internalNodeData = append(internalNodeData, siblingHash[:]...)
case rightChild:
internalNodeData = append(internalNodeData, siblingHash[:]...)
internalNodeData = append(internalNodeData, currentNode[:]...)
default:
return types.Digest{}, ErrInvalidPosition
}
currentNode = sha256.Sum256(internalNodeData)
}
return currentNode, nil
}
```
### getVectorCommitmentPositions
```go
// getVectorCommitmentPositions maps a depth and a vector commitment index to the "positions" of the nodes
// on the leaf-to-root path, with 0/1 denoting left/right (respectively). It does so by expressing the index in binary
// using exactly depth bits, starting from the most-significant bit.
// For example, leafDepth=4 and index=5 maps to the array [0,1,0,1] -- indicating that the leaf is a left child,
// the leaf's parent is a right child, etc.
// Parameters:
// index - the leaf's index in the vector commitment tree.
// depth - the length of the path from the leaf to the root.
func getVectorCommitmentPositions(index uint64, depth uint64) ([]NodePosition, error) {
// A depth of 0 is only valid when the proof's length is also 0. Since the calling function checks for the situation
// where both values are 0, depth of 0 must be invalid.
if depth == 0 {
return []NodePosition{}, ErrInvalidTreeDepth
}
// Since each bit of the index is mapped to an element in the resulting positions array,
// the index must contain a number of bits amounting to the depth parameter, which means it must be smaller than 2 ^ depth.
if index >= 1<<depth {
return []NodePosition{}, ErrIndexDepthMismatch
}
// The resulting array should contain a number of positions equal to the depth of the tree -
// the length of the path between the root and the leaves - as that is the amounts of nodes traversed when calculating
// the vector commitment root.
directions := make([]NodePosition, depth)
// We iterate on the resulting array starting from the end, to allow us to extract LSBs, yet have the eventual result
// be equivalent to extracting MSBs.
for i := len(directions) - 1; i >= 0; i-- {
// We take index's current LSB, translate it to a node position and place it in index i.
directions[i] = NodePosition(index & 1)
// We shift the index to the right, to prepare for the extracting of the next LSB.
index >>= 1
}
return directions, nil
}
```
## Main
Tying it all together
```go
// A light client is composed of two modules:
// 1. An oracle, in charge of maintaining Algorand's state as verified with state proofs. For more details, see oracle.go.
// 2. A transaction verifier, in charge of verifying Algorand transaction occurrence by interfacing with the oracle. For more
// details, see transactionVerifier.go.
// This main function aims to demonstrate the interface between these two modules. In an actual
// light client, they can be entirely separate processes/smart contracts.
// The oracle should receive Algorand data from an off chain relayer, and the transaction verifier should receive
// transaction occurrence queries from third parties. For the purposes of this PoC, they're two separate go packages, and
// both the relayer and other third parties have been replaced with example committed data.
func main() {
// This is the genesis data, required for initializing the oracle. This data can either be queried from the
// blockchain itself, or found in the developer's portal.
genesisVotersCommitment, genesisVotersLnProvenWeight, err := encodedassets.GetParsedGenesisData("encodedassets/genesis/")
if err != nil {
fmt.Printf("Failed to parse genesis assets: %s\n", err)
return
}
// This is data required for verifying a transaction. In a real light client, this data should come from a
// third party. The third party is responsible for querying Algorand to get most of this data.
genesisHash, round, seed, transactionHash, transactionProofResponse, lightBlockHeaderProofResponse, err :=
encodedassets.GetParsedTransactionVerificationData("encodedassets/transactionverification/")
if err != nil {
fmt.Printf("Failed to parse assets needed for transaction verification: %s\n", err)
return
}
// This is data required for advancing the oracle's state. In a real light client, this data should come from a relayer.
stateProofMessage, stateProof, err :=
encodedassets.GetParsedStateProofAdvancmentData("encodedassets/stateproofverification/")
if err != nil {
fmt.Printf("Failed to parse assets needed for oracle state advancement: %s\n", err)
return
}
// In a real light client, intervalSize and firstAttestedRound should be hardcoded, and retrieved from the Algorand
// consensus and from the Algorand blockchain respectively.
intervalSize := uint64(8)
firstAttestedRound := uint64(9)
// We initialize the oracle using the parsed genesis data and a hard coded capacity.
oracleInstance := oracle.InitializeOracle(firstAttestedRound, intervalSize, genesisVotersCommitment, genesisVotersLnProvenWeight, 1000)
// We advance the oracle's state using the state proof and the state proof message. The oracle verifies the message
// using the state proof. See the documentation in oracle.go for more details.
err = oracleInstance.AdvanceState(stateProof, stateProofMessage)
if err != nil {
fmt.Printf("Failed to advance oracle state: %s\n", err)
return
}
// After advancing the oracle's state, we retrieve the block interval commitment containing the given transaction's round from the oracle.
desiredTransactionCommitment, err := oracleInstance.GetStateProofCommitment(round)
if err != nil {
fmt.Printf("Failed to retrieve commitment interval for round %d: %s\n", round, err)
return
}
// We then verify the transaction's occurrence using the data provided for transaction verification,
// along with the block interval commitment this transaction belongs to.
err = transactionverifier.VerifyTransaction(transactionHash, transactionProofResponse,
lightBlockHeaderProofResponse, round, genesisHash, seed, desiredTransactionCommitment)
if err != nil {
fmt.Printf("Transaction verification failed: %s\n", err)
return
}
}
```
```