Browse Source

Stub out algorand support in wormhole (#611)

* Stub out algorand support in wormhole

  1) Introduce the algorand chain constant in all the appropriate places
  2) Deploy pyth/hernandc algorand smart contracts into devnet
  3) Fund all the correct contracts for devnet testing

Change-Id: I6e4402b5b21223b32ea89653f8c7606f5c7f2843

* pr/jsiegel/algorand-v1: ALGORAND is not a EVM chain @ gusc1a-ossdev-jsl1

* pr/jsiegel/algorand-v1: fix lint @ gusc1a-ossdev-jsl1

* pr/jsiegel/algorand-v1: put the requirements into the image @ gusc1a-ossdev-jsl1

* jsiegel/algorand: make the watcher hang forever @ gusc1a-ossdev-jsl1

* jsiegel/algorand: comment these out @ gusc1a-ossdev-jsl1

* jsiegel/algorand: put this back in @ gusc1a-ossdev-jsl1

* jsiegel/algorand: fix guardian example @ gusc1a-ossdev-jsl1

* Generate teal source code

commit-id:a537a109

* jsiegel/algorand: it builds @ gusc1a-ossdev-jsl1

* pr/jsiegel/algorand-v1: add Dockerfile.teal @ gusc1a-ossdev-jsl1

* jsiegel/algorand: improve the dependencies @ gusc1a-ossdev-jsl1

* pr/jsiegel/algorand-v1: Fix up build @ gusc1a-ossdev-jsl1

* dead file

* pr/jsiegel/algorand-v1: remove more stuff @ gusc1a-ossdev-jsl1

* pr/jsiegel/algorand-v1: fix build @ gusc1a-ossdev-jsl1

* pr/jsiegel/algorand-v1: freeze the requirements @ gusc1a-ossdev-jsl1

* pr/jsiegel/algorand-v1: Fix teal to use pipenv @ gusc1a-ossdev-jsl1

* pr/jsiegel/algorand-v1: fix miss-merge @ gusc1a-ossdev-jsl1

Co-authored-by: Leo <leo@certus.one>
jumpsiegel 3 năm trước cách đây
mục cha
commit
f90ed66ca0

+ 4 - 0
DEVELOP.md

@@ -182,3 +182,7 @@ Node logs:
 Account list:
 
     kubectl exec -c goal-kmd algorand-0 -- ./goal account list
+
+Get yourself a working shell:
+
+    kubectl exec -c goal-kmd algorand-0 -it shell-demo -- /bin/bash

+ 21 - 0
Dockerfile.teal

@@ -0,0 +1,21 @@
+# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
+FROM docker.io/fedora:34 AS teal-build
+RUN dnf -y install python3-pip
+
+COPY staging/algorand/teal /teal
+
+# Install pyTEAL dependencies
+COPY third_party/algorand/Pipfile.lock Pipfile.lock
+COPY third_party/algorand/Pipfile Pipfile
+
+RUN pip install pipenv
+RUN pipenv install
+
+# Regenerate TEAL assembly
+RUN pipenv run python3 /teal/wormhole/pyteal/vaa-processor.py vaa-processor-approval.teal vaa-processor-clear.teal
+RUN pipenv run python3 /teal/wormhole/pyteal/vaa-verify.py 0 vaa-verify.teal
+
+FROM scratch AS teal-export
+COPY --from=teal-build /vaa-processor-approval.teal third_party/algorand/teal/
+COPY --from=teal-build /vaa-processor-clear.teal third_party/algorand/teal/
+COPY --from=teal-build /vaa-verify.teal third_party/algorand/teal/

+ 9 - 0
Tiltfile

@@ -86,6 +86,14 @@ local_resource(
     trigger_mode = trigger_mode,
 )
 
+local_resource(
+    name = "teal-gen",
+    deps = ["staging/algorand/teal"],
+    cmd = "tilt docker build -- --target teal-export -f Dockerfile.teal -o type=local,dest=. .",
+    env = {"DOCKER_BUILDKIT": "1"},
+    trigger_mode = trigger_mode,
+)
+
 # wasm
 
 local_resource(
@@ -301,6 +309,7 @@ docker_build(
 
 k8s_resource(
     "algorand",
+    resource_deps = ["teal-gen"],
     port_forwards = [
         port_forward(4001, name = "Algorand RPC [:4001]", host = webHost),
         port_forward(4002, name = "Algorand KMD [:4002]", host = webHost),

+ 1 - 1
devnet/algorand.yaml

@@ -52,7 +52,7 @@ spec:
           command:
             - /bin/sh
             - -c
-            - ./goal kmd start -d /network/Node && ./goal account list && sleep infinity
+            - ./goal kmd start -d /network/Node && ./goal account list && /setup/setup.sh && sleep infinity
           ports:
             - containerPort: 4002
               name: kmd

+ 6 - 0
devnet/node.yaml

@@ -94,6 +94,12 @@ spec:
             - http://terra-lcd:1317
             - --terraContract
             - terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5
+            - --algorandRPC
+            - http://localhost:4001
+            - --algorandToken
+            - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+            - --algorandContract
+            - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
             - --solanaContract
             - Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o
             - --solanaWS

+ 32 - 0
node/cmd/guardiand/node.go

@@ -37,6 +37,8 @@ import (
 
 	"github.com/certusone/wormhole/node/pkg/terra"
 
+	"github.com/certusone/wormhole/node/pkg/algorand"
+
 	ipfslog "github.com/ipfs/go-log/v2"
 )
 
@@ -78,6 +80,10 @@ var (
 	terraLCD      *string
 	terraContract *string
 
+	algorandRPC      *string
+	algorandToken    *string
+	algorandContract *string
+
 	solanaWsRPC *string
 	solanaRPC   *string
 
@@ -145,6 +151,10 @@ func init() {
 	terraLCD = NodeCmd.Flags().String("terraLCD", "", "Path to LCD service root for http calls")
 	terraContract = NodeCmd.Flags().String("terraContract", "", "Wormhole contract address on Terra blockchain")
 
+	algorandRPC = NodeCmd.Flags().String("algorandRPC", "", "Algorand RPC URL")
+	algorandToken = NodeCmd.Flags().String("algorandToken", "", "Algorand access token")
+	algorandContract = NodeCmd.Flags().String("algorandContract", "", "Algorand contract")
+
 	solanaWsRPC = NodeCmd.Flags().String("solanaWS", "", "Solana Websocket URL (required")
 	solanaRPC = NodeCmd.Flags().String("solanaRPC", "", "Solana RPC URL (required")
 
@@ -248,6 +258,9 @@ func runNode(cmd *cobra.Command, args []string) {
 	readiness.RegisterComponent(common.ReadinessEthSyncing)
 	readiness.RegisterComponent(common.ReadinessSolanaSyncing)
 	readiness.RegisterComponent(common.ReadinessTerraSyncing)
+	if *unsafeDevMode {
+		readiness.RegisterComponent(common.ReadinessAlgorandSyncing)
+	}
 	readiness.RegisterComponent(common.ReadinessBSCSyncing)
 	readiness.RegisterComponent(common.ReadinessPolygonSyncing)
 	readiness.RegisterComponent(common.ReadinessAvalancheSyncing)
@@ -384,6 +397,18 @@ func runNode(cmd *cobra.Command, args []string) {
 		logger.Fatal("Please specify --terraContract")
 	}
 
+	if *unsafeDevMode {
+		if *algorandRPC == "" {
+			logger.Fatal("Please specify --algorandRPC")
+		}
+		if *algorandToken == "" {
+			logger.Fatal("Please specify --algorandToken")
+		}
+		if *algorandContract == "" {
+			logger.Fatal("Please specify --algorandContract")
+		}
+	}
+
 	if *bigTablePersistenceEnabled {
 		if *bigTableGCPProject == "" {
 			logger.Fatal("Please specify --bigTableGCPProject")
@@ -585,6 +610,13 @@ func runNode(cmd *cobra.Command, args []string) {
 			return err
 		}
 
+		if *unsafeDevMode {
+			if err := supervisor.Run(ctx, "algorandwatch",
+				algorand.NewWatcher(*algorandRPC, *algorandToken, *algorandContract, lockC, setC).Run); err != nil {
+				return err
+			}
+		}
+
 		if err := supervisor.Run(ctx, "solwatch-confirmed",
 			solana.NewSolanaWatcher(*solanaWsRPC, *solanaRPC, solAddress, lockC, rpc.CommitmentConfirmed).Run); err != nil {
 			return err

+ 30 - 0
node/pkg/algorand/watcher.go

@@ -0,0 +1,30 @@
+package algorand
+
+import (
+	"context"
+	"github.com/certusone/wormhole/node/pkg/common"
+	"github.com/certusone/wormhole/node/pkg/readiness"
+)
+
+type (
+	// Watcher is responsible for looking over Algorand blockchain and reporting new transactions to the contract
+	Watcher struct {
+		urlRPC   string
+		urlToken string
+		contract string
+
+		msgChan chan *common.MessagePublication
+		setChan chan *common.GuardianSet
+	}
+)
+
+// NewWatcher creates a new Algorand contract watcher
+func NewWatcher(urlRPC string, urlToken string, contract string, lockEvents chan *common.MessagePublication, setEvents chan *common.GuardianSet) *Watcher {
+	return &Watcher{urlRPC: urlRPC, urlToken: urlToken, contract: contract, msgChan: lockEvents, setChan: setEvents}
+}
+
+func (e *Watcher) Run(ctx context.Context) error {
+	readiness.SetReady(common.ReadinessAlgorandSyncing)
+
+	select {}
+}

+ 1 - 0
node/pkg/common/readiness.go

@@ -6,6 +6,7 @@ const (
 	ReadinessEthSyncing        readiness.Component = "ethSyncing"
 	ReadinessSolanaSyncing     readiness.Component = "solanaSyncing"
 	ReadinessTerraSyncing      readiness.Component = "terraSyncing"
+	ReadinessAlgorandSyncing   readiness.Component = "algorandSyncing"
 	ReadinessBSCSyncing        readiness.Component = "bscSyncing"
 	ReadinessPolygonSyncing    readiness.Component = "polygonSyncing"
 	ReadinessEthRopstenSyncing readiness.Component = "ethRopstenSyncing"

+ 6 - 0
node/pkg/vaa/structs.go

@@ -98,6 +98,8 @@ func (c ChainID) String() string {
 		return "avalanche"
 	case ChainIDOasis:
 		return "oasis"
+	case ChainIDAlgorand:
+		return "algorand"
 	case ChainIDEthereumRopsten:
 		return "ethereum-ropsten"
 	default:
@@ -123,6 +125,8 @@ func ChainIDFromString(s string) (ChainID, error) {
 		return ChainIDAvalanche, nil
 	case "oasis":
 		return ChainIDOasis, nil
+	case "algorand":
+		return ChainIDAlgorand, nil
 	case "ethereum-ropsten":
 		return ChainIDEthereumRopsten, nil
 	default:
@@ -146,6 +150,8 @@ const (
 	ChainIDAvalanche ChainID = 6
 	// ChainIDOasis is the ChainID of Oasis
 	ChainIDOasis ChainID = 7
+	// ChainIDAlgorand is the ChainID of Algorand
+	ChainIDAlgorand ChainID = 8
 
 	// ChainIDEthereumRopsten is the ChainID of Ethereum Ropsten
 	ChainIDEthereumRopsten ChainID = 10001

+ 1 - 0
proto/publicrpc/v1/publicrpc.proto

@@ -16,6 +16,7 @@ enum ChainID {
   CHAIN_ID_POLYGON = 5;
   CHAIN_ID_AVALANCHE = 6;
   CHAIN_ID_OASIS = 7;
+  CHAIN_ID_ALGORAND = 8;
   // Special case - Eth has two testnets. CHAIN_ID_ETHEREUM is Goerli,
   // but we also want to connect to Ropsten, so we add a separate chain.
   CHAIN_ID_ETHEREUM_ROPSTEN = 10001;

+ 2 - 1
sdk/js/src/utils/consts.ts

@@ -1,4 +1,4 @@
-export type ChainId = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10001;
+export type ChainId = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10001;
 export const CHAIN_ID_SOLANA: ChainId = 1;
 export const CHAIN_ID_ETH: ChainId = 2;
 export const CHAIN_ID_TERRA: ChainId = 3;
@@ -6,6 +6,7 @@ export const CHAIN_ID_BSC: ChainId = 4;
 export const CHAIN_ID_POLYGON: ChainId = 5;
 export const CHAIN_ID_AVAX: ChainId = 6;
 export const CHAIN_ID_OASIS: ChainId = 7;
+export const CHAIN_ID_ALGORAND: ChainId = 8;
 export const CHAIN_ID_ETHEREUM_ROPSTEN: ChainId = 10001;
 
 export const WSOL_ADDRESS = "So11111111111111111111111111111111111111112";

+ 2 - 2
staging/algorand/scripts/createapp.sh

@@ -120,10 +120,10 @@ if __name__ == "__main__":
     print("VAA Verify Stateless Program, (c) 2021-22 Randlabs Inc. ")
     print("Compiling...")
 
-    if len(sys.argv) >= 2:
+    if len(sys.argv) >= 1:
         appid = sys.argv[1]
 
-    if len(sys.argv) >= 3:
+    if len(sys.argv) >= 2:
         outfile = sys.argv[2]
 
     with open(outfile, "w") as f:

+ 2 - 0
third_party/algorand/.gitignore

@@ -0,0 +1,2 @@
+# Generated
+teal

+ 11 - 0
third_party/algorand/Dockerfile

@@ -3,6 +3,11 @@ FROM docker.io/algorand/stable:3.2.1@sha256:0a87978492680fd98e2cc410f59f2bfd7fef
 
 RUN mkdir -p /setup
 ADD template.json /setup/
+ADD setup.sh /setup/
+ADD setup.py /setup/
+ADD teal/vaa-verify.teal /setup/
+ADD teal/vaa-processor-clear.teal /setup/
+ADD teal/vaa-processor-approval.teal /setup/
 
 RUN ./goal network create -n sandnet -r /network -t /setup/template.json && echo rawr
 
@@ -14,3 +19,9 @@ ADD config.json /network/Node/config.json
 ADD kmd_config.json /network/Node/kmd-v0.5/kmd_config.json
 
 ENV ALGORAND_DATA=/network/Node
+
+ADD Pipfile.lock /setup/
+ADD Pipfile /setup/
+RUN apt-get update
+RUN apt-get install -y python3-pip
+RUN pip install pipenv

+ 12 - 0
third_party/algorand/Pipfile

@@ -0,0 +1,12 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+py-algorand-sdk = "*"
+pyteal = "*"
+mypy = "*"
+pytest = "*"
+
+[dev-packages]

+ 316 - 0
third_party/algorand/Pipfile.lock

@@ -0,0 +1,316 @@
+{
+    "_meta": {
+        "hash": {
+            "sha256": "7d3b87829ef46d00b82e4450cc45b6ca299dc0a5e9bba990744eda1c901d8af4"
+        },
+        "pipfile-spec": 6,
+        "requires": {},
+        "sources": [
+            {
+                "name": "pypi",
+                "url": "https://pypi.org/simple",
+                "verify_ssl": true
+            }
+        ]
+    },
+    "default": {
+        "attrs": {
+            "hashes": [
+                "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
+                "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==21.2.0"
+        },
+        "cffi": {
+            "hashes": [
+                "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3",
+                "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2",
+                "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636",
+                "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20",
+                "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728",
+                "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27",
+                "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66",
+                "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443",
+                "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0",
+                "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7",
+                "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39",
+                "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605",
+                "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a",
+                "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37",
+                "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029",
+                "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139",
+                "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc",
+                "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df",
+                "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14",
+                "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880",
+                "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2",
+                "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a",
+                "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e",
+                "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474",
+                "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024",
+                "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8",
+                "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0",
+                "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e",
+                "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a",
+                "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e",
+                "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032",
+                "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6",
+                "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e",
+                "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b",
+                "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e",
+                "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954",
+                "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962",
+                "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c",
+                "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4",
+                "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55",
+                "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962",
+                "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023",
+                "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c",
+                "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6",
+                "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8",
+                "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382",
+                "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7",
+                "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc",
+                "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997",
+                "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"
+            ],
+            "version": "==1.15.0"
+        },
+        "iniconfig": {
+            "hashes": [
+                "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
+                "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
+            ],
+            "version": "==1.1.1"
+        },
+        "msgpack": {
+            "hashes": [
+                "sha256:0d8c332f53ffff01953ad25131272506500b14750c1d0ce8614b17d098252fbc",
+                "sha256:1c58cdec1cb5fcea8c2f1771d7b5fec79307d056874f746690bd2bdd609ab147",
+                "sha256:2c3ca57c96c8e69c1a0d2926a6acf2d9a522b41dc4253a8945c4c6cd4981a4e3",
+                "sha256:2f30dd0dc4dfe6231ad253b6f9f7128ac3202ae49edd3f10d311adc358772dba",
+                "sha256:2f97c0f35b3b096a330bb4a1a9247d0bd7e1f3a2eba7ab69795501504b1c2c39",
+                "sha256:36a64a10b16c2ab31dcd5f32d9787ed41fe68ab23dd66957ca2826c7f10d0b85",
+                "sha256:3d875631ecab42f65f9dce6f55ce6d736696ced240f2634633188de2f5f21af9",
+                "sha256:40fb89b4625d12d6027a19f4df18a4de5c64f6f3314325049f219683e07e678a",
+                "sha256:47d733a15ade190540c703de209ffbc42a3367600421b62ac0c09fde594da6ec",
+                "sha256:494471d65b25a8751d19c83f1a482fd411d7ca7a3b9e17d25980a74075ba0e88",
+                "sha256:51fdc7fb93615286428ee7758cecc2f374d5ff363bdd884c7ea622a7a327a81e",
+                "sha256:6eef0cf8db3857b2b556213d97dd82de76e28a6524853a9beb3264983391dc1a",
+                "sha256:6f4c22717c74d44bcd7af353024ce71c6b55346dad5e2cc1ddc17ce8c4507c6b",
+                "sha256:73a80bd6eb6bcb338c1ec0da273f87420829c266379c8c82fa14c23fb586cfa1",
+                "sha256:89908aea5f46ee1474cc37fbc146677f8529ac99201bc2faf4ef8edc023c2bf3",
+                "sha256:8a3a5c4b16e9d0edb823fe54b59b5660cc8d4782d7bf2c214cb4b91a1940a8ef",
+                "sha256:96acc674bb9c9be63fa8b6dabc3248fdc575c4adc005c440ad02f87ca7edd079",
+                "sha256:973ad69fd7e31159eae8f580f3f707b718b61141838321c6fa4d891c4a2cca52",
+                "sha256:9b6f2d714c506e79cbead331de9aae6837c8dd36190d02da74cb409b36162e8a",
+                "sha256:9c0903bd93cbd34653dd63bbfcb99d7539c372795201f39d16fdfde4418de43a",
+                "sha256:9fce00156e79af37bb6db4e7587b30d11e7ac6a02cb5bac387f023808cd7d7f4",
+                "sha256:a598d0685e4ae07a0672b59792d2cc767d09d7a7f39fd9bd37ff84e060b1a996",
+                "sha256:b0a792c091bac433dfe0a70ac17fc2087d4595ab835b47b89defc8bbabcf5c73",
+                "sha256:bb87f23ae7d14b7b3c21009c4b1705ec107cb21ee71975992f6aca571fb4a42a",
+                "sha256:bf1e6bfed4860d72106f4e0a1ab519546982b45689937b40257cfd820650b920",
+                "sha256:c1ba333b4024c17c7591f0f372e2daa3c31db495a9b2af3cf664aef3c14354f7",
+                "sha256:c2140cf7a3ec475ef0938edb6eb363fa704159e0bf71dde15d953bacc1cf9d7d",
+                "sha256:c7e03b06f2982aa98d4ddd082a210c3db200471da523f9ac197f2828e80e7770",
+                "sha256:d02cea2252abc3756b2ac31f781f7a98e89ff9759b2e7450a1c7a0d13302ff50",
+                "sha256:da24375ab4c50e5b7486c115a3198d207954fe10aaa5708f7b65105df09109b2",
+                "sha256:e4c309a68cb5d6bbd0c50d5c71a25ae81f268c2dc675c6f4ea8ab2feec2ac4e2",
+                "sha256:f01b26c2290cbd74316990ba84a14ac3d599af9cebefc543d241a66e785cf17d",
+                "sha256:f201d34dc89342fabb2a10ed7c9a9aaaed9b7af0f16a5923f1ae562b31258dea",
+                "sha256:f74da1e5fcf20ade12c6bf1baa17a2dc3604958922de8dc83cbe3eff22e8b611"
+            ],
+            "version": "==1.0.3"
+        },
+        "mypy": {
+            "hashes": [
+                "sha256:13b3c110309b53f5a62aa1b360f598124be33a42563b790a2a9efaacac99f1fc",
+                "sha256:140174e872d20d4768124a089b9f9fc83abd6a349b7f8cc6276bc344eb598922",
+                "sha256:31895b0b3060baf15bf76e789d94722c026f673b34b774bba9e8772295edccff",
+                "sha256:331a81d2c9bf1be25317260a073b41f4584cd11701a7c14facef0aa5a005e843",
+                "sha256:40cb062f1b7ff4cd6e897a89d8ddc48c6ad7f326b5277c93a8c559564cc1551c",
+                "sha256:41f3575b20714171c832d8f6c7aaaa0d499c9a2d1b8adaaf837b4c9065c38540",
+                "sha256:431be889ffc8d9681813a45575c42e341c19467cbfa6dd09bf41467631feb530",
+                "sha256:562a0e335222d5bbf5162b554c3afe3745b495d67c7fe6f8b0d1b5bace0c1eeb",
+                "sha256:618e677aabd21f30670bffb39a885a967337f5b112c6fb7c79375e6dced605d6",
+                "sha256:69b5a835b12fdbfeed84ef31152d41343d32ccb2b345256d8682324409164330",
+                "sha256:71c77bd885d2ce44900731d4652d0d1c174dc66a0f11200e0c680bdedf1a6b37",
+                "sha256:82e6c15675264e923b60a11d6eb8f90665504352e68edfbb4a79aac7a04caddd",
+                "sha256:98b4f91a75fed2e4c6339e9047aba95968d3a7c4b91e92ab9dc62c0c583564f4",
+                "sha256:993c2e52ea9570e6e872296c046c946377b9f5e89eeb7afea2a1524cf6e50b27",
+                "sha256:9cd316e9705555ca6a50670ba5fb0084d756d1d8cb1697c83820b1456b0bc5f3",
+                "sha256:a55438627f5f546192f13255a994d6d1cf2659df48adcf966132b4379fd9c86b",
+                "sha256:df0fec878ccfcb2d1d2306ba31aa757848f681e7bbed443318d9bbd4b0d0fe9a",
+                "sha256:e091fe58b4475b3504dc7c3022ff7f4af2f9e9ddf7182047111759ed0973bbde",
+                "sha256:f8b2059f73878e92eff7ed11a03515d6572f4338a882dd7547b5f7dd242118e6",
+                "sha256:ffb1e57ec49a30e3c0ebcfdc910ae4aceb7afb649310b7355509df6b15bd75f6"
+            ],
+            "index": "pypi",
+            "version": "==0.920"
+        },
+        "mypy-extensions": {
+            "hashes": [
+                "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
+                "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
+            ],
+            "version": "==0.4.3"
+        },
+        "packaging": {
+            "hashes": [
+                "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
+                "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==21.3"
+        },
+        "pluggy": {
+            "hashes": [
+                "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
+                "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==1.0.0"
+        },
+        "py": {
+            "hashes": [
+                "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719",
+                "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==1.11.0"
+        },
+        "py-algorand-sdk": {
+            "hashes": [
+                "sha256:110480dc5baf2102721a5fe860c22fb445a737dfbb0a2e82ed97fee03b792d19",
+                "sha256:691771118f88855affbb57853a73113ce0ea554d7b9f1bbe50e309a83a632158"
+            ],
+            "index": "pypi",
+            "version": "==1.8.0"
+        },
+        "pycparser": {
+            "hashes": [
+                "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
+                "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"
+            ],
+            "version": "==2.21"
+        },
+        "pycryptodomex": {
+            "hashes": [
+                "sha256:08c809e9f4be8d4f9948cf4d5ebc7431bbd9e1c0cd5ff478d0d5229f1bc4ad50",
+                "sha256:097095a7c24b9e7eec865417f620f78adf218162c03b68e4fde194bf87801a67",
+                "sha256:0981e8071d2ee457d842821438f833e362eed9a25a445d54ad7610b24293118f",
+                "sha256:1bd9d158afa33dca04748b23e7b9d4055f8c8015ace2e972a866519af02d5eed",
+                "sha256:1f6c370abf11546b1c9b70062918d601ac8fb7ff113554601b43175eed7480ef",
+                "sha256:2595b7be43b8b2da953ea3506a8d71c07fc9b479d5c118b0c44a5eca2a1664f6",
+                "sha256:2d173a5db4e306cd32558b1a3ceb45bd2ebeb6596525fd5945963798b3851e3d",
+                "sha256:33c06d6819a0204fac675d100f92aa472c704cd774a47171a5949c75c1040ed6",
+                "sha256:3559da56e1045ad567e69fcc74245073fe1943b07b137bfd1073c7a540a89df7",
+                "sha256:3bfa2936f8391bfaa17ed6a5c726e33acad56d7b47b8bf824b1908b16b140025",
+                "sha256:4361881388817f89aa819a553e987200a6eb664df995632b063997dd373a7cee",
+                "sha256:43af464dcac1ae53e6e14a0ae6f08373b538f3c49fb9e426423618571edfecff",
+                "sha256:44097663c62b3aa03b5b403b816dedafa592984e8c6857a061ade41f32a2666e",
+                "sha256:4cbaea8ab8bfa283e6219af39624d921f72f8174765a35416aab4d4b4dec370e",
+                "sha256:5b0fd9fc81d43cd54dc8e4b2df8730ffd1e34f1f0679920deae16f6487aa1414",
+                "sha256:676d9f4286f490612fa35ca8fe4b1fced8ff18e653abc1dda34fbf166129d6c2",
+                "sha256:79ad48096ceb5c714fbc4dc82e3e6b37f095f627b1fef90d94d85e19a19d6611",
+                "sha256:83379f1fd7b99c4993f5e905f2a6ddb9003996655151ea3c2ee77332ad009d08",
+                "sha256:88dc997e3e9199a0d67b547fba36c6d1c54fca7d83c4bfe0d3f34f55a4717a2c",
+                "sha256:8c5b97953130ff76500c6e8e159f2b881c737ebf00034006517b57f382d5317c",
+                "sha256:922e9dac0166e4617e5c7980d2cff6912a6eb5cb5c13e7ece222438650bd7f66",
+                "sha256:9c037aaf6affc8f7c4f6f9f6279aa57dd526734246fb5221a0fff3124f57e0b1",
+                "sha256:a896b41c518269c1cceb582e298a868e6c74bb3cbfd362865ea686c78aebe91d",
+                "sha256:b1a6f17c4ad896ed628663b021cd797b098c7e9537fd259958f6ffb3b8921081",
+                "sha256:b5ddaee74e1f06af9c0765a147904dddacf4ca9707f8f079e14e2b14b4f5a544",
+                "sha256:d55374ebc36de7a3217f2e2318886f0801dd5e486e21aba1fc4ca08e3b6637d7",
+                "sha256:ddac6a092b97aa11d2a21aec33e941b4453ef774da3d98f2b7c1e01da05e6d5e",
+                "sha256:de9832ac3c51484fe1757ca4303695423b16cfa54874dae9239bf41f50a2affa",
+                "sha256:e42a82c63952ed70be3c13782c6984a519b514e6b10108a9647c7576b6c86650",
+                "sha256:ea8e83bf4731a2369350d7771a1f2ef8d72ad3da70a37d86b1374be8c675abd0"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+            "version": "==3.12.0"
+        },
+        "pynacl": {
+            "hashes": [
+                "sha256:06cbb4d9b2c4bd3c8dc0d267416aaed79906e7b33f114ddbf0911969794b1cc4",
+                "sha256:11335f09060af52c97137d4ac54285bcb7df0cef29014a1a4efe64ac065434c4",
+                "sha256:2fe0fc5a2480361dcaf4e6e7cea00e078fcda07ba45f811b167e3f99e8cff574",
+                "sha256:30f9b96db44e09b3304f9ea95079b1b7316b2b4f3744fe3aaecccd95d547063d",
+                "sha256:4e10569f8cbed81cb7526ae137049759d2a8d57726d52c1a000a3ce366779634",
+                "sha256:511d269ee845037b95c9781aa702f90ccc36036f95d0f31373a6a79bd8242e25",
+                "sha256:537a7ccbea22905a0ab36ea58577b39d1fa9b1884869d173b5cf111f006f689f",
+                "sha256:54e9a2c849c742006516ad56a88f5c74bf2ce92c9f67435187c3c5953b346505",
+                "sha256:757250ddb3bff1eecd7e41e65f7f833a8405fede0194319f87899690624f2122",
+                "sha256:7757ae33dae81c300487591c68790dfb5145c7d03324000433d9a2c141f82af7",
+                "sha256:7c6092102219f59ff29788860ccb021e80fffd953920c4a8653889c029b2d420",
+                "sha256:8122ba5f2a2169ca5da936b2e5a511740ffb73979381b4229d9188f6dcb22f1f",
+                "sha256:9c4a7ea4fb81536c1b1f5cc44d54a296f96ae78c1ebd2311bd0b60be45a48d96",
+                "sha256:c914f78da4953b33d4685e3cdc7ce63401247a21425c16a39760e282075ac4a6",
+                "sha256:cd401ccbc2a249a47a3a1724c2918fcd04be1f7b54eb2a5a71ff915db0ac51c6",
+                "sha256:d452a6746f0a7e11121e64625109bc4468fc3100452817001dbe018bb8b08514",
+                "sha256:ea6841bc3a76fa4942ce00f3bda7d436fda21e2d91602b9e21b7ca9ecab8f3ff",
+                "sha256:f8851ab9041756003119368c1e6cd0b9c631f46d686b3904b18c0139f4419f80"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.4.0"
+        },
+        "pyparsing": {
+            "hashes": [
+                "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4",
+                "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==3.0.6"
+        },
+        "pyteal": {
+            "hashes": [
+                "sha256:6a7fbb2155ee79e0b6dfbd635e6ac4508a0c2279ddb4203f1c356b47bd684bb6",
+                "sha256:e395cf30fce630ac26157132ae4e0240845060a858a1a162aeac8b92a286b554"
+            ],
+            "index": "pypi",
+            "version": "==0.9.1"
+        },
+        "pytest": {
+            "hashes": [
+                "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
+                "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
+            ],
+            "index": "pypi",
+            "version": "==6.2.5"
+        },
+        "six": {
+            "hashes": [
+                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
+                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==1.16.0"
+        },
+        "toml": {
+            "hashes": [
+                "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
+                "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
+            ],
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+            "version": "==0.10.2"
+        },
+        "tomli": {
+            "hashes": [
+                "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224",
+                "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"
+            ],
+            "markers": "python_version >= '3.7'",
+            "version": "==2.0.0"
+        },
+        "typing-extensions": {
+            "hashes": [
+                "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e",
+                "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==4.0.1"
+        }
+    },
+    "develop": {}
+}

+ 248 - 0
third_party/algorand/setup.py

@@ -0,0 +1,248 @@
+from time import time, sleep
+from typing import List, Tuple, Dict, Any, Optional, Union
+from base64 import b64decode
+import base64
+import random
+import hashlib
+
+from algosdk.v2client.algod import AlgodClient
+from algosdk.kmd import KMDClient
+from algosdk import account, mnemonic
+from algosdk.future import transaction
+from algosdk.encoding import decode_address
+from pyteal import compileTeal, Mode, Expr
+from pyteal import *
+from algosdk.logic import get_application_address
+
+import pprint
+
+# Q5XDfcbiqiBwfMlY3gO1Mb0vyNCO+szD3v9azhrG16iO5Z5aTduNzeut/FLG0NOG0+txrBGN6lhi5iwytgkyKg==
+
+# position atom discover cluster fiction amused toe siren slam author surround spread garage craft isolate whisper kangaroo kitchen lend toss culture various effort absent kidney
+
+class Account:
+    """Represents a private key and address for an Algorand account"""
+
+    def __init__(self, privateKey: str) -> None:
+        self.sk = privateKey
+        self.addr = account.address_from_private_key(privateKey)
+#        print (privateKey + " -> " + self.getMnemonic())
+
+    def getAddress(self) -> str:
+        return self.addr
+
+    def getPrivateKey(self) -> str:
+        return self.sk
+
+    def getMnemonic(self) -> str:
+        return mnemonic.from_private_key(self.sk)
+
+    @classmethod
+    def FromMnemonic(cls, m: str) -> "Account":
+        return cls(mnemonic.to_private_key(m))
+
+class PendingTxnResponse:
+    def __init__(self, response: Dict[str, Any]) -> None:
+        self.poolError: str = response["pool-error"]
+        self.txn: Dict[str, Any] = response["txn"]
+
+        self.applicationIndex: Optional[int] = response.get("application-index")
+        self.assetIndex: Optional[int] = response.get("asset-index")
+        self.closeRewards: Optional[int] = response.get("close-rewards")
+        self.closingAmount: Optional[int] = response.get("closing-amount")
+        self.confirmedRound: Optional[int] = response.get("confirmed-round")
+        self.globalStateDelta: Optional[Any] = response.get("global-state-delta")
+        self.localStateDelta: Optional[Any] = response.get("local-state-delta")
+        self.receiverRewards: Optional[int] = response.get("receiver-rewards")
+        self.senderRewards: Optional[int] = response.get("sender-rewards")
+
+        self.innerTxns: List[Any] = response.get("inner-txns", [])
+        self.logs: List[bytes] = [b64decode(l) for l in response.get("logs", [])]
+
+class Setup:
+    def __init__(self) -> None:
+        self.ALGOD_ADDRESS = "http://localhost:4001"
+        self.ALGOD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        self.FUNDING_AMOUNT = 100_000_000
+
+        self.KMD_ADDRESS = "http://localhost:4002"
+        self.KMD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+        self.KMD_WALLET_NAME = "unencrypted-default-wallet"
+        self.KMD_WALLET_PASSWORD = ""
+
+        self.TARGET_ACCOUNT = "position atom discover cluster fiction amused toe siren slam author surround spread garage craft isolate whisper kangaroo kitchen lend toss culture various effort absent kidney"
+
+        self.kmdAccounts : Optional[List[Account]] = None
+
+        self.accountList : List[Account] = []
+
+        self.APPROVAL_PROGRAM = b""
+        self.CLEAR_STATE_PROGRAM = b""
+
+    def waitForTransaction(
+            self, client: AlgodClient, txID: str, timeout: int = 10
+    ) -> PendingTxnResponse:
+        lastStatus = client.status()
+        lastRound = lastStatus["last-round"]
+        startRound = lastRound
+    
+        while lastRound < startRound + timeout:
+            pending_txn = client.pending_transaction_info(txID)
+    
+            if pending_txn.get("confirmed-round", 0) > 0:
+                return PendingTxnResponse(pending_txn)
+    
+            if pending_txn["pool-error"]:
+                raise Exception("Pool error: {}".format(pending_txn["pool-error"]))
+    
+            lastStatus = client.status_after_block(lastRound + 1)
+    
+            lastRound += 1
+    
+        raise Exception(
+            "Transaction {} not confirmed after {} rounds".format(txID, timeout)
+        )
+
+    def getKmdClient(self) -> KMDClient:
+        return KMDClient(self.KMD_TOKEN, self.KMD_ADDRESS)
+    
+    def getGenesisAccounts(self) -> List[Account]:
+        if self.kmdAccounts is None:
+            kmd = self.getKmdClient()
+    
+            wallets = kmd.list_wallets()
+            walletID = None
+            for wallet in wallets:
+                if wallet["name"] == self.KMD_WALLET_NAME:
+                    walletID = wallet["id"]
+                    break
+    
+            if walletID is None:
+                raise Exception("Wallet not found: {}".format(self.KMD_WALLET_NAME))
+    
+            walletHandle = kmd.init_wallet_handle(walletID, self.KMD_WALLET_PASSWORD)
+    
+            try:
+                addresses = kmd.list_keys(walletHandle)
+                privateKeys = [
+                    kmd.export_key(walletHandle, self.KMD_WALLET_PASSWORD, addr)
+                    for addr in addresses
+                ]
+                self.kmdAccounts = [Account(sk) for sk in privateKeys]
+            finally:
+                kmd.release_wallet_handle(walletHandle)
+    
+        return self.kmdAccounts
+    
+    def getTargetAccount(self) -> Account:
+        return Account.FromMnemonic(self.TARGET_ACCOUNT)
+
+    def fundTargetAccount(self, client: AlgodClient, target: Account):
+        print("fundTargetAccount")
+        genesisAccounts = self.getGenesisAccounts()
+        suggestedParams = client.suggested_params()
+    
+        for fundingAccount in genesisAccounts:
+            txn = transaction.PaymentTxn(
+                    sender=fundingAccount.getAddress(),
+                    receiver=target.getAddress(),
+                    amt=self.FUNDING_AMOUNT,
+                    sp=suggestedParams,
+                )
+            pprint.pprint(txn)
+            print("signing txn")
+            stxn = txn.sign(fundingAccount.getPrivateKey())
+            print("sending txn")
+            client.send_transaction(stxn)
+            print("waiting for txn")
+            self.waitForTransaction(client, stxn.get_txid())
+
+    def getAlgodClient(self) -> AlgodClient:
+        return AlgodClient(self.ALGOD_TOKEN, self.ALGOD_ADDRESS)
+
+    def getBalances(self, client: AlgodClient, account: str) -> Dict[int, int]:
+        balances: Dict[int, int] = dict()
+    
+        accountInfo = client.account_info(account)
+    
+        # set key 0 to Algo balance
+        balances[0] = accountInfo["amount"]
+    
+        assets: List[Dict[str, Any]] = accountInfo.get("assets", [])
+        for assetHolding in assets:
+            assetID = assetHolding["asset-id"]
+            amount = assetHolding["amount"]
+            balances[assetID] = amount
+    
+        return balances
+
+    def setup(self):
+        self.client = self.getAlgodClient()
+
+        self.target = self.getTargetAccount()
+        
+        b = self.getBalances(self.client, self.target.getAddress())
+        if (b[0] < 100000000):
+            print("Account needs money... funding it")
+            self.fundTargetAccount(self.client, self.target)
+        print(self.getBalances(self.client, self.target.getAddress()))
+
+
+    def deploy(self):
+        vaa_processor_approval = self.client.compile(open("vaa-processor-approval.teal", "r").read())
+        vaa_processor_clear = self.client.compile(open("vaa-processor-clear.teal", "r").read())
+        vaa_verify = self.client.compile(open("vaa-verify.teal", "r").read())
+        verify_hash = vaa_verify['hash']
+        print("verify_hash " + verify_hash + " " + str(len(decode_address(verify_hash))))
+
+        globalSchema = transaction.StateSchema(num_uints=4, num_byte_slices=20)
+        localSchema = transaction.StateSchema(num_uints=0, num_byte_slices=0)
+    
+        app_args = [ "beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe", 0, 0 ]
+    
+        txn = transaction.ApplicationCreateTxn(
+            sender=self.target.getAddress(),
+            on_complete=transaction.OnComplete.NoOpOC,
+            approval_program=b64decode(vaa_processor_approval["result"]),
+            clear_program=b64decode(vaa_processor_clear["result"]),
+            global_schema=globalSchema,
+            local_schema=localSchema,
+            app_args=app_args,
+            sp=self.client.suggested_params(),
+        )
+    
+        signedTxn = txn.sign(self.target.getPrivateKey())
+        self.client.send_transaction(signedTxn)
+        response = self.waitForTransaction(self.client, signedTxn.get_txid())
+        assert response.applicationIndex is not None and response.applicationIndex > 0
+        print("app_id: ", response.applicationIndex)
+
+        appAddr = get_application_address(response.applicationIndex)
+        suggestedParams = self.client.suggested_params()
+        appCallTxn = transaction.ApplicationCallTxn(
+            sender=self.target.getAddress(),
+            index=response.applicationIndex,
+            on_complete=transaction.OnComplete.NoOpOC,
+            app_args=[b"setvphash", decode_address(verify_hash)],
+            sp=suggestedParams,
+        )
+
+        signedAppCallTxn = appCallTxn.sign(self.target.getPrivateKey())
+        self.client.send_transactions([signedAppCallTxn])
+        response = self.waitForTransaction(self.client, appCallTxn.get_txid())
+        print("set the vp hash to the stateless contract")
+
+        appCallTxn = transaction.PaymentTxn(
+            sender=self.target.getAddress(),
+            receiver=verify_hash,
+            amt=500000,
+            sp=suggestedParams,
+        )
+        signedAppCallTxn = appCallTxn.sign(self.target.getPrivateKey())
+        self.client.send_transactions([signedAppCallTxn])
+        response = self.waitForTransaction(self.client, appCallTxn.get_txid())
+        print("funded the stateless contract")
+        
+s = Setup()
+s.setup()
+s.deploy()

+ 7 - 0
third_party/algorand/setup.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+dn="$(dirname "$0")"
+cd $dn
+
+pipenv install
+pipenv run python3 setup.py