diff --git a/ARCs/arc-0063.md b/ARCs/arc-0063.md
new file mode 100644
index 000000000..2ded4456e
--- /dev/null
+++ b/ARCs/arc-0063.md
@@ -0,0 +1,320 @@
+---
+arc: 63
+title: Lsig Plug-In Signer for Msig Vault
+description: Delegated multisig-account controlled by one account
+author: Stéphane BARROSO (@SudoWeezy)
+discussions-to: https://github.com/algorandfoundation/ARCs/issues/303
+status: Draft
+type: Standards Track
+category: ARC
+created: 2024-07-16
+---
+
+## Abstract
+
+This ARC proposes a method for creating a delegated multisig account controlled by one account and a Logic Signature (Lsig).
+
+## Motivation
+
+The motivation behind this ARC is to extend Algorand account features by enabling third-party "Plug-Ins" using a combination of delegated Lsig and Multi-Signature accounts, which act as vaults. This approach allows anyone to sign the Lsig for the vault, while maintaining security and control through a classic algorand account.
+
+## 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 RFC-2119
+
+### Components
+
+1. **Owner Account**: Vault's Owner
+1. **Lsig Plug-In**: Provided by a third party.
+1. **Plug-In Signer**: Created by generating a new key pair
+1. **1/2 Msig Account**: Comprises the owner's address, and the plug-in signer.
+
+### Implementation 1
+
+We will use the following Logic plug-in for illustrative purposes:
+
+**DO NOT USE IN PRODUCTION**
+
+```python
+teal_program = """
+#pragma version 10
+txn TypeEnum
+pushint 4
+==
+txn AssetAmount
+pushint 0
+==
+&&
+txn RekeyTo
+global ZeroAddress
+==
+&&
+txn Fee
+global MinTxnFee
+==
+&&
+return
+"""
+compiled_program = client.compile(teal_program)
+program = base64.b64decode(compiled_program["result"])
+lsig = transaction.LogicSigAccount(program)
+```
+
+> This give opt-in control over the signer
+
+#### 1. **Generate Plug-In Signer**
+
+- Generate a random new account that we will only use once.
+
+```python
+plug_in_sk, plug_in_addr = account.generate_account()
+```
+
+#### 2. **Sign Lsig with Plug-In Signer**
+
+- Sign the Lsig using the plug-in signer.
+- Publish the public signature on the blockchain.
+
+```python
+ public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(base64.b64decode(plug_in_sk)[: constants.key_len_bytes])
+ message = constants.logic_prefix + program
+ raw_signed = nacl.bindings.crypto_sign(message, secret_key)
+ crypto_sign_BYTES = nacl.bindings.crypto_sign_BYTES
+ signature = nacl.encoding.RawEncoder.encode(raw_signed[:crypto_sign_BYTES])
+ plug_in_public_sig = base64.b64encode(signature).decode()
+```
+
+> You can achieve the same result like this:
+
+```python
+lsig = transaction.LogicSigAccount(program).sign(plug_in_sk)
+plug_in_public_sig = lsig.lsig.sig
+```
+
+#### 3. **Create 1/2 Msig Account**
+
+- Create a multi-signature account with owner address, and the plug-in signer.
+
+```python
+owner_vault_msig = transaction.Multisig(1,1,[owner_addr, plug_in_addr])
+```
+
+- Add a transaction note to the transaction to help third party to retrieve signer and vault information.
+
+```json
+ {
+ "pk": plug_in_addr,
+ "sk": plug_in_public_sig,
+ "lsig": lsig.address(),
+ }
+```
+
+- Prefix the note following the [ARC-2](./arc-0002.md) standard. `arc63:j`
+
+```python
+ptxn = transaction.PaymentTxn(
+ owner_addr, sp, owner_vault_msig.address(), int(1e6), note=f"arc_63:j{note_field}"
+).sign(owner_sk)
+```
+
+#### 4. **Opt-In to Msig Vault**
+
+- Anyone can opt-in to the Msig vault using the plug-in signer’s public address and the published signature.
+
+```python
+optin_txn = AssetTransferTxn(
+ sender=owner_vault_msig.address(),
+ sp=sp,
+ receiver=owner_vault_msig.address(),
+ amt=0,
+ index=a_id,
+)
+lsig.lsig.msig = owner_vault_msig
+lsig.append_to_multisig(plug_in_sk) # signature from plug_in_public
+lstx = LogicSigTransaction(optin_txn, lsig)
+```
+
+### Implementation 2
+
+We will use the following Lsig plug-in for our illustrative purposes:
+
+**DO NOT USE IN PRODUCTION**
+
+```python
+teal_program = f"""
+#pragma version 10
+txn TypeEnum
+int appl
+==
+txn ApplicationID
+int {app_client.app_id}
+==
+&&
+txn RekeyTo
+global ZeroAddress
+==
+&&
+txn Fee
+global MinTxnFee
+==
+&&
+return
+"""
+compiled_program = client.compile(teal_program)
+program = base64.b64decode(compiled_program["result"])
+lsig = transaction.LogicSigAccount(program)
+```
+
+> This allow the application with the id `app_client.app_id` to control the signer
+
+To be consistant with the previous example, we will use a similar opt-in process.
+
+```python
+class SmartApp(ARC4Contract):
+ def __init__(self) -> None:
+ self.db = BoxMap(Account, String, key_prefix="")
+
+ @abimethod()
+ def opt_in(self, id: UInt64, account: Account) -> None:
+ itxn.AssetTransfer(
+ asset_amount=0,
+ xfer_asset=id,
+ sender=account,
+ asset_receiver=account,
+ fee=1000,
+ ).submit()
+
+ @abimethod()
+ def set_public_sig(self, account: Account, sig: String) -> bool:
+ self.db[account] = sig
+ return self.db[account] == sig
+
+ @abimethod(readonly=True)
+ def get_public_sig(self, account: Account) -> String:
+ return self.db[account]
+```
+
+### 1. [Generate Plug-In Signer](./arc-0063.md#1-generate-plug-in-signer)
+
+### 2. [Sign Lsig with Plug-In Signer](./arc-0063.md#2-sign-lsig-with-plug-in-signer)
+
+#### 3. **Create 1/2 Msig Account** and execute an app Call
+
+- Create a multi-signature account with owner address, and the plug-in signer.
+
+```python
+owner_vault_msig = transaction.Multisig(1,1,[owner_addr, plug_in_addr])
+```
+
+- Publish the public signature by using set_public_sig to the app.
+
+```python
+response = app_client.set_public_sig(
+ account=owner_addr,
+ sig=plug_in_public_sig,
+ transaction_parameters=algokit_utils.TransactionParameters(
+ boxes=[(app_client.app_id, encoding.decode_address(owner_addr))],
+ accounts=[owner_addr]
+ )
+)
+```
+
+- Get the transaction set_public_sig from the app.
+
+```python
+response = app_client.get_public_sig(
+ account=owner_addr,
+ transaction_parameters=algokit_utils.TransactionParameters(
+ boxes=[(app_client.app_id, encoding.decode_address(owner_addr))]
+ ),
+)
+```
+
+#### 4. **App in control of Msig Vault**
+
+- Anyone can now call the app to opt-in any asset to the Msig vault using the plug-in signer’s public address and the published signature.
+
+```python
+composer.opt_in(
+ id=a_id,
+ account=app_client.app_address,
+ transaction_parameters=algokit_utils.TransactionParameters(
+ foreign_assets=[a_id], signer=app_client.signer,
+ sender=owner_vault_msig.address()
+ ),
+)
+opt_in_txn = composer.atc.txn_list[0].txn
+
+lsig.lsig.msig = owner_vault_msig
+lsig.lsig.msig.subsigs[1].signature = base64.b64decode(plug_in_public_sig)
+lstx = transaction.LogicSigTransaction(opt_in_txn, lsig)
+```
+
+### Diagram
+
+```mermaid
+graph TD
+ subgraph signer
+ PKS[PK]
+ SKS[SK]
+ PKS ~~~ SKS
+ end
+ subgraph msigVault
+ SKVS[Public Signature]
+ SKVA[Public Address]
+ PKV[ownerPK]
+ PKV ~~~ SKVA
+ PKV ~~~ SKVS
+ end
+ subgraph owner
+ PKO[PK]
+ end
+
+ ta[[Throwaway Account]]
+
+ lsig((Lsig Plug In))
+
+ sign((Sign))
+
+ ta --> signer
+ PKO --> PKV
+ SKS --> sign
+ lsig --- sign
+ sign --> SKVS
+ PKS --> SKVA
+ msigVault ~~~ lsig
+```
+
+## Rationale
+
+The rationale for this design is to leverage third-party Lsig plug-ins. By note storing the plug-in signer private key, we mitigate risks associated with its misuse, while the multi-signature account setup ensures controlled access and flexibility in asset management.
+
+## Backwards Compatibility
+
+This ARC introduces no backward incompatibilities. It builds upon existing Algorand functionalities, ensuring seamless integration with current systems.
+
+## Reference Implementation
+
+An example implementation in Python is provided, demonstrating the creation of a plug-in signer, signing an Lsig, and opting into a multi-signature vault.
+
+### Only with Lsig
+
+[Create_Opt_in_Plug_in](../assets/arc-0063/create_plugin.py)
+
+### Delegating the Lsig to an App
+
+[Deploy config](../assets/arc-0063/deploy_config.py)
+[Contract](../assets/arc-0063/contract.py)
+
+> This need to be run with Algokit
+
+## Security Considerations
+
+Even if the plug-in signer is rekeyed, the private key can still sign new lsigs, which is why the private key should not be accessible by anyone after the signature.
+This step is crucial to prevent any unauthorized use of the signer post-creation.
+
+If the Multi-signature vault accounts is rekeyd to any other account (Msig or traditional), it will keep the same public address and will not be delegated anymore.
+
+## Copyright
+
+Copyright and related rights waived via CCO.
diff --git a/assets/arc-0063/contract.py b/assets/arc-0063/contract.py
new file mode 100644
index 000000000..6e9c9d67e
--- /dev/null
+++ b/assets/arc-0063/contract.py
@@ -0,0 +1,26 @@
+from algopy import ARC4Contract, Account, UInt64, itxn, BoxMap
+from algopy.arc4 import abimethod, String
+
+
+class SmartApp(ARC4Contract):
+ def __init__(self) -> None:
+ self.db = BoxMap(Account, String, key_prefix="")
+
+ @abimethod()
+ def opt_in(self, id: UInt64, account: Account) -> None:
+ itxn.AssetTransfer(
+ asset_amount=0,
+ xfer_asset=id,
+ sender=account,
+ asset_receiver=account,
+ fee=1000,
+ ).submit()
+
+ @abimethod()
+ def set_public_sig(self, account: Account, sig: String) -> bool:
+ self.db[account] = sig
+ return self.db[account] == sig
+
+ @abimethod(readonly=True)
+ def get_public_sig(self, account: Account) -> String:
+ return self.db[account]
diff --git a/assets/arc-0063/create_plugin.py b/assets/arc-0063/create_plugin.py
new file mode 100644
index 000000000..b47a96648
--- /dev/null
+++ b/assets/arc-0063/create_plugin.py
@@ -0,0 +1,181 @@
+from algosdk import mnemonic, transaction, encoding, constants, v2client, account
+from typing import Dict, Any
+import base64
+import nacl
+import json
+
+algod_address = "http://localhost:4001" # Adjust if using a different port
+algod_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+client = v2client.algod.AlgodClient(algod_token, algod_address)
+indexer_address = "http://localhost:8980" # Adjust if using a different port
+indexer_token = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+indexer = v2client.indexer.IndexerClient(indexer_token, indexer_address)
+sp = client.suggested_params()
+
+teal_program = """
+#pragma version 10
+txn TypeEnum
+pushint 4
+==
+txn AssetAmount
+pushint 0
+==
+&&
+txn AssetCloseTo
+global ZeroAddress
+==
+&&
+txn RekeyTo
+global ZeroAddress
+==
+&&
+txn Fee
+global MinTxnFee
+==
+&&
+return
+"""
+
+compiled_program = client.compile(teal_program)
+program = base64.b64decode(compiled_program["result"])
+lsig = transaction.LogicSigAccount(program)
+
+add = [
+ {
+ "address": "45UO5ZGAAV3VSUFWPY72UITVNSWKLSYJBBALU2O56E32QQYHXHCI5D2PDA",
+ "mnemonic": "dumb pencil plastic isolate butter ribbon glide tragic pulse empty grape double glass stadium disorder riot agent donkey city weird shadow bubble ladder absent kidney",
+ },
+ {
+ "address": "6QZBRTHUT4P4D26HBW7NSJJ26P3WV4NWXLBF7AB5TVDVJFXLFLN6RMZQKI",
+ "mnemonic": "sense gate people glare window bright betray tiny group subject blast gasp cargo safe play news inhale evolve luggage coil biology wide custom absorb trust",
+ }
+]
+
+owner_sk, owner_addr = mnemonic.to_private_key(add[0]["mnemonic"]), add[0]["address"]
+asa_creator_sk, asa_creator_addr = mnemonic.to_private_key(add[1]["mnemonic"]), add[1]["address"]
+
+ #******************** Plug_IN_Signer Account Generation ****************************#
+if True:
+ plug_in_sk, plug_in_addr = account.generate_account()
+else:
+ plug_in_sk = "Cq9JfCTzMp9bSKVNxuF2YsNm0fS9RsshOVbN6I8Av5zbhEH2I1Qd8UN6UhgOfD1REDY9/pNjPy+D++ib2xTAAg=="
+ plug_in_addr = "3OCED5RDKQO7CQ32KIMA47B5KEIDMPP6SNRT6L4D7PUJXWYUYABBZG56JE"
+ #******************** Plug_IN_Signer Public Signature ****************************#
+public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(base64.b64decode(plug_in_sk)[: constants.key_len_bytes])
+message = constants.logic_prefix + program
+raw_signed = nacl.bindings.crypto_sign(message, secret_key)
+crypto_sign_BYTES = nacl.bindings.crypto_sign_BYTES
+signature = nacl.encoding.RawEncoder.encode(raw_signed[:crypto_sign_BYTES])
+plug_in_public_sig = base64.b64encode(signature).decode()
+
+owner_vault_msig = transaction.Multisig(1,1,[owner_addr, plug_in_addr])
+
+
+rekey_info = indexer.search_transactions_by_address(plug_in_addr, rekey_to=True)["transactions"]
+
+if (int(client.account_info(owner_vault_msig.address())["amount"]) == 0):
+ #********************* MSIG VAULT Generation **************************************#
+ #********************* MSIG VAUL Funding **************************************#
+ note_field = json.dumps({
+ "pk": plug_in_addr,
+ "sk": plug_in_public_sig,
+ "lsig": lsig.address()
+ })
+ ptxn_vault = transaction.PaymentTxn(
+ owner_addr, sp, owner_vault_msig.address(), int(1e6), note=f"arc63:j{note_field}"
+ )
+
+ #******************** Fund Signer before rekey ADDRESS ****************************#
+
+ ptxn_signer = transaction.PaymentTxn(
+ owner_addr, sp, plug_in_addr, int(1e5 + 1e3)
+ )
+ #******************** REKEY PLUG_IN TO 0 ADDRESS ****************************#
+
+ zero_msig = transaction.Multisig(1,1,[constants.ZERO_ADDRESS])
+ rekey_txn = transaction.PaymentTxn(
+ plug_in_addr, sp, plug_in_addr, 0, rekey_to=zero_msig.address()
+ )
+ transaction.assign_group_id([ptxn_vault, ptxn_signer, rekey_txn])
+
+ signed_ptxn_vault = ptxn_vault.sign(owner_sk)
+ signed_ptxn_signer = ptxn_signer.sign(owner_sk)
+
+ signed_rekey = rekey_txn.sign(plug_in_sk)
+
+ signed_group = [signed_ptxn_vault, signed_ptxn_signer, signed_rekey]
+ print(signed_group)
+ txid = client.send_transactions(signed_group)
+ result: Dict[str, Any] = transaction.wait_for_confirmation(
+ client, txid, 4
+ )
+ print(f"txID: {txid} confirmed in round: {result.get('confirmed-round', 0)}")
+
+print("owner vault addresses : ", owner_vault_msig.address()) #NUVYGSZCMMH65PGYGPRB63JNZMMIKT6ROK5HNM3BKVKLSNL77FTB7DKMJU
+for i in owner_vault_msig.subsigs:
+ print("owner vault address: ", encoding.encode_address(i.public_key), base64.b64encode(i.public_key))
+
+
+asa_creator_info = client.account_info(asa_creator_addr)
+if 'assets' in asa_creator_info and (len(asa_creator_info['assets']) > 0):
+ a_id = client.account_info(asa_creator_addr)['assets'][0]['asset-id']
+else:
+ #******************** asa_creator CREATE ASA ****************************#
+ print("asa_creator Create ASA")
+ actxn = transaction.AssetConfigTxn( sender=asa_creator_addr, sp=sp, default_frozen=False, unit_name="rug2", asset_name="2 Really Useful Gift", manager=asa_creator_addr, reserve=asa_creator_addr, freeze=asa_creator_addr, clawback=asa_creator_addr, url="https://path/to/my/asset/details", total=10, decimals=0, )
+ sactxn = actxn.sign(asa_creator_sk)
+ tx_id = client.send_transaction(sactxn)
+ print(f"Sent asset create transaction with txid: {tx_id}")
+ # Wait for the transaction to be confirmed
+ results = transaction.wait_for_confirmation(client, tx_id, 4)
+ a_id = results['asset-index']
+ print(f"Result confirmed in round: {results['confirmed-round']} ASA ID : {results['asset-index']}")
+print(f'asa_creator created 10 ASA: {a_id}')
+
+
+ #******************** MSIG VAULT OPT IN ****************************#
+optin_txn = transaction.AssetTransferTxn(
+ sender=owner_vault_msig.address(),
+ sp=sp,
+ receiver=owner_vault_msig.address(),
+ amt=0,
+ index=a_id,
+)
+lsig.lsig.msig = owner_vault_msig
+lsig.append_to_multisig(plug_in_sk)
+
+assert (lsig.lsig.msig.subsigs[1].signature == base64.b64decode(plug_in_public_sig)) # signature from plug_in_public
+lstx = transaction.LogicSigTransaction(optin_txn, lsig)
+
+
+
+optin_txid = client.send_transaction(lstx)
+
+
+print(f"Sent Msig Vault Opt-in with txid: {optin_txid}")
+# Wait for the transaction to be confirmed
+results = transaction.wait_for_confirmation(client, optin_txid, 4)
+print(f"Result confirmed in round: {results['confirmed-round']}")
+print(" #********************Opt Out ASA***************************#")
+owner_vault_msig = transaction.Multisig(
+ 1,
+ 1,
+ [owner_addr, plug_in_addr]
+)
+
+optout_txn = transaction.AssetCloseOutTxn(
+ sender=owner_vault_msig.address(),
+ sp=sp,
+ receiver=owner_vault_msig.address(),
+ index=a_id
+)
+
+msig_txn = transaction.MultisigTransaction(optout_txn, owner_vault_msig)
+msig_txn.sign(owner_sk)
+optout_txn = client.send_transaction(msig_txn)
+
+print(f"Sent Msig Vault Opt-out with txid: {optout_txn}")
+# Wait for the transaction to be confirmed
+results = transaction.wait_for_confirmation(client, optout_txn, 4)
+print(f"Result confirmed in round: {results['confirmed-round']}")
+print(f"{owner_vault_msig.address()} Opted - Out {a_id}")
diff --git a/assets/arc-0063/deploy_config.py b/assets/arc-0063/deploy_config.py
new file mode 100644
index 000000000..a289c0018
--- /dev/null
+++ b/assets/arc-0063/deploy_config.py
@@ -0,0 +1,269 @@
+import logging
+import algokit_utils
+from algosdk import (
+ mnemonic,
+ transaction,
+ constants,
+ account,
+ encoding
+)
+from algosdk.v2client.algod import AlgodClient
+from algosdk.v2client.indexer import IndexerClient
+from typing import Dict, Any
+import base64
+import nacl
+
+logger = logging.getLogger(__name__)
+
+
+add = [
+ {
+ "address": "45UO5ZGAAV3VSUFWPY72UITVNSWKLSYJBBALU2O56E32QQYHXHCI5D2PDA",
+ "mnemonic": "dumb pencil plastic isolate butter ribbon glide tragic pulse empty grape double glass stadium disorder riot agent donkey city weird shadow bubble ladder absent kidney",
+ },
+ {
+ "address": "6QZBRTHUT4P4D26HBW7NSJJ26P3WV4NWXLBF7AB5TVDVJFXLFLN6RMZQKI",
+ "mnemonic": "sense gate people glare window bright betray tiny group subject blast gasp cargo safe play news inhale evolve luggage coil biology wide custom absorb trust",
+ },
+]
+
+owner_sk, owner_addr = mnemonic.to_private_key(add[0]["mnemonic"]), add[0]["address"]
+asa_creator_sk, asa_creator_addr = (
+ mnemonic.to_private_key(add[1]["mnemonic"]),
+ add[1]["address"],
+)
+
+
+# define deployment behaviour based on supplied app spec
+def deploy(
+ algod_client: AlgodClient,
+ indexer_client: IndexerClient,
+ app_spec: algokit_utils.ApplicationSpecification,
+ deployer: algokit_utils.Account,
+) -> None:
+ from smart_contracts.artifacts.smart_app.smart_app_client import SmartAppClient, Composer, AtomicTransactionComposer
+
+ app_client = SmartAppClient(
+ algod_client,
+ creator=deployer,
+ indexer_client=indexer_client,
+ )
+ dispenser = algokit_utils.get_dispenser_account(algod_client)
+
+ sp = algod_client.suggested_params()
+ if (int(algod_client.account_info(owner_addr)["amount"]) <= 1e7):
+ ptxn_signer = transaction.PaymentTxn(
+ dispenser.address, sp, owner_addr, int(1e7)
+ ).sign(dispenser.private_key)
+ algod_client.send_transaction(ptxn_signer)
+
+ asa_creator_info = algod_client.account_info(asa_creator_addr)
+ if "assets" in asa_creator_info and (len(asa_creator_info["assets"]) > 0):
+ a_id = algod_client.account_info(asa_creator_addr)["assets"][0]["asset-id"]
+ else:
+ # ******************** asa_creator CREATE ASA ****************************#
+ ptxn_signer = transaction.PaymentTxn(
+ dispenser.address, sp, asa_creator_addr, int(2e5 + 1e3)
+ ).sign(dispenser.private_key)
+ algod_client.send_transaction(ptxn_signer)
+ logger.info("asa_creator Create ASA")
+ actxn = transaction.AssetConfigTxn(
+ sender=asa_creator_addr,
+ sp=sp,
+ default_frozen=False,
+ unit_name="rug2",
+ asset_name="2 Really Useful Gift",
+ manager=asa_creator_addr,
+ reserve=asa_creator_addr,
+ freeze=asa_creator_addr,
+ clawback=asa_creator_addr,
+ url="https://path/to/my/asset/details",
+ total=1,
+ decimals=0,
+ )
+ sactxn = actxn.sign(asa_creator_sk)
+ tx_id = algod_client.send_transaction(sactxn)
+ logger.info(f"Sent asset create transaction with txid: {tx_id}")
+ # Wait for the transaction to be confirmed
+ results = transaction.wait_for_confirmation(algod_client, tx_id, 4)
+ a_id = results["asset-index"]
+ logger.info(
+ f"Result confirmed in round: {
+ results['confirmed-round']} ASA ID : {results['asset-index']}"
+ )
+ logger.info(f"asa_creator created 10 ASA: {a_id}")
+
+ app_client.deploy(
+ on_schema_break=algokit_utils.OnSchemaBreak.AppendApp,
+ on_update=algokit_utils.OnUpdate.AppendApp,
+ )
+
+ teal_program = f"""
+ #pragma version 10
+ txn TypeEnum
+ int appl
+ ==
+ txn ApplicationID
+ int {app_client.app_id}
+ ==
+ &&
+ txn RekeyTo
+ global ZeroAddress
+ ==
+ &&
+ txn Fee
+ global MinTxnFee
+ ==
+ &&
+ return
+ """
+
+ def compile_program(teal_program):
+ compiled_program = algod_client.compile(teal_program)
+ return base64.b64decode(compiled_program["result"])
+
+ program = compile_program(teal_program)
+ lsig = transaction.LogicSigAccount(program)
+
+ # ******************** Plug_IN_Signer Account Generation *************************#
+ if False:
+ plug_in_sk, plug_in_addr = account.generate_account()
+ else:
+ plug_in_sk = """
+ Cq9JfCTzMp9bSKVNxuF2YsNm0fS9RsshOVbN6I8Av5zbhEH2I1Qd8UN6UhgOfD1REDY9/pNjPy+D++ib2xTAAg==
+ """
+ plug_in_addr = "3OCED5RDKQO7CQ32KIMA47B5KEIDMPP6SNRT6L4D7PUJXWYUYABBZG56JE"
+ # ******************** Plug_IN_Signer Public Signature *********************#
+ _, secret_key = nacl.bindings.crypto_sign_seed_keypair(
+ base64.b64decode(plug_in_sk)[: constants.key_len_bytes]
+ )
+ message = constants.logic_prefix + program
+ raw_signed = nacl.bindings.crypto_sign(message, secret_key)
+ crypto_sign_BYTES = nacl.bindings.crypto_sign_BYTES
+ signature = nacl.encoding.RawEncoder.encode(raw_signed[:crypto_sign_BYTES])
+ plug_in_public_sig = base64.b64encode(signature).decode()
+
+ owner_vault_msig = transaction.Multisig(1, 1, [owner_addr, plug_in_addr])
+ logger.info(f"Address owner_addr : {owner_addr}")
+ logger.info(f"Address owner_vault_msig : {owner_vault_msig.address()}")
+ logger.info(f"Address plug_in_addr : {plug_in_addr}")
+ logger.info(f"App add app_address : {app_client.app_address}")
+ logger.info(f"App ID app_id : {app_client.app_id}")
+ logger.info(f"Signer plug_in_public_sig: {plug_in_public_sig}")
+
+ if int(algod_client.account_info(owner_vault_msig.address())["amount"]) == 0:
+
+ # ********************* MSIG VAULT Generation *******************************#
+ # ********************* MSIG App Funding *******************************#
+ ptxn_app = transaction.PaymentTxn(
+ owner_addr,
+ sp,
+ app_client.app_address,
+ int(1e6)
+ )
+ # ********************* MSIG VAUL Funding *******************************#
+ ptxn_vault = transaction.PaymentTxn(
+ owner_addr,
+ sp,
+ owner_vault_msig.address(),
+ int(1e6)
+ )
+
+ transaction.assign_group_id([ptxn_app, ptxn_vault])
+
+ signed_ptxn_app = ptxn_app.sign(owner_sk)
+ signed_ptxn_vault = ptxn_vault.sign(owner_sk)
+
+ signed_group = [
+ signed_ptxn_app, signed_ptxn_vault
+ ]
+ logger.info(signed_group)
+ txid = algod_client.send_transactions(signed_group)
+ result: Dict[str, Any] = transaction.wait_for_confirmation(
+ algod_client, txid, 4
+ )
+ logger.info(f"txID: {txid} confirmed in round: {
+ result.get('confirmed-round', 0)}")
+
+ asa_creator_info = algod_client.account_info(asa_creator_addr)
+ if "assets" in asa_creator_info and (len(asa_creator_info["assets"]) > 0):
+ a_id = algod_client.account_info(asa_creator_addr)["assets"][0]["asset-id"]
+ else:
+ # ******************** asa_creator CREATE ASA ****************************#
+ logger.info("asa_creator Create ASA")
+ actxn = transaction.AssetConfigTxn(
+ sender=asa_creator_addr,
+ sp=sp,
+ default_frozen=False,
+ unit_name="rug2",
+ asset_name="2 Really Useful Gift",
+ manager=asa_creator_addr,
+ reserve=asa_creator_addr,
+ freeze=asa_creator_addr,
+ clawback=asa_creator_addr,
+ url="https://path/to/my/asset/details",
+ total=10,
+ decimals=0,
+ )
+ sactxn = actxn.sign(asa_creator_sk)
+ tx_id = algod_client.send_transaction(sactxn)
+ logger.info(f"Sent asset create transaction with txid: {tx_id}")
+ # Wait for the transaction to be confirmed
+ results = transaction.wait_for_confirmation(algod_client, tx_id, 4)
+ a_id = results["asset-index"]
+ logger.info(
+ f"Result confirmed in round: {
+ results['confirmed-round']} ASA ID : {results['asset-index']}"
+ )
+ logger.info(f"asa_creator created 10 ASA: {a_id}")
+
+ if (int(algod_client.account_info(app_client.app_address)["amount"]) <= 151300):
+ ptxn_signer = transaction.PaymentTxn(
+ dispenser.address, sp, app_client.app_address, int(151300)
+ ).sign(dispenser.private_key)
+ algod_client.send_transaction(ptxn_signer)
+ logger.info(" Set public Sig")
+ response = app_client.set_public_sig(
+ account=owner_addr,
+ sig=plug_in_public_sig,
+ transaction_parameters=algokit_utils.TransactionParameters(
+ boxes=[(app_client.app_id, encoding.decode_address(owner_addr))],
+ accounts=[owner_addr]
+ )
+ )
+ logger.info(f"result: {response.return_value}")
+
+ logger.info(" Get public Sig")
+ response = app_client.get_public_sig(
+ account=owner_addr,
+ transaction_parameters=algokit_utils.TransactionParameters(
+ boxes=[(app_client.app_id, encoding.decode_address(owner_addr))]
+ ),
+ )
+ logger.info(f"result: {response.return_value}")
+ atc = AtomicTransactionComposer()
+ client = algokit_utils.ApplicationClient(
+ algod_client,
+ app_id=app_client.app_id,
+ app_spec=app_client.app_spec)
+
+ composer = Composer(client, atc)
+ composer.opt_in(
+ id=a_id,
+ account=app_client.app_address,
+ transaction_parameters=algokit_utils.TransactionParameters(
+ foreign_assets=[a_id], signer=app_client.signer,
+ sender=owner_vault_msig.address()
+ ),
+ )
+ opt_in_txn = composer.atc.txn_list[0].txn
+
+ lsig.lsig.msig = owner_vault_msig
+ lsig.lsig.msig.subsigs[1].signature = base64.b64decode(plug_in_public_sig)
+ lstx = transaction.LogicSigTransaction(opt_in_txn, lsig)
+ optin_txid = algod_client.send_transaction(lstx)
+
+ logger.info(f"Sent Msig Vault Opt-in with txid: {optin_txid}")
+ # Wait for the transaction to be confirmed
+ results = transaction.wait_for_confirmation(algod_client, optin_txid, 4)
+ logger.info(f"Result confirmed in round: {results['confirmed-round']}")