{"version":3,"file":"asset-manager.mjs","sources":["../../src/types/asset-manager.ts"],"sourcesContent":["import algosdk, { Address } from 'algosdk'\nimport { Config } from '../config'\nimport { chunkArray } from '../util'\nimport { AccountAssetInformation } from './account'\nimport { CommonTransactionParams, MAX_TRANSACTION_GROUP_SIZE, TransactionComposer } from './composer'\nimport { SendParams } from './transaction'\n\n/** Individual result from performing a bulk opt-in or bulk opt-out for an account against a series of assets. */\nexport interface BulkAssetOptInOutResult {\n  /** The ID of the asset opted into / out of */\n  assetId: bigint\n  /** The transaction ID of the resulting opt in / out */\n  transactionId: string\n}\n\n/** Information about an asset. */\nexport interface AssetInformation {\n  /** The ID of the asset. */\n  assetId: bigint\n\n  /** The address of the account that created the asset.\n   *\n   * This is the address where the parameters for this asset can be found,\n   * and also the address where unwanted asset units can be sent when\n   * closing out an asset position and opting-out of the asset.\n   */\n  creator: string\n\n  /** The total amount of the smallest divisible (decimal) units that were created of the asset.\n   *\n   * For example, if `decimals` is, say, 2, then for every 100 `total` there is 1 whole unit.\n   */\n  total: bigint\n\n  /** The amount of decimal places the asset was created with.\n   *\n   * * If 0, the asset is not divisible;\n   * * If 1, the base unit of the asset is in tenths;\n   * * If 2, the base unit of the asset is in hundredths;\n   * * If 3, the base unit of the asset is in thousandths;\n   * * and so on up to 19 decimal places.\n   */\n  decimals: number\n\n  /** Whether the asset was frozen by default for all accounts.\n   *\n   * If `true` then for anyone apart from the creator to hold the\n   * asset it needs to be unfrozen per account using an asset freeze\n   * transaction from the `freeze` account.\n   */\n  defaultFrozen?: boolean\n\n  /** The address of the optional account that can manage the configuration of the asset and destroy it.\n   *\n   * If not set the asset is permanently immutable.\n   */\n  manager?: string\n\n  /**\n   * The address of the optional account that holds the reserve (uncirculated supply) units of the asset.\n   *\n   * This address has no specific authority in the protocol itself and is informational only.\n   *\n   * Some standards like [ARC-19](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0019.md)\n   * rely on this field to hold meaningful data.\n   *\n   * It can be used in the case where you want to signal to holders of your asset that the uncirculated units\n   * of the asset reside in an account that is different from the default creator account.\n   *\n   * If not set the field is permanently empty.\n   */\n  reserve?: string\n\n  /**\n   * The address of the optional account that can be used to freeze or unfreeze holdings of this asset for any account.\n   *\n   * If empty, freezing is not permitted.\n   *\n   * If not set the field is permanently empty.\n   */\n  freeze?: string\n\n  /**\n   * The address of the optional account that can clawback holdings of this asset from any account.\n   *\n   * The clawback account has the ability to **unconditionally take assets from any account**.\n   *\n   * If empty, clawback is not permitted.\n   *\n   * If not set the field is permanently empty.\n   */\n  clawback?: string\n\n  /** The optional name of the unit of this asset (e.g. ticker name).\n   *\n   * Max size is 8 bytes.\n   */\n  unitName?: string\n\n  /** The optional name of the unit of this asset (e.g. ticker name).\n   *\n   * Max size is 8 bytes.\n   */\n  unitNameAsBytes?: Uint8Array\n\n  /** The optional name of the asset.\n   *\n   * Max size is 32 bytes.\n   */\n  assetName?: string\n\n  /** The optional name of the asset.\n   *\n   * Max size is 32 bytes.\n   */\n  assetNameAsBytes?: Uint8Array\n\n  /** Optional URL where more information about the asset can be retrieved (e.g. metadata).\n   *\n   * Max size is 96 bytes.\n   */\n  url?: string\n\n  /** Optional URL where more information about the asset can be retrieved (e.g. metadata).\n   *\n   * Max size is 96 bytes.\n   */\n  urlAsBytes?: Uint8Array\n\n  /** 32-byte hash of some metadata that is relevant to the asset and/or asset holders.\n   *\n   * The format of this metadata is up to the application.\n   */\n  metadataHash?: Uint8Array\n}\n\n/** Allows management of asset information. */\nexport class AssetManager {\n  private _algod: algosdk.Algodv2\n  private _newGroup: () => TransactionComposer\n\n  /**\n   * Create a new asset manager.\n   * @param algod An algod client\n   * @param newGroup A function that creates a new `TransactionComposer` transaction group\n   * @example Create a new asset manager\n   * ```typescript\n   * const assetManager = new AssetManager(algod, () => new TransactionComposer({algod, () => signer, () => suggestedParams}))\n   * ```\n   */\n  constructor(algod: algosdk.Algodv2, newGroup: () => TransactionComposer) {\n    this._algod = algod\n    this._newGroup = newGroup\n  }\n\n  /**\n   * Returns the current asset information for the asset with the given ID.\n   *\n   * @example\n   * ```typescript\n   * const assetInfo = await assetManager.getById(12353n);\n   * ```\n   *\n   * @param assetId The ID of the asset\n   * @returns The asset information\n   */\n  public async getById(assetId: bigint): Promise<AssetInformation> {\n    const asset = await this._algod.getAssetByID(Number(assetId)).do()\n\n    return {\n      assetId: BigInt(asset.index),\n      total: BigInt(asset.params.total),\n      decimals: Number(asset.params.decimals),\n      assetName: asset.params.name,\n      assetNameAsBytes: asset.params.nameB64,\n      unitName: asset.params.unitName,\n      unitNameAsBytes: asset.params.unitNameB64,\n      url: asset.params.url,\n      urlAsBytes: asset.params.urlB64,\n      creator: asset.params.creator,\n      manager: asset.params.manager,\n      clawback: asset.params.clawback,\n      freeze: asset.params.freeze,\n      reserve: asset.params.reserve,\n      defaultFrozen: asset.params.defaultFrozen,\n      metadataHash: asset.params.metadataHash,\n    }\n  }\n\n  /**\n   * Returns the given sender account's asset holding for a given asset.\n   *\n   * @example\n   * ```typescript\n   * const address = \"XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA\";\n   * const assetId = 123345n;\n   * const accountInfo = await assetManager.getAccountInformation(address, assetId);\n   * ```\n   *\n   * [Response data schema details](https://dev.algorand.co/reference/rest-apis/algod/#accountassetinformation)\n   * @param sender The address of the sender/account to look up\n   * @param assetId The ID of the asset to return a holding for\n   * @returns The account asset holding information\n   */\n  public async getAccountInformation(sender: string | Address, assetId: bigint): Promise<AccountAssetInformation> {\n    const info = await this._algod.accountAssetInformation(sender, Number(assetId)).do()\n\n    return {\n      assetId: BigInt(assetId),\n      balance: BigInt(info.assetHolding?.amount ?? 0),\n      frozen: info.assetHolding?.isFrozen === true,\n      round: BigInt(info['round']),\n    }\n  }\n\n  /**\n   * Opt an account in to a list of Algorand Standard Assets.\n   *\n   * Transactions will be sent in batches of 16 as transaction groups.\n   *\n   * @param account The account to opt-in\n   * @param assetIds The list of asset IDs to opt-in to\n   * @param options Any parameters to control the transaction or execution of the transaction\n   * @example Example using AlgorandClient\n   * ```typescript\n   * // Basic example\n   * assetManager.bulkOptIn(\"ACCOUNTADDRESS\", [12345n, 67890n])\n   * // With configuration\n   * assetManager.bulkOptIn(\"ACCOUNTADDRESS\", [12345n, 67890n], { maxFee: (1000).microAlgo(), suppressLog: true })\n   * ```\n   * @returns An array of records matching asset ID to transaction ID of the opt in\n   */\n  async bulkOptIn(\n    account: string | Address,\n    assetIds: bigint[],\n    options?: Omit<CommonTransactionParams, 'sender'> & SendParams,\n  ): Promise<BulkAssetOptInOutResult[]> {\n    const results: BulkAssetOptInOutResult[] = []\n\n    for (const assetGroup of chunkArray(assetIds, MAX_TRANSACTION_GROUP_SIZE)) {\n      const composer = this._newGroup()\n\n      for (const assetId of assetGroup) {\n        composer.addAssetOptIn({\n          ...options,\n          sender: account,\n          assetId: BigInt(assetId),\n        })\n      }\n\n      const result = await composer.send(options)\n\n      Config.getLogger(options?.suppressLog).info(\n        `Successfully opted in ${account} for assets ${assetGroup.join(', ')} with transaction IDs ${result.txIds.join(', ')}` +\n          `\\n  Grouped under ${result.groupId} in round ${result.confirmations?.[0]?.confirmedRound}.`,\n      )\n\n      assetGroup.forEach((assetId, index) => {\n        results.push({ assetId: BigInt(assetId), transactionId: result.txIds[index] })\n      })\n    }\n\n    return results\n  }\n\n  /**\n   * Opt an account out of a list of Algorand Standard Assets.\n   *\n   * Transactions will be sent in batches of 16 as transaction groups.\n   *\n   * @param account The account to opt-in\n   * @param assetIds The list of asset IDs to opt-out of\n   * @param options Any parameters to control the transaction or execution of the transaction\n   * @example Example using AlgorandClient\n   * ```typescript\n   * // Basic example\n   * assetManager.bulkOptOut(\"ACCOUNTADDRESS\", [12345n, 67890n])\n   * // With configuration\n   * assetManager.bulkOptOut(\"ACCOUNTADDRESS\", [12345n, 67890n], { ensureZeroBalance: true, maxFee: (1000).microAlgo(), suppressLog: true })\n   * ```\n   * @returns An array of records matching asset ID to transaction ID of the opt in\n   */\n  async bulkOptOut(\n    account: string | Address,\n    assetIds: bigint[],\n    options?: Omit<CommonTransactionParams, 'sender'> &\n      SendParams & {\n        /** Whether or not to check if the account has a zero balance for each asset first or not.\n         *\n         * Defaults to `true`.\n         *\n         * If this is set to `true` and the account has an asset balance it will throw an error.\n         *\n         * If this is set to `false` and the account has an asset balance it will lose those assets to the asset creator.\n         */\n        ensureZeroBalance?: boolean\n      },\n  ): Promise<BulkAssetOptInOutResult[]> {\n    const results: BulkAssetOptInOutResult[] = []\n\n    for (const assetGroup of chunkArray(assetIds, MAX_TRANSACTION_GROUP_SIZE)) {\n      const composer = this._newGroup()\n\n      const notOptedInAssetIds: bigint[] = []\n      const nonZeroBalanceAssetIds: bigint[] = []\n      for (const assetId of assetGroup) {\n        if (options?.ensureZeroBalance !== false) {\n          try {\n            const accountAssetInfo = await this.getAccountInformation(account, assetId)\n            if (accountAssetInfo.balance !== 0n) {\n              nonZeroBalanceAssetIds.push(BigInt(assetId))\n            }\n          } catch {\n            notOptedInAssetIds.push(BigInt(assetId))\n          }\n        }\n      }\n\n      if (notOptedInAssetIds.length > 0 || nonZeroBalanceAssetIds.length > 0) {\n        throw new Error(\n          `Account ${account}${notOptedInAssetIds.length > 0 ? ` is not opted-in to Asset${notOptedInAssetIds.length > 1 ? 's' : ''} ${notOptedInAssetIds.join(', ')}` : ''}${\n            nonZeroBalanceAssetIds.length > 0\n              ? ` has non-zero balance for Asset${nonZeroBalanceAssetIds.length > 1 ? 's' : ''} ${nonZeroBalanceAssetIds.join(', ')}`\n              : ''\n          }; can't opt-out.`,\n        )\n      }\n\n      for (const assetId of assetGroup) {\n        composer.addAssetOptOut({\n          ...options,\n          creator: (await this.getById(BigInt(assetId))).creator,\n          sender: account,\n          assetId: BigInt(assetId),\n        })\n      }\n\n      const result = await composer.send(options)\n\n      Config.getLogger(options?.suppressLog).info(\n        `Successfully opted ${account} out of assets ${assetGroup.join(', ')} with transaction IDs ${result.txIds.join(', ')}` +\n          `\\n  Grouped under ${result.groupId} in round ${result.confirmations?.[0]?.confirmedRound}.`,\n      )\n\n      assetGroup.forEach((assetId, index) => {\n        results.push({ assetId: BigInt(assetId), transactionId: result.txIds[index] })\n      })\n    }\n\n    return results\n  }\n}\n"],"names":[],"mappings":"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"}