710 lines
27 KiB
TypeScript
710 lines
27 KiB
TypeScript
import fs from 'fs';
|
|
import fetch from 'node-fetch';
|
|
import { promisify } from 'util';
|
|
import * as evm from './evm';
|
|
import * as anchor from '@project-serum/anchor';
|
|
import { Solana as SolanaTypes} from '../chains/solana/target/types/solana';
|
|
import { findProgramAddressSync } from '@project-serum/anchor/dist/cjs/utils/pubkey';
|
|
import {
|
|
attestFromSolana,
|
|
createWrappedOnSolana,
|
|
getEmitterAddressEth,
|
|
getEmitterAddressSolana,
|
|
getForeignAssetSolana,
|
|
parseSequenceFromLogSolana,
|
|
postVaaSolanaWithRetry,
|
|
setDefaultWasm,
|
|
tryNativeToUint8Array,
|
|
importCoreWasm,
|
|
transferFromSolana,
|
|
redeemOnSolana
|
|
} from '@certusone/wormhole-sdk';
|
|
import * as byteify from 'byteify';
|
|
import keccak256 from "keccak256";
|
|
import {
|
|
getAccount,
|
|
getOrCreateAssociatedTokenAccount,
|
|
createMint,
|
|
TOKEN_PROGRAM_ID,
|
|
approve,
|
|
createWrappedNativeAccount,
|
|
closeAccount
|
|
} from '@solana/spl-token'
|
|
import {
|
|
PROGRAM_ID as metaplexProgramID,
|
|
} from '@metaplex-foundation/mpl-token-metadata';
|
|
import { parseUnits } from 'ethers/lib/utils';
|
|
|
|
const exec = promisify(require('child_process').exec);
|
|
const config = JSON.parse(fs.readFileSync('./xdapp.config.json').toString());
|
|
const IDL = JSON.parse(fs.readFileSync('./chains/solana/target/idl/solana.json').toString());
|
|
const CONTRACT_ADDRESS = "BHz6MJGvo8PJaBFqaxyzgJYdY6o8h1rBgsRrUmnHCU9k";
|
|
|
|
export async function deploy(src: string){
|
|
const rpc = config.networks[src].rpc;
|
|
const core = config.networks[src].bridgeAddress;
|
|
const token = config.networks[src].tokenBridgeAddress;
|
|
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString()))));
|
|
const connection = new anchor.web3.Connection(rpc);
|
|
|
|
// Request Airdrop for saved keypair
|
|
console.log("Requesting Funds...");
|
|
await connection.requestAirdrop(srcKey.publicKey, 1e9*1000); //request 1000 SOL
|
|
await new Promise((r) => setTimeout(r, 20000)); //wait for the airdrop to go through
|
|
console.log(`${srcKey.publicKey.toString()} balance: `, await (connection.getBalance(srcKey.publicKey)));
|
|
|
|
//Compile and Deploy Solana Code
|
|
console.log("Compiling and deploying solana code takes a min, hang on...");
|
|
const cmd = `cd chains/solana && solana config set -u ${rpc} -k keypairs/id.json && anchor build && anchor deploy --program-name solana --program-keypair keypairs/solana-keypair.json && exit`
|
|
|
|
const {stdout, stderr} = await exec (cmd);
|
|
console.log(stdout);
|
|
|
|
// Also initalize the contract
|
|
//Initalize the Contract
|
|
const xmint = new anchor.Program<SolanaTypes>(
|
|
IDL,
|
|
CONTRACT_ADDRESS,
|
|
new anchor.AnchorProvider(
|
|
new anchor.web3.Connection(rpc),
|
|
new anchor.Wallet(srcKey),
|
|
{}));
|
|
|
|
const [configAcc, _] = findProgramAddressSync([Buffer.from("config")], xmint.programId);
|
|
|
|
// Deploy the SPL Token for Xmint
|
|
const mintAuthorityPDA = findProgramAddressSync([Buffer.from("mint_authority")], xmint.programId)[0];
|
|
|
|
const mint = await createMint(
|
|
connection,
|
|
srcKey,
|
|
mintAuthorityPDA,
|
|
mintAuthorityPDA,
|
|
9, // We are using 9 to match the CLI decimal default exactly
|
|
);
|
|
|
|
await new Promise((r) => setTimeout(r, 15000)) // wait for the chain to recognize the program
|
|
|
|
const metadataAccount = findProgramAddressSync([
|
|
Buffer.from("metadata"),
|
|
metaplexProgramID.toBuffer(),
|
|
mint.toBuffer()
|
|
], metaplexProgramID)[0]
|
|
|
|
const redeemerAcc = findProgramAddressSync([Buffer.from("redeemer")], xmint.programId)[0];
|
|
|
|
// Initalize will also CPI into metaplex metadata program to setup metadata for the token
|
|
await xmint.methods
|
|
.initialize(
|
|
`${src}-TOKEN`,
|
|
`${src}T`,
|
|
""
|
|
)
|
|
.accounts({
|
|
config: configAcc,
|
|
owner: srcKey.publicKey,
|
|
systemProgram: anchor.web3.SystemProgram.programId,
|
|
mint: mint,
|
|
mintAuthority: mintAuthorityPDA,
|
|
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
|
metadataAccount: metadataAccount,
|
|
metadataProgram: metaplexProgramID,
|
|
redeemer: redeemerAcc
|
|
})
|
|
.rpc();
|
|
|
|
await new Promise((r) => setTimeout(r, 15000)) // wait for the chain to recognize the metadata
|
|
|
|
// Store deploy info
|
|
fs.writeFileSync(`./deployinfo/${src}.deploy.json`, JSON.stringify({
|
|
address: CONTRACT_ADDRESS,
|
|
tokenAddress: mint,
|
|
tokenReceipientAddress: redeemerAcc.toString(),
|
|
vaas: []
|
|
}, null, 4));
|
|
}
|
|
|
|
/**
|
|
* Attest token from src chain and create wrapped on target chain
|
|
* @param src
|
|
* @param target
|
|
* @param address
|
|
*/
|
|
export async function attest(src: string, target:string, address:string = null){
|
|
const srcNetwork = config.networks[src];
|
|
const targetNetwork = config.networks[target];
|
|
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
|
|
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
|
|
)));
|
|
const connection = new anchor.web3.Connection(srcNetwork.rpc);
|
|
|
|
// If we don't pass in an address (like for WSOL) we assume we want to attest the xmint token
|
|
if(!address){
|
|
address = srcDeployInfo.tokenAddress;
|
|
}
|
|
console.log(`Attesting ${address} from ${src} network onto ${target}`);
|
|
|
|
setDefaultWasm("node");
|
|
const tx = await attestFromSolana(
|
|
connection,
|
|
srcNetwork.bridgeAddress,
|
|
srcNetwork.tokenBridgeAddress,
|
|
srcKey.publicKey.toString(),
|
|
address
|
|
);
|
|
tx.partialSign(srcKey);
|
|
const txid = await connection.sendRawTransaction(tx.serialize());
|
|
console.log("TXID: ", txid);
|
|
|
|
const attestVaa = await fetchVaa(src, txid, true);
|
|
switch(targetNetwork.type){
|
|
case "evm":
|
|
await evm.createWrapped(target, src, attestVaa);
|
|
break;
|
|
case "solana":
|
|
await createWrapped(target, src, attestVaa);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches the signed VAA from the Gaurdian. Will use xmint as emitter if portal=false
|
|
* @param src
|
|
* @param tx
|
|
* @param portal
|
|
*/
|
|
async function fetchVaa(src:string, tx:string, portal:boolean=false):Promise<string>{
|
|
const srcNetwork = config.networks[src];
|
|
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
|
|
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
|
|
)));
|
|
const connection = new anchor.web3.Connection(srcNetwork.rpc);
|
|
|
|
setDefaultWasm("node");
|
|
const emitterAddr = portal ? await getEmitterAddressSolana(srcNetwork.tokenBridgeAddress) : await getEmitterAddressSolana(srcDeployInfo.address);
|
|
let transaction = await connection.getTransaction(tx);
|
|
let timeElapsed = 0;
|
|
while (!transaction) {
|
|
await new Promise((r) => setTimeout(r, 1000)) // wait for the chain to recognize the program
|
|
transaction = await connection.getTransaction(tx);
|
|
timeElapsed += 1;
|
|
}
|
|
console.log(`VAA from TX(${tx}) found in ${timeElapsed}s`);
|
|
const seq = parseSequenceFromLogSolana(transaction);
|
|
await new Promise((r) => setTimeout(r, 5000)); //wait for gaurdian to pick up messsage
|
|
console.log(
|
|
"Searching for: ",
|
|
`${config.wormhole.restAddress}/v1/signed_vaa/${srcNetwork.wormholeChainId}/${emitterAddr}/${seq}`
|
|
);
|
|
const vaaBytes = await (
|
|
await fetch(
|
|
`${config.wormhole.restAddress}/v1/signed_vaa/${srcNetwork.wormholeChainId}/${emitterAddr}/${seq}`
|
|
)
|
|
).json();
|
|
|
|
if(!vaaBytes['vaaBytes']){
|
|
throw new Error("VAA not found!");
|
|
}
|
|
|
|
console.log("VAA Found: ", vaaBytes.vaaBytes);
|
|
return vaaBytes.vaaBytes;
|
|
}
|
|
|
|
|
|
export async function createWrapped(src:string, target:string, vaa:string){
|
|
const srcNetwork = config.networks[src];
|
|
const targetNetwork = config.networks[target];
|
|
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
|
|
const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
|
|
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
|
|
)));
|
|
const connection = new anchor.web3.Connection(srcNetwork.rpc);
|
|
|
|
setDefaultWasm("node");
|
|
//Have to Post the VAA first before we can use it
|
|
await postVaaSolanaWithRetry(
|
|
connection,
|
|
async (transaction) => {
|
|
transaction.partialSign(srcKey);
|
|
return transaction;
|
|
},
|
|
srcNetwork.bridgeAddress,
|
|
srcKey.publicKey.toString(),
|
|
Buffer.from(vaa, "base64"),
|
|
10
|
|
);
|
|
|
|
const tx = await createWrappedOnSolana(
|
|
connection,
|
|
srcNetwork.bridgeAddress,
|
|
srcNetwork.tokenBridgeAddress,
|
|
srcKey.publicKey.toString(),
|
|
Buffer.from(vaa, "base64")
|
|
);
|
|
tx.partialSign(srcKey);
|
|
const txid = await connection.sendRawTransaction(tx.serialize());
|
|
|
|
await new Promise((r) => setTimeout(r, 25000)); // wait for blocks to advance before fetching new foreign address
|
|
const foreignAddress = await getForeignAssetSolana(
|
|
connection,
|
|
srcNetwork.tokenBridgeAddress,
|
|
targetNetwork.wormholeChainId,
|
|
tryNativeToUint8Array(targetDeployInfo.tokenAddress, targetNetwork.wormholeChainId)
|
|
);
|
|
console.log(`${src} Network has new PortalWrappedToken for ${target} network at ${foreignAddress}`);
|
|
|
|
//If the attestation is WETH, save the ATA for config of the WETH mint as recipient address
|
|
}
|
|
|
|
export async function registerApp(src:string, target:string){
|
|
const srcNetwork = config.networks[src];
|
|
const targetNetwork = config.networks[target];
|
|
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
|
|
const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
|
|
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
|
|
)));
|
|
const connection = new anchor.web3.Connection(srcNetwork.rpc);
|
|
let targetEmitter:String;
|
|
switch (targetNetwork.type){
|
|
case 'evm':
|
|
targetEmitter = getEmitterAddressEth(targetDeployInfo.address);
|
|
break;
|
|
case 'solana':
|
|
targetEmitter = await getEmitterAddressSolana(targetDeployInfo.address);
|
|
break;
|
|
}
|
|
|
|
const xmint = new anchor.Program<SolanaTypes>(
|
|
IDL,
|
|
CONTRACT_ADDRESS,
|
|
new anchor.AnchorProvider(
|
|
connection,
|
|
new anchor.Wallet(srcKey),
|
|
{}));
|
|
|
|
const [emitterAcc, emitterBmp] = findProgramAddressSync([
|
|
Buffer.from("EmitterAddress"),
|
|
byteify.serializeUint16(targetNetwork.wormholeChainId)
|
|
], xmint.programId);
|
|
|
|
const [configAcc, _] = findProgramAddressSync([
|
|
Buffer.from("config")
|
|
], xmint.programId);
|
|
|
|
const tx = await xmint.methods
|
|
.registerChain(
|
|
targetNetwork.wormholeChainId,
|
|
Buffer.from(targetEmitter, 'hex'),
|
|
)
|
|
.accounts({
|
|
config: configAcc,
|
|
emitterAcc: emitterAcc,
|
|
owner: xmint.provider.publicKey,
|
|
systemProgram: anchor.web3.SystemProgram.programId
|
|
})
|
|
.rpc();
|
|
console.log(`Registered ${target} contract on ${src}`);
|
|
// Alongside registering the App, go ahead register the tokens with one another
|
|
// Register target token with src chain
|
|
console.log(`Attesting ${target} token on ${src}`);
|
|
switch(targetNetwork.type){
|
|
case "evm":
|
|
await evm.attest(target, src);
|
|
break;
|
|
case "solana":
|
|
await attest(target, src);
|
|
break;
|
|
}
|
|
console.log(`Attested ${target} token on ${src}`);
|
|
}
|
|
|
|
export async function balance(src: string, target: string) : Promise<string>{
|
|
const srcNetwork = config.networks[src];
|
|
const targetNetwork = config.networks[target];
|
|
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
|
|
const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
|
|
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
|
|
)));
|
|
const connection = new anchor.web3.Connection(srcNetwork.rpc);
|
|
|
|
if (src == target) {
|
|
//Get Native Currency
|
|
return (await connection.getBalance(srcKey.publicKey)).toString()
|
|
}
|
|
|
|
setDefaultWasm("node")
|
|
// Else get the Token Balance of the Foreign Network's token on the Src Network
|
|
const foreignAddress = await getForeignAssetSolana(
|
|
connection,
|
|
srcNetwork.tokenBridgeAddress,
|
|
targetNetwork.wormholeChainId,
|
|
tryNativeToUint8Array(targetDeployInfo.tokenAddress, targetNetwork.wormholeChainId)
|
|
);
|
|
|
|
const tokenAccountAddress = await getOrCreateAssociatedTokenAccount(
|
|
connection,
|
|
srcKey,
|
|
new anchor.web3.PublicKey(foreignAddress),
|
|
srcKey.publicKey,
|
|
);
|
|
|
|
const tokenAccount = await getAccount(
|
|
connection,
|
|
tokenAccountAddress.address
|
|
)
|
|
|
|
return tokenAccount.amount.toString();
|
|
}
|
|
|
|
export async function buyToken(src:string, target: string, amount:number){
|
|
// Buy token on target chain with SOL on SRC chain
|
|
// Create p3 that pays SOL
|
|
|
|
const srcNetwork = config.networks[src];
|
|
const targetNetwork = config.networks[target];
|
|
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
|
|
const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
|
|
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
|
|
)));
|
|
const connection = new anchor.web3.Connection(srcNetwork.rpc);
|
|
|
|
// For this project, 1 Native Token (1 SOL) will always equal 100 Chain tokens, no matter the source or target chains
|
|
const solToTransfer = parseUnits((amount/100).toString(), "9");
|
|
const targetChainAddress = tryNativeToUint8Array(targetDeployInfo.tokenReceipientAddress, targetNetwork.wormholeChainId);
|
|
setDefaultWasm("node");
|
|
|
|
// Recieve tokens into the ATA for payer for wrapped ETH0 Tokens
|
|
// Requires getting wrapped mint data
|
|
const wrappedTokenAddress = await getForeignAssetSolana(
|
|
connection,
|
|
srcNetwork.tokenBridgeAddress,
|
|
targetNetwork.wormholeChainId,
|
|
tryNativeToUint8Array(targetDeployInfo.tokenAddress, targetNetwork.wormholeChainId)
|
|
);
|
|
|
|
const targetTokenATA = await getOrCreateAssociatedTokenAccount(
|
|
connection,
|
|
srcKey,
|
|
new anchor.web3.PublicKey(wrappedTokenAddress),
|
|
srcKey.publicKey
|
|
);
|
|
|
|
const purchaseOrderPayload = tryNativeToUint8Array(targetTokenATA.address.toString(), srcNetwork.wormholeChainId);
|
|
|
|
// Tokenbridge Authority Signer holds delegate authority to mutate the wSOL ATA account
|
|
const tokenBridgePubKey = new anchor.web3.PublicKey(srcNetwork.tokenBridgeAddress);
|
|
const tokenBridgeAuthoritySigner = findProgramAddressSync([Buffer.from("authority_signer")], tokenBridgePubKey)[0];
|
|
|
|
// wSOL ATA Account
|
|
const wSOLATAAcc = await createWrappedNativeAccount(
|
|
connection,
|
|
srcKey,
|
|
srcKey.publicKey,
|
|
solToTransfer.toNumber(),
|
|
new anchor.web3.Keypair(),
|
|
)
|
|
|
|
//wSOL Approve
|
|
await approve(
|
|
connection,
|
|
srcKey,
|
|
wSOLATAAcc,
|
|
tokenBridgeAuthoritySigner,
|
|
srcKey,
|
|
solToTransfer.toBigInt()
|
|
);
|
|
|
|
console.log("Approved wSOL Transfer");
|
|
await new Promise((r) => setTimeout(r, 16000)); //wait for accounts to finialize
|
|
//p3 Generation
|
|
const tx = await transferFromSolana(
|
|
connection,
|
|
srcNetwork.bridgeAddress,
|
|
srcNetwork.tokenBridgeAddress,
|
|
srcKey.publicKey.toString(),
|
|
wSOLATAAcc.toString(),
|
|
srcNetwork.wrappedNativeAddress,
|
|
solToTransfer.toBigInt(),
|
|
targetChainAddress,
|
|
targetNetwork.wormholeChainId,
|
|
tryNativeToUint8Array(wSOLATAAcc.toString(), srcNetwork.wormholeChainId),
|
|
srcNetwork.wormholeChainId,
|
|
srcKey.publicKey.toString(),
|
|
BigInt(0),
|
|
purchaseOrderPayload
|
|
);
|
|
|
|
tx.partialSign(srcKey);
|
|
const txid = await connection.sendRawTransaction(tx.serialize());
|
|
console.log("SOL transferred: ", txid);
|
|
const vaa = await fetchVaa(src, txid, true);
|
|
|
|
/*
|
|
await closeAccount(
|
|
connection,
|
|
srcKey,
|
|
wSOLATAAcc,
|
|
srcKey.publicKey,
|
|
srcKey.publicKey
|
|
);
|
|
*/
|
|
|
|
return vaa;
|
|
}
|
|
|
|
export async function claimTokens(src:string, vaa:string){
|
|
const srcNetwork = config.networks[src];
|
|
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
|
|
)));
|
|
const connection = new anchor.web3.Connection(srcNetwork.rpc);
|
|
|
|
//Post VAA before trying to claim it
|
|
setDefaultWasm("node");
|
|
await postVaaSolanaWithRetry(
|
|
connection,
|
|
async (tx) => {
|
|
tx.partialSign(srcKey);
|
|
return tx;
|
|
},
|
|
srcNetwork.bridgeAddress,
|
|
srcKey.publicKey.toString(),
|
|
Buffer.from(vaa, "base64"),
|
|
10
|
|
);
|
|
|
|
await new Promise((r) => setTimeout(r, 16000)); //wait for accounts to finialize
|
|
|
|
const tx = await redeemOnSolana(
|
|
connection,
|
|
srcNetwork.bridgeAddress,
|
|
srcNetwork.tokenBridgeAddress,
|
|
srcKey.publicKey.toString(),
|
|
Buffer.from(vaa, 'base64'),
|
|
);
|
|
tx.partialSign(srcKey);
|
|
const txid = await connection.sendRawTransaction(tx.serialize());
|
|
console.log("Claimed tokens on Solana: ", txid);
|
|
}
|
|
|
|
export async function submitForeignPurchase(src:string, target:string, vaa:string){
|
|
const srcNetwork = config.networks[src];
|
|
const targetNetwork = config.networks[target];
|
|
const srcDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${src}.deploy.json`).toString());
|
|
const targetDeployInfo = JSON.parse(fs.readFileSync(`./deployinfo/${target}.deploy.json`).toString());
|
|
const srcKey = anchor.web3.Keypair.fromSecretKey(Uint8Array.from(JSON.parse((fs.readFileSync(`keypairs/${src}.key`).toString())
|
|
)));
|
|
const connection = new anchor.web3.Connection(srcNetwork.rpc);
|
|
|
|
setDefaultWasm("node");
|
|
// Post the VAA to Solana Chain for signature verification
|
|
await postVaaSolanaWithRetry(
|
|
connection,
|
|
async (tx) => {
|
|
tx.partialSign(srcKey);
|
|
return tx;
|
|
},
|
|
srcNetwork.bridgeAddress,
|
|
srcKey.publicKey.toString(),
|
|
Buffer.from(vaa, "base64"),
|
|
10
|
|
);
|
|
|
|
const xmint = new anchor.Program<SolanaTypes>(
|
|
IDL,
|
|
CONTRACT_ADDRESS,
|
|
new anchor.AnchorProvider(
|
|
connection,
|
|
new anchor.Wallet(srcKey),
|
|
{}));
|
|
const { parse_vaa } = await importCoreWasm(); //this function can only be imported from the WASM right now, not directly
|
|
const parsed_vaa = parse_vaa(Buffer.from(vaa, 'base64'));
|
|
const vaaHash = getVaaHash(parsed_vaa); //await getSignedVAAHash(Buffer.from(vaa, "base64"));
|
|
|
|
// Account that we stored the registered foreign emitter
|
|
let emitterAddressAcc = findProgramAddressSync([
|
|
byteify.serializeUint16(parsed_vaa.emitter_chain),
|
|
Buffer.from(parsed_vaa.emitter_address, 'hex')
|
|
], new anchor.web3.PublicKey(srcNetwork.tokenBridgeAddress))[0];
|
|
|
|
// A blank account we're creating just to keep track of already processed messages
|
|
let targetEmitterAddress;
|
|
switch (targetNetwork.type) {
|
|
case "evm":
|
|
targetEmitterAddress = getEmitterAddressEth(targetNetwork.tokenBridgeAddress);
|
|
break;
|
|
case "solana":
|
|
targetEmitterAddress = await getEmitterAddressSolana(targetNetwork.tokenBridgeAddress);
|
|
}
|
|
let processedVaaKey = findProgramAddressSync([
|
|
Buffer.from(targetEmitterAddress, "hex"),
|
|
byteify.serializeUint16(parsed_vaa.emitter_chain),
|
|
byteify.serializeUint64(parsed_vaa.sequence)
|
|
], xmint.programId)[0];
|
|
|
|
// Account where the core bridge stored the vaa after the signatures checked out
|
|
const tokenBridgePubKey = new anchor.web3.PublicKey(srcNetwork.tokenBridgeAddress);
|
|
const coreBridgePubKey = new anchor.web3.PublicKey(srcNetwork.bridgeAddress);
|
|
let coreBridgeVaaKey = findProgramAddressSync([
|
|
Buffer.from("PostedVAA"),
|
|
Buffer.from(vaaHash, 'hex')
|
|
], coreBridgePubKey)[0]
|
|
const [configAcc, _] = findProgramAddressSync([Buffer.from("config")], xmint.programId);
|
|
|
|
// Accounts for Completing P3
|
|
const tokenBridgeConfigAcc = findProgramAddressSync([Buffer.from("config")], tokenBridgePubKey)[0];
|
|
const tokenBridgeClaimAcc = findProgramAddressSync([
|
|
Buffer.from(targetEmitterAddress, "hex"),
|
|
byteify.serializeUint16(parsed_vaa.emitter_chain),
|
|
byteify.serializeUint64(parsed_vaa.sequence)
|
|
], tokenBridgePubKey)[0];
|
|
|
|
|
|
// WETH is being transferred in, so we need the WETH Portal Mint on Solana
|
|
const wethMint = new anchor.web3.PublicKey(await getForeignAssetSolana(
|
|
connection,
|
|
srcNetwork.tokenBridgeAddress,
|
|
targetNetwork.wormholeChainId,
|
|
tryNativeToUint8Array(targetNetwork.wrappedNativeAddress, targetNetwork.wormholeChainId)
|
|
));
|
|
|
|
const wethWrappedMeta = findProgramAddressSync([
|
|
Buffer.from("meta"),
|
|
wethMint.toBuffer()
|
|
], tokenBridgePubKey)[0];
|
|
|
|
const mintAuthorityWrapped = findProgramAddressSync([
|
|
Buffer.from("mint_signer")
|
|
], tokenBridgePubKey)[0];
|
|
|
|
const redeemerAcc = findProgramAddressSync([Buffer.from("redeemer")], xmint.programId)[0];
|
|
const wethAtaAcc = await getOrCreateAssociatedTokenAccount(
|
|
connection,
|
|
srcKey,
|
|
wethMint,
|
|
redeemerAcc,
|
|
true // Allow off curve because the owner of the ATA acc is a PDA
|
|
);
|
|
|
|
// MINT SOL#T Tokens to Contract PDA Accounts
|
|
const xmintTokenMint = new anchor.web3.PublicKey(srcDeployInfo.tokenAddress);
|
|
const xmintAuthority = findProgramAddressSync([Buffer.from("mint_authority")], xmint.programId)[0];
|
|
const xmintAtaAccount = await getOrCreateAssociatedTokenAccount(
|
|
connection,
|
|
srcKey,
|
|
xmintTokenMint,
|
|
xmintAuthority,
|
|
true // Allow off curve because the owner of the ATA acc is a PDA
|
|
);
|
|
|
|
// P1 Portal Transfer back to Recepient Accounts
|
|
const tokenBridgeMintCustody = findProgramAddressSync([xmintTokenMint.toBuffer()], tokenBridgePubKey)[0];
|
|
const tokenBridgeAuthoritySigner = findProgramAddressSync([Buffer.from("authority_signer")], tokenBridgePubKey)[0];
|
|
const tokenBridgeCustodySigner = findProgramAddressSync([Buffer.from("custody_signer")], tokenBridgePubKey)[0];
|
|
const coreBridgeConfig = findProgramAddressSync([Buffer.from("Bridge")], coreBridgePubKey)[0];
|
|
const transferMsgKey = new anchor.web3.Keypair();
|
|
const tokenBridgeEmitter = findProgramAddressSync([Buffer.from("emitter")], tokenBridgePubKey)[0];
|
|
const tokenBridgeSequenceKey = findProgramAddressSync([
|
|
Buffer.from("Sequence"),
|
|
tokenBridgeEmitter.toBuffer()
|
|
], coreBridgePubKey)[0];
|
|
const coreBridgeFeeCollector = findProgramAddressSync([Buffer.from("fee_collector")], coreBridgePubKey)[0];
|
|
|
|
// need to split the following into two because Solana account limit
|
|
const receiptAcc = findProgramAddressSync([ Buffer.from(vaaHash, "hex") ], xmint.programId)[0];
|
|
|
|
// Submit Foreign Purchase
|
|
const submitTx = await xmint.methods
|
|
.submitForeignPurchase()
|
|
.accounts({
|
|
payer: srcKey.publicKey,
|
|
systemProgram: anchor.web3.SystemProgram.programId,
|
|
config: configAcc,
|
|
coreBridgeVaa: coreBridgeVaaKey,
|
|
processedVaa: processedVaaKey,
|
|
emitterAcc: emitterAddressAcc,
|
|
receipt: receiptAcc,
|
|
|
|
tokenBridgeConfig: tokenBridgeConfigAcc,
|
|
tokenBridgeClaimKey: tokenBridgeClaimAcc,
|
|
wethAtaAccount: wethAtaAcc.address,
|
|
redeemer:redeemerAcc,
|
|
feeRecipient: wethAtaAcc.address,
|
|
wethMint: wethMint,
|
|
wethMintWrappedMeta: wethWrappedMeta,
|
|
mintAuthorityWrapped: mintAuthorityWrapped,
|
|
rentAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
|
|
coreBridge: coreBridgePubKey,
|
|
splProgram: TOKEN_PROGRAM_ID,
|
|
|
|
tokenBridge: tokenBridgePubKey
|
|
})
|
|
.preInstructions([
|
|
anchor.web3.ComputeBudgetProgram.requestUnits({
|
|
units: 300000,
|
|
additionalFee: 0,
|
|
})
|
|
])
|
|
.rpc();
|
|
|
|
await new Promise((r) => setTimeout(r, 16000)); //wait for accounts to finialize
|
|
|
|
// Claim Foreign Purchase
|
|
const claimTx = await xmint.methods
|
|
.claimForeignPurchase()
|
|
.accounts({
|
|
payer: srcKey.publicKey,
|
|
systemProgram: anchor.web3.SystemProgram.programId,
|
|
config: configAcc,
|
|
receipt: receiptAcc,
|
|
|
|
rentAccount: anchor.web3.SYSVAR_RENT_PUBKEY,
|
|
tokenBridgeConfig: tokenBridgeConfigAcc,
|
|
splProgram: TOKEN_PROGRAM_ID,
|
|
|
|
xmintTokenMint: xmintTokenMint,
|
|
xmintAuthority: xmintAuthority,
|
|
xmintAtaAccount: xmintAtaAccount.address,
|
|
|
|
tokenBridgeMintCustody: tokenBridgeMintCustody,
|
|
tokenBridgeAuthoritySigner: tokenBridgeAuthoritySigner,
|
|
tokenBridgeCustodySigner: tokenBridgeCustodySigner,
|
|
coreBridgeConfig: coreBridgeConfig,
|
|
xmintTransferMsgKey: transferMsgKey.publicKey,
|
|
tokenBridgeEmitter: tokenBridgeEmitter,
|
|
tokenBridgeSequenceKey: tokenBridgeSequenceKey,
|
|
coreBridgeFeeCollector: coreBridgeFeeCollector,
|
|
clock: anchor.web3.SYSVAR_CLOCK_PUBKEY,
|
|
coreBridge: coreBridgePubKey,
|
|
|
|
tokenBridge: tokenBridgePubKey
|
|
})
|
|
.preInstructions([
|
|
anchor.web3.ComputeBudgetProgram.requestUnits({
|
|
units: 300000,
|
|
additionalFee: 0,
|
|
})
|
|
])
|
|
.signers([transferMsgKey])
|
|
.rpc();
|
|
|
|
const sfpVaa = await fetchVaa(src, claimTx, true);
|
|
console.log(sfpVaa);
|
|
return sfpVaa;
|
|
}
|
|
|
|
|
|
export async function sellToken(src:string, target:string, amount:number){}
|
|
export async function submitForeignSale(src:string, target:string, vaa:string){}
|
|
|
|
function getVaaHash(parsed_vaa){
|
|
//Create VAA Hash to use in core bridge key
|
|
let buffer_array = []
|
|
buffer_array.push(byteify.serializeUint32(parsed_vaa.timestamp));
|
|
buffer_array.push(byteify.serializeUint32(parsed_vaa.nonce));
|
|
buffer_array.push(byteify.serializeUint16(parsed_vaa.emitter_chain));
|
|
buffer_array.push(Uint8Array.from(parsed_vaa.emitter_address));
|
|
buffer_array.push(byteify.serializeUint64(parsed_vaa.sequence));
|
|
buffer_array.push(byteify.serializeUint8(parsed_vaa.consistency_level));
|
|
buffer_array.push(Uint8Array.from(parsed_vaa.payload));
|
|
const hash = keccak256(Buffer.concat(buffer_array));
|
|
return hash.toString("hex");
|
|
} |