import { AccountLayout, Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token"; import { Connection, Keypair, PublicKey, SystemProgram, Transaction, } from "@solana/web3.js"; import { MsgExecuteContract } from "@terra-money/terra.js"; import { ethers, Overrides } from "ethers"; import { fromUint8Array } from "js-base64"; import { Bridge__factory } from "../ethers-contracts"; import { ixFromRust } from "../solana"; import { importCoreWasm, importTokenWasm } from "../solana/wasm"; import { CHAIN_ID_SOLANA, WSOL_ADDRESS, WSOL_DECIMALS, MAX_VAA_DECIMALS, } from "../utils"; import { hexToNativeString } from "../utils/array"; import { parseTransferPayload } from "../utils/parseVaa"; export async function redeemOnEth( tokenBridgeAddress: string, signer: ethers.Signer, signedVAA: Uint8Array, overrides: Overrides & { from?: string | Promise } = {} ) { const bridge = Bridge__factory.connect(tokenBridgeAddress, signer); const v = await bridge.completeTransfer(signedVAA, overrides); const receipt = await v.wait(); return receipt; } export async function redeemOnEthNative( tokenBridgeAddress: string, signer: ethers.Signer, signedVAA: Uint8Array, overrides: Overrides & { from?: string | Promise } = {} ) { const bridge = Bridge__factory.connect(tokenBridgeAddress, signer); const v = await bridge.completeTransferAndUnwrapETH(signedVAA, overrides); const receipt = await v.wait(); return receipt; } export async function redeemOnTerra( tokenBridgeAddress: string, walletAddress: string, signedVAA: Uint8Array ) { return new MsgExecuteContract(walletAddress, tokenBridgeAddress, { submit_vaa: { data: fromUint8Array(signedVAA), }, }); } export async function redeemAndUnwrapOnSolana( connection: Connection, bridgeAddress: string, tokenBridgeAddress: string, payerAddress: string, signedVAA: Uint8Array ) { const { parse_vaa } = await importCoreWasm(); const { complete_transfer_native_ix } = await importTokenWasm(); const parsedVAA = parse_vaa(signedVAA); const parsedPayload = parseTransferPayload( Buffer.from(new Uint8Array(parsedVAA.payload)) ); const targetAddress = hexToNativeString( parsedPayload.targetAddress, CHAIN_ID_SOLANA ); if (!targetAddress) { throw new Error("Failed to read the target address."); } const targetPublicKey = new PublicKey(targetAddress); const targetAmount = parsedPayload.amount * BigInt(WSOL_DECIMALS - MAX_VAA_DECIMALS) * BigInt(10); const rentBalance = await Token.getMinBalanceRentForExemptAccount(connection); const mintPublicKey = new PublicKey(WSOL_ADDRESS); const payerPublicKey = new PublicKey(payerAddress); const ancillaryKeypair = Keypair.generate(); const completeTransferIx = ixFromRust( complete_transfer_native_ix( tokenBridgeAddress, bridgeAddress, payerAddress, signedVAA ) ); //This will create a temporary account where the wSOL will be moved const createAncillaryAccountIx = SystemProgram.createAccount({ fromPubkey: payerPublicKey, newAccountPubkey: ancillaryKeypair.publicKey, lamports: rentBalance, //spl token accounts need rent exemption space: AccountLayout.span, programId: TOKEN_PROGRAM_ID, }); //Initialize the account as a WSOL account, with the original payerAddress as owner const initAccountIx = await Token.createInitAccountInstruction( TOKEN_PROGRAM_ID, mintPublicKey, ancillaryKeypair.publicKey, payerPublicKey ); //Send in the amount of wSOL which we want converted to SOL const balanceTransferIx = Token.createTransferInstruction( TOKEN_PROGRAM_ID, targetPublicKey, ancillaryKeypair.publicKey, payerPublicKey, [], new u64(targetAmount.toString(16), 16) ); //Close the ancillary account for cleanup. Payer address receives any remaining funds const closeAccountIx = Token.createCloseAccountInstruction( TOKEN_PROGRAM_ID, ancillaryKeypair.publicKey, //account to close payerPublicKey, //Remaining funds destination payerPublicKey, //authority [] ); const { blockhash } = await connection.getRecentBlockhash(); const transaction = new Transaction(); transaction.recentBlockhash = blockhash; transaction.feePayer = new PublicKey(payerAddress); transaction.add(completeTransferIx); transaction.add(createAncillaryAccountIx); transaction.add(initAccountIx); transaction.add(balanceTransferIx); transaction.add(closeAccountIx); transaction.partialSign(ancillaryKeypair); return transaction; } export async function redeemOnSolana( connection: Connection, bridgeAddress: string, tokenBridgeAddress: string, payerAddress: string, signedVAA: Uint8Array ) { const { parse_vaa } = await importCoreWasm(); const parsedVAA = parse_vaa(signedVAA); const isSolanaNative = Buffer.from(new Uint8Array(parsedVAA.payload)).readUInt16BE(65) === CHAIN_ID_SOLANA; const { complete_transfer_wrapped_ix, complete_transfer_native_ix } = await importTokenWasm(); const ixs = []; if (isSolanaNative) { ixs.push( ixFromRust( complete_transfer_native_ix( tokenBridgeAddress, bridgeAddress, payerAddress, signedVAA ) ) ); } else { ixs.push( ixFromRust( complete_transfer_wrapped_ix( tokenBridgeAddress, bridgeAddress, payerAddress, signedVAA ) ) ); } const transaction = new Transaction().add(...ixs); const { blockhash } = await connection.getRecentBlockhash(); transaction.recentBlockhash = blockhash; transaction.feePayer = new PublicKey(payerAddress); return transaction; }