adding algorand

This commit is contained in:
spacemandev 2022-05-19 14:00:33 -05:00
parent 2c86a36dc0
commit c711593e53
9 changed files with 590 additions and 25 deletions

View File

@ -1 +1,2 @@
node_modules/
node_modules/
xdapp.config.json

View File

@ -0,0 +1,318 @@
#!/usr/bin/python3
"""
Copyright 2022 Wormhole Project Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from algosdk import account, mnemonic, abi
from algosdk.encoding import decode_address, encode_address
from algosdk.future import transaction
from algosdk.logic import get_application_address
from algosdk.v2client.algod import AlgodClient
from base64 import b64decode
from pyteal.ast import *
from pyteal.compiler import *
from pyteal.ir import *
from pyteal.types import *
from typing import List, Tuple, Dict, Any, Optional, Union
import pprint
import sys
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 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)
print (" " + self.getMnemonic())
print (" " + self.addr)
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))
def fullyCompileContract(client: AlgodClient, contract: Expr) -> bytes:
teal = compileTeal(contract, mode=Mode.Application, version=6)
response = client.compile(teal)
return response
def clear_app():
return Int(1)
devMode = True
def approve_app():
me = Global.current_application_address()
# This bit of magic causes the line number of the assert to show
# up in the small bit of info shown when an assert trips. This
# tells you the actual line number of the assert that caused the
# txn to fail.
def MagicAssert(a) -> Expr:
if devMode:
from inspect import currentframe
return Assert(And(a, Int(currentframe().f_back.f_lineno)))
else:
return Assert(a)
# potential badness
def assert_common_checks(e) -> Expr:
return MagicAssert(And(
e.rekey_to() == Global.zero_address(),
e.close_remainder_to() == Global.zero_address(),
e.asset_close_to() == Global.zero_address()
))
def sendMessage():
return Seq(
InnerTxnBuilder.Begin(),
InnerTxnBuilder.SetFields(
{
TxnField.type_enum: TxnType.ApplicationCall,
TxnField.application_id: App.globalGet(Bytes("coreid")),
TxnField.application_args: [Bytes("publishMessage"), Txn.application_args[1], Itob(Int(0))],
TxnField.accounts: [Txn.accounts[1]],
TxnField.note: Bytes("publishMessage"),
TxnField.fee: Int(0),
}
),
InnerTxnBuilder.Submit(),
# It is the way...
Approve()
)
@Subroutine(TealType.uint64)
def checkedGet(v) -> Expr:
maybe = App.globalGetEx(Txn.application_id(), v)
# If we assert here, it means we have not registered the emitter
return Seq(maybe, MagicAssert(maybe.hasValue()), maybe.value())
def receiveMessage():
off = ScratchVar()
emitter = ScratchVar()
sequence = ScratchVar()
tidx = ScratchVar()
return Seq([
# First, lets make sure we are looking at the correct vaa version...
MagicAssert(Btoi(Extract(Txn.application_args[1], Int(0), Int(1))) == Int(1)),
# From the vaa, I will grab the emitter and sequence number
off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(14)), # The offset of the chain/emitter
emitter.store(Extract(Txn.application_args[1], off.load(), Int(34))),
sequence.store(Btoi(Extract(Txn.application_args[1], off.load() + Int(34), Int(8)))),
# Should be going up and never repeating.. If you want
# something that can take messages in any order, look at
# checkForDuplicate() in the token_bridge contract. It is
# kind of a heavy lift but it can be done
MagicAssert(sequence.load() > checkedGet(emitter.load())),
App.globalPut(emitter.load(), sequence.load()),
# Now lets check to see if this vaa was actually signed by
# the guardians. We do this by confirming that the
# previous txn in the group was to the wormhole core and
# against the verifyVAA method. If that passed, then the
# vaa must be legit
MagicAssert(Txn.group_index() > Int(0)),
tidx.store(Txn.group_index() - Int(1)),
MagicAssert(And(
# Lets see if the vaa we are about to process was actually verified by the core
Gtxn[tidx.load()].type_enum() == TxnType.ApplicationCall,
Gtxn[tidx.load()].application_id() == App.globalGet(Bytes("coreid")),
Gtxn[tidx.load()].application_args[0] == Bytes("verifyVAA"),
Gtxn[tidx.load()].sender() == Txn.sender(),
# we are all taking about the same vaa?
Gtxn[tidx.load()].application_args[1] == Txn.application_args[1],
# We all opted into the same accounts?
Gtxn[tidx.load()].accounts[0] == Txn.accounts[0],
Gtxn[tidx.load()].accounts[1] == Txn.accounts[1],
Gtxn[tidx.load()].accounts[2] == Txn.accounts[2],
)),
# check for hackery
assert_common_checks(Gtxn[tidx.load()]),
assert_common_checks(Txn),
# ... boiler plate is done...
# What is the offset into the vaa of the actual payload?
off.store(Btoi(Extract(Txn.application_args[1], Int(5), Int(1))) * Int(66) + Int(57)),
# Lets extract it and log it...
MagicAssert(Len(Txn.application_args[1]) > off.load()),
Log(Extract(Txn.application_args[1], off.load(), Len(Txn.application_args[1]) - off.load())),
# It is the way...
Approve()
])
# You could wrap your governance in a vaa from a trusted
# governance emitter. For the purposes of this demo, we are
# skipping that. Again, you could look at the core contract or
# the portal contract in wormhole to see examples of doing
# governance with vaa's.
def registerEmitter():
return Seq([
# The chain comes in as 8 bytes, we will take the last two bytes, append the emitter to it, and set it to zero
App.globalPut(Concat(Txn.application_args[2], Txn.application_args[1]), Int(0)),
# It is the way...
Approve()
])
METHOD = Txn.application_args[0]
router = Cond(
[METHOD == Bytes("registerEmitter"), registerEmitter()],
[METHOD == Bytes("receiveMessage"), receiveMessage()],
[METHOD == Bytes("sendMessage"), sendMessage()],
)
on_create = Seq( [
App.globalPut(Bytes("coreid"), Btoi(Txn.application_args[0])),
Return(Int(1))
])
on_update = Seq( [
Return(Int(0))
] )
on_delete = Seq( [
Return(Int(0))
] )
on_optin = Seq( [
Return(Int(1))
] )
return Cond(
[Txn.application_id() == Int(0), on_create],
[Txn.on_completion() == OnComplete.UpdateApplication, on_update],
[Txn.on_completion() == OnComplete.DeleteApplication, on_delete],
[Txn.on_completion() == OnComplete.OptIn, on_optin],
[Txn.on_completion() == OnComplete.NoOp, router]
)
def get_test_app(client: AlgodClient) -> Tuple[bytes, bytes]:
APPROVAL_PROGRAM = fullyCompileContract(client, approve_app())
CLEAR_STATE_PROGRAM = fullyCompileContract(client, clear_app())
return APPROVAL_PROGRAM, CLEAR_STATE_PROGRAM
def waitForTransaction(
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)
)
if __name__ == "__main__":
#algod_address = "https://testnet-api.algonode.cloud"
#algod_address = "https://mainnet-api.algonode.cloud"
algod_address = sys.argv[3]
algod_token="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
client = AlgodClient(algod_token, algod_address)
approval, clear = get_test_app(client)
globalSchema = transaction.StateSchema(num_uints=2, num_byte_slices=40)
# localSchema is IMPORTANT.. you need 16 byte slices
localSchema = transaction.StateSchema(num_uints=0, num_byte_slices=16)
sender = Account.FromMnemonic(sys.argv[2])
coreid = int(sys.argv[1])
txn = transaction.ApplicationCreateTxn(
sender=sender.getAddress(),
on_complete=transaction.OnComplete.NoOpOC,
approval_program=b64decode(approval["result"]),
clear_program=b64decode(clear["result"]),
global_schema=globalSchema,
local_schema=localSchema,
sp=client.suggested_params(),
app_args = [coreid]
)
signedTxn = txn.sign(sender.getPrivateKey())
print("creating app")
client.send_transaction(signedTxn)
response = waitForTransaction(client, signedTxn.get_txid())
messenger=response.applicationIndex
#print("done.. Handing it some money")
txn = transaction.PaymentTxn(sender = sender.getAddress(), sp = client.suggested_params(), receiver = get_application_address(response.applicationIndex), amt = 100000)
signedTxn = txn.sign(sender.getPrivateKey())
client.send_transaction(signedTxn)
#pprint.pprint({"testapp": str(testapp), "address": get_application_address(testapp), "emitterAddress": decode_address(get_application_address(testapp)).hex()})
print("App ID:", messenger)
print("Address: ", get_application_address(messenger))

View File

@ -0,0 +1,12 @@
{
"name": "algorand",
"version": "1.0.0",
"description": "",
"main": "index.js",
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT"
}

View File

@ -0,0 +1,27 @@
attrs==21.4.0
cffi==1.15.0
colorama==0.4.4
execnet==1.9.0
future-fstrings==1.2.0
iniconfig==1.1.1
msgpack==1.0.3
packaging==21.3
pluggy==1.0.0
py==1.11.0
pycparser==2.21
pycryptodomex==3.12.0
pydantic==1.9.0
PyNaCl==1.5.0
pyparsing==3.0.6
pyteal==v0.10.1
py-algorand-sdk==1.10.0b1
pytest==6.2.5
pytest-depends==1.0.1
pytest-forked==1.4.0
pytest-xdist==2.5.0
PyYAML==6.0
toml==0.10.2
typing-extensions==4.0.1
uvarint==1.2.0
eth_abi==2.1.1
coincurve==16.0.0

View File

@ -19,6 +19,10 @@
"deployedAddress": "",
"emittedVAAs": [
]
},
"algo": {
"type": "algorand",
"rpc": ""
}
},
"wormhole": {

View File

@ -1,10 +1,22 @@
import { exec } from "child_process";
import fs from "fs";
import { ethers } from 'ethers';
import algo from "algosdk";
import {
getEmitterAddressAlgorand,
getEmitterAddressEth,
parseSequenceFromLogEth
getEmitterAddressSolana,
getEmitterAddressTerra,
parseSequenceFromLogEth,
parseSequenceFromLogAlgorand,
uint8ArrayToHex
} from "@certusone/wormhole-sdk";
import {
optin,
submitVAAHeader
} from "@certusone/wormhole-sdk/lib/cjs/algorand/Algorand.js";
import fetch from 'node-fetch';
async function main(){
@ -34,31 +46,86 @@ async function main(){
}
})
);
} else if (network.type == "algorand"){
console.log(`Deploying Algorand network: ${process.argv[2]} to ${network.rpc}`);
exec(
`cd chains/algorand && python3 messenger.py ${network.bridgeAddress} '${network.mnemonic}' ${network.rpc}:${network.port}`,
((err,out,errStr) => {
if(err) {
throw new Error(err);
}
if(out){
console.log(out);
network.appId = parseInt(out.split("App ID:")[1].split("Address")[0].trim());
network.deployedAddress = out.split("Address: ")[1].trim();
network.emittedVAAs = [];
config.networks[process.argv[2]] = network;
fs.writeFileSync('./xdapp.config.json', JSON.stringify(config, null, 4));
}
})
)
} else {
throw new Error("Invalid Network Type!");
}
} else if (process.argv[3] == "register_chain") {
if(!network.deployedAddress){
throw new Error("Deploy to this network first!");
}
const targetNetwork = config.networks[process.argv[4]];
if(!targetNetwork.deployedAddress){
throw new Error("Target Network not deployed yet!");
}
let emitterAddr;
if(targetNetwork.type == "evm"){
emitterAddr = Buffer.from(getEmitterAddressEth(targetNetwork.deployedAddress), "hex");
} else if (targetNetwork.type == "algorand") {
emitterAddr = Buffer.from(getEmitterAddressAlgorand(targetNetwork.appId), "hex");
} else if (targetNetwork.type == "solana") {
emitterAddr = Buffer.from(await getEmitterAddressSolana(targetNetwork.deployedAddress), "hex");
} else if (targetNetwork.type == "terra") {
emitterAddr = Buffer.from(await getEmitterAddressTerra(targetNetwork.deployedAddress), "hex");
}
if(network.type == "evm"){
const signer = new ethers.Wallet(network.privateKey)
.connect(new ethers.providers.JsonRpcProvider(network.rpc));
const targetNetwork = config.networks[process.argv[4]];
if(!targetNetwork.deployedAddress){
throw new Error("Target Network not deployed yet!");
}
if(targetNetwork.type == "evm"){
const emitter_address = Buffer.from(getEmitterAddressEth(targetNetwork.deployedAddress), "hex");
const messenger = new ethers.Contract(
network.deployedAddress,
JSON.parse(fs.readFileSync('./chains/evm/out/Messenger.sol/Messenger.json').toString()).abi,
signer
);
const tx = await messenger.registerApplicationContracts(targetNetwork.wormholeChainId, emitter_address);
console.log(`Network(${process.argv[2]}) Registered EVM style Emitter: `, tx.hash);
}
const messenger = new ethers.Contract(
network.deployedAddress,
JSON.parse(fs.readFileSync('./chains/evm/out/Messenger.sol/Messenger.json').toString()).abi,
signer
);
await messenger.registerApplicationContracts(targetNetwork.wormholeChainId, emitterAddr);
} else if (network.type == "algorand"){
const algodClient = new algo.Algodv2(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
network.rpc,
network.port
);
const sender = algo.mnemonicToSecretKey(network.mnemonic);
const params = await algodClient.getTransactionParams().do();
let txs = [];
txs.push({
tx: algo.makeApplicationCallTxnFromObject({
appArgs: [
Uint8Array.from(Buffer.from("registerEmitter")),
Uint8Array.from(emitterAddr),
algo.bigIntToBytes(BigInt(targetNetwork.wormholeChainId), 2)
],
appIndex: network.appId,
from: sender.addr,
onComplete: algo.OnApplicationComplete.NoOpOC,
suggestedParams: params,
}),
signer: null,
});
await signSendAndConfirmAlgorand(algodClient, txs, sender);
}
console.log(`Network(${process.argv[2]}) Registered Emitter: ${targetNetwork.deployedAddress} from Chain: ${targetNetwork.wormholeChainId}`);
} else if (process.argv[3] == "send_msg") {
if(!network.deployedAddress){
throw new Error("Deploy to this network first!");
@ -92,17 +159,72 @@ async function main(){
config.networks[process.argv[2]] = network;
fs.writeFileSync('./xdapp.config.json', JSON.stringify(config, null, 2));
console.log(`Network(${process.argv[2]}) Emitted VAA: `, vaaBytes.vaaBytes);
} else if (network.type == "algorand"){
const algodClient = new algo.Algodv2(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
network.rpc,
network.port
);
const sender = algo.mnemonicToSecretKey(network.mnemonic);
const params = await algodClient.getTransactionParams().do();
let txs = [];
//Opt In to allow core brige to store data with Algo Contract
const messengerPubkey = uint8ArrayToHex(algo.decodeAddress(network.deployedAddress).publicKey);
const { addr: emitterAddr, txs: emitterOptInTxs } = await optin(
algodClient,
sender.addr,
BigInt(network.bridgeAddress),
BigInt(0),
messengerPubkey
);
txs.push(...emitterOptInTxs);
let accts = [
emitterAddr,
algo.getApplicationAddress(network.bridgeAddress),
];
let appTxn = algo.makeApplicationCallTxnFromObject({
appArgs: [
Uint8Array.from(Buffer.from("sendMessage")),
Uint8Array.from(Buffer.from(process.argv[4]))
],
accounts: accts,
appIndex: network.appId,
foreignApps: [network.bridgeAddress],
from: sender.addr,
onComplete: algo.OnApplicationComplete.NoOpOC,
suggestedParams: params,
});
appTxn.fee *= 2;
txs.push({tx: appTxn, signer: null});
const receipt = await signSendAndConfirmAlgorand(algodClient, txs, sender);
const emitAddr = getEmitterAddressAlgorand(network.appId);
const seq = parseSequenceFromLogAlgorand(receipt);
await new Promise((r) => setTimeout(r, 10000));
const vaaBytes = await (
await fetch(
`${config.wormhole.restAddress}/v1/signed_vaa/${network.wormholeChainId}/${emitAddr}/${seq}`
)
).json();
if(!network.emittedVAAs){
network.emittedVAAs = [vaaBytes.vaaBytes];
} else {
network.emittedVAAs.push(vaaBytes.vaaBytes);
}
config.networks[process.argv[2]] = network;
fs.writeFileSync('./xdapp.config.json', JSON.stringify(config, null, 2));
console.log(`Network(${process.argv[2]}) Emitted VAA: `, vaaBytes.vaaBytes);
}
} else if (process.argv[3] == "submit_vaa") {
if(!network.deployedAddress){
throw new Error("Deploy to this network first!");
}
const targetNetwork = config.networks[process.argv[4]];
const vaaBytes = isNaN(parseInt(process.argv[5])) ?
targetNetwork.emittedVAAs.pop() :
targetNetwork.emittedVAAs[parseInt(process.argv[5])];
if(network.type == "evm"){
const targetNetwork = config.networks[process.argv[4]];
const vaaBytes = isNaN(parseInt(process.argv[5])) ?
targetNetwork.emittedVAAs.pop() :
targetNetwork.emittedVAAs[parseInt(process.argv[5])];
const signer = new ethers.Wallet(network.privateKey)
.connect(new ethers.providers.JsonRpcProvider(network.rpc));
@ -114,6 +236,37 @@ async function main(){
const tx = await messenger.receiveEncodedMsg(Buffer.from(vaaBytes, "base64"));
console.log(`Submitted VAA: ${vaaBytes}\nTX: ${tx.hash}`);
} else if (network.type == "algorand"){
const algodClient = new algo.Algodv2(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
network.rpc,
network.port
);
const sender = algo.mnemonicToSecretKey(network.mnemonic);
const params = await algodClient.getTransactionParams().do();
let txs = [];
let sstate = await submitVAAHeader(algodClient, BigInt(network.bridgeAddress), Uint8Array.from(Buffer.from(vaaBytes, "base64")), sender.addr, BigInt(network.appId))
txs = sstate.txs;
let accts = sstate.accounts;
txs.push({
tx: algo.makeApplicationCallTxnFromObject({
appArgs: [
Uint8Array.from(Buffer.from(("receiveMessage"))),
Uint8Array.from(Buffer.from(vaaBytes, "base64"))
],
accounts: accts,
appIndex: network.appId,
from: sender.addr,
onComplete: algo.OnApplicationComplete.NoOpOC,
suggestedParams: params,
}),
signer: null,
});
ret = await signSendAndConfirmAlgorand(algodClient, txs, sender);
console.log(ret);
}
} else if (process.argv[3] == "get_current_msg") {
if(!network.deployedAddress){
@ -134,4 +287,27 @@ async function main(){
}
}
async function signSendAndConfirmAlgorand(
algodClient,
txs,
wallet
) {
algo.assignGroupID(txs.map((tx) => tx.tx));
const signedTxns = [];
for (const tx of txs) {
if (tx.signer) {
signedTxns.push(await tx.signer.signTxn(tx.tx));
} else {
signedTxns.push(tx.tx.signTxn(wallet.sk));
}
}
await algodClient.sendRawTransaction(signedTxns).do();
const result = await algo.waitForConfirmation(
algodClient,
txs[txs.length - 1].tx.txID(),
1
);
return result;
}
main();

View File

@ -9,14 +9,22 @@
"version": "1.0.0",
"license": "MIT",
"workspaces": [
"chains/evm"
"chains/evm",
"chains/algorand"
],
"dependencies": {
"@certusone/wormhole-sdk": "^0.3.3",
"algosdk": "^1.16.0",
"byteify": "^2.0.10",
"ethers": "^5.6.6",
"node-fetch": "^2.6.7"
}
},
"chains/algorand": {
"version": "1.0.0",
"license": "MIT",
"devDependencies": {}
},
"node_modules/@babel/runtime": {
"version": "7.17.9",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
@ -1014,6 +1022,10 @@
"node": ">= 10"
}
},
"node_modules/algorand": {
"resolved": "chains/algorand",
"link": true
},
"node_modules/algosdk": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/algosdk/-/algosdk-1.16.0.tgz",
@ -1242,6 +1254,11 @@
"node": ">=6.14.2"
}
},
"node_modules/byteify": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/byteify/-/byteify-2.0.10.tgz",
"integrity": "sha512-clrE0NtRB/YwjQcmrUU9qpxRIQ5Jc1HGedv6/LWd08upE0FV0S4YvPBkmKEsTaquqGmhx34LkRBO+lXpTgwYgw=="
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@ -2995,6 +3012,9 @@
"resolved": "https://registry.npmjs.org/algo-msgpack-with-bigint/-/algo-msgpack-with-bigint-2.1.1.tgz",
"integrity": "sha512-F1tGh056XczEaEAqu7s+hlZUDWwOBT70Eq0lfMpBP2YguSQVyxRbprLq5rELXKQOyOaixTWYhMeMQMzP0U5FoQ=="
},
"algorand": {
"version": "file:chains/algorand"
},
"algosdk": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/algosdk/-/algosdk-1.16.0.tgz",
@ -3182,6 +3202,11 @@
"node-gyp-build": "^4.3.0"
}
},
"byteify": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/byteify/-/byteify-2.0.10.tgz",
"integrity": "sha512-clrE0NtRB/YwjQcmrUU9qpxRIQ5Jc1HGedv6/LWd08upE0FV0S4YvPBkmKEsTaquqGmhx34LkRBO+lXpTgwYgw=="
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",

View File

@ -10,11 +10,14 @@
"author": "",
"license": "MIT",
"workspaces": [
"chains/evm"
"chains/evm",
"chains/algorand"
],
"type": "module",
"dependencies": {
"@certusone/wormhole-sdk": "^0.3.3",
"algosdk": "^1.16.0",
"byteify": "^2.0.10",
"ethers": "^5.6.6",
"node-fetch": "^2.6.7"
}

View File

@ -1,4 +1,3 @@
cd ../
node messenger.js eth0 deploy
node messenger.js eth1 deploy
node messenger.js eth0 register_chain eth1