adding algorand
This commit is contained in:
parent
2c86a36dc0
commit
c711593e53
|
@ -1 +1,2 @@
|
|||
node_modules/
|
||||
xdapp.config.json
|
||||
|
|
|
@ -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))
|
|
@ -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"
|
||||
}
|
|
@ -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
|
|
@ -19,6 +19,10 @@
|
|||
"deployedAddress": "",
|
||||
"emittedVAAs": [
|
||||
]
|
||||
},
|
||||
"algo": {
|
||||
"type": "algorand",
|
||||
"rpc": ""
|
||||
}
|
||||
},
|
||||
"wormhole": {
|
|
@ -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!");
|
||||
}
|
||||
|
||||
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!");
|
||||
}
|
||||
|
||||
let emitterAddr;
|
||||
if(targetNetwork.type == "evm"){
|
||||
const emitter_address = Buffer.from(getEmitterAddressEth(targetNetwork.deployedAddress), "hex");
|
||||
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 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);
|
||||
}
|
||||
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,18 +159,73 @@ 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!");
|
||||
}
|
||||
|
||||
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])];
|
||||
|
||||
if(network.type == "evm"){
|
||||
|
||||
const signer = new ethers.Wallet(network.privateKey)
|
||||
.connect(new ethers.providers.JsonRpcProvider(network.rpc));
|
||||
const messenger = new ethers.Contract(
|
||||
|
@ -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();
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
cd ../
|
||||
node messenger.js eth0 deploy
|
||||
node messenger.js eth1 deploy
|
||||
node messenger.js eth0 register_chain eth1
|
||||
|
|
Loading…
Reference in New Issue