2021-09-22 12:07:21 -07:00
|
|
|
import { AccountLayout, Token, TOKEN_PROGRAM_ID, u64 } from "@solana/spl-token";
|
|
|
|
import {
|
|
|
|
Connection,
|
|
|
|
Keypair,
|
|
|
|
PublicKey,
|
|
|
|
SystemProgram,
|
|
|
|
Transaction,
|
|
|
|
} from "@solana/web3.js";
|
2021-09-09 21:22:11 -07:00
|
|
|
import { MsgExecuteContract } from "@terra-money/terra.js";
|
2022-04-29 12:57:41 -07:00
|
|
|
import { Algodv2 } from "algosdk";
|
2022-03-02 12:11:00 -08:00
|
|
|
import { ethers, Overrides } from "ethers";
|
2021-09-09 21:22:11 -07:00
|
|
|
import { fromUint8Array } from "js-base64";
|
2022-04-29 12:57:41 -07:00
|
|
|
import { TransactionSignerPair, _submitVAAAlgorand } from "../algorand";
|
2021-08-17 18:19:15 -07:00
|
|
|
import { Bridge__factory } from "../ethers-contracts";
|
|
|
|
import { ixFromRust } from "../solana";
|
2021-11-05 12:18:12 -07:00
|
|
|
import { importCoreWasm, importTokenWasm } from "../solana/wasm";
|
2021-09-22 12:07:21 -07:00
|
|
|
import {
|
2022-08-04 08:53:08 -07:00
|
|
|
CHAIN_ID_NEAR,
|
2021-09-22 12:07:21 -07:00
|
|
|
CHAIN_ID_SOLANA,
|
2022-08-04 08:53:08 -07:00
|
|
|
ChainId,
|
|
|
|
MAX_VAA_DECIMALS,
|
2021-09-22 12:07:21 -07:00
|
|
|
WSOL_ADDRESS,
|
|
|
|
WSOL_DECIMALS,
|
2022-06-29 10:09:29 -07:00
|
|
|
uint8ArrayToHex,
|
2022-09-28 06:53:15 -07:00
|
|
|
callFunctionNear,
|
2022-10-24 15:12:02 -07:00
|
|
|
hashLookup
|
2021-09-22 12:07:21 -07:00
|
|
|
} from "../utils";
|
2022-08-04 08:53:08 -07:00
|
|
|
|
2022-06-29 10:09:29 -07:00
|
|
|
import { getForeignAssetNear } from ".";
|
2022-08-04 08:53:08 -07:00
|
|
|
|
2022-06-29 10:09:29 -07:00
|
|
|
import { _parseVAAAlgorand } from "../algorand";
|
2022-08-04 08:53:08 -07:00
|
|
|
|
2021-09-22 12:07:21 -07:00
|
|
|
import { hexToNativeString } from "../utils/array";
|
|
|
|
import { parseTransferPayload } from "../utils/parseVaa";
|
2022-08-04 08:53:08 -07:00
|
|
|
import BN from "bn.js";
|
2022-06-29 10:09:29 -07:00
|
|
|
import { MsgExecuteContract as MsgExecuteContractInjective } from "@injectivelabs/sdk-ts";
|
2022-09-28 06:53:15 -07:00
|
|
|
import { FunctionCallOptions } from "near-api-js/lib/account";
|
|
|
|
import { Provider } from "near-api-js/lib/providers";
|
2022-10-12 08:30:32 -07:00
|
|
|
import { MsgExecuteContract as XplaMsgExecuteContract } from "@xpla/xpla.js";
|
2022-10-24 15:12:02 -07:00
|
|
|
import { AptosClient, Types } from "aptos";
|
|
|
|
import { completeTransfer as completeTransferAptos, completeTransferAndRegister } from "../aptos";
|
2021-08-17 18:19:15 -07:00
|
|
|
|
|
|
|
export async function redeemOnEth(
|
|
|
|
tokenBridgeAddress: string,
|
|
|
|
signer: ethers.Signer,
|
2022-03-02 12:11:00 -08:00
|
|
|
signedVAA: Uint8Array,
|
|
|
|
overrides: Overrides & { from?: string | Promise<string> } = {}
|
2021-08-17 18:19:15 -07:00
|
|
|
) {
|
|
|
|
const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
|
2022-03-02 12:11:00 -08:00
|
|
|
const v = await bridge.completeTransfer(signedVAA, overrides);
|
2021-08-17 18:19:15 -07:00
|
|
|
const receipt = await v.wait();
|
|
|
|
return receipt;
|
|
|
|
}
|
|
|
|
|
2021-09-18 17:41:17 -07:00
|
|
|
export async function redeemOnEthNative(
|
|
|
|
tokenBridgeAddress: string,
|
|
|
|
signer: ethers.Signer,
|
2022-03-02 12:11:00 -08:00
|
|
|
signedVAA: Uint8Array,
|
|
|
|
overrides: Overrides & { from?: string | Promise<string> } = {}
|
2021-09-18 17:41:17 -07:00
|
|
|
) {
|
|
|
|
const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
|
2022-03-02 12:11:00 -08:00
|
|
|
const v = await bridge.completeTransferAndUnwrapETH(signedVAA, overrides);
|
2021-09-18 17:41:17 -07:00
|
|
|
const receipt = await v.wait();
|
|
|
|
return receipt;
|
|
|
|
}
|
|
|
|
|
2021-08-30 03:32:31 -07:00
|
|
|
export async function redeemOnTerra(
|
|
|
|
tokenBridgeAddress: string,
|
|
|
|
walletAddress: string,
|
|
|
|
signedVAA: Uint8Array
|
|
|
|
) {
|
2021-10-22 09:33:05 -07:00
|
|
|
return new MsgExecuteContract(walletAddress, tokenBridgeAddress, {
|
|
|
|
submit_vaa: {
|
|
|
|
data: fromUint8Array(signedVAA),
|
2021-08-30 03:32:31 -07:00
|
|
|
},
|
2021-10-22 09:33:05 -07:00
|
|
|
});
|
2021-08-30 03:32:31 -07:00
|
|
|
}
|
|
|
|
|
2022-06-29 10:09:29 -07:00
|
|
|
/**
|
|
|
|
* Submits the supplied VAA to Injective
|
|
|
|
* @param tokenBridgeAddress Address of Inj token bridge contract
|
|
|
|
* @param walletAddress Address of wallet in inj format
|
|
|
|
* @param signedVAA VAA with the attestation message
|
|
|
|
* @returns Message to be broadcast
|
|
|
|
*/
|
|
|
|
export async function submitVAAOnInjective(
|
|
|
|
tokenBridgeAddress: string,
|
|
|
|
walletAddress: string,
|
|
|
|
signedVAA: Uint8Array
|
|
|
|
): Promise<MsgExecuteContractInjective> {
|
|
|
|
return MsgExecuteContractInjective.fromJSON({
|
|
|
|
contractAddress: tokenBridgeAddress,
|
|
|
|
sender: walletAddress,
|
|
|
|
msg: {
|
|
|
|
data: fromUint8Array(signedVAA),
|
|
|
|
},
|
|
|
|
action: "submit_vaa",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
export const redeemOnInjective = submitVAAOnInjective;
|
|
|
|
|
2022-10-12 08:30:32 -07:00
|
|
|
export function redeemOnXpla(
|
|
|
|
tokenBridgeAddress: string,
|
|
|
|
walletAddress: string,
|
|
|
|
signedVAA: Uint8Array
|
|
|
|
): XplaMsgExecuteContract {
|
|
|
|
return new XplaMsgExecuteContract(walletAddress, tokenBridgeAddress, {
|
|
|
|
submit_vaa: {
|
|
|
|
data: fromUint8Array(signedVAA),
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-09-22 12:07:21 -07:00
|
|
|
export async function redeemAndUnwrapOnSolana(
|
|
|
|
connection: Connection,
|
|
|
|
bridgeAddress: string,
|
|
|
|
tokenBridgeAddress: string,
|
|
|
|
payerAddress: string,
|
|
|
|
signedVAA: Uint8Array
|
|
|
|
) {
|
2021-11-05 12:18:12 -07:00
|
|
|
const { parse_vaa } = await importCoreWasm();
|
|
|
|
const { complete_transfer_native_ix } = await importTokenWasm();
|
2021-09-22 12:07:21 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-08-17 18:19:15 -07:00
|
|
|
export async function redeemOnSolana(
|
|
|
|
connection: Connection,
|
|
|
|
bridgeAddress: string,
|
|
|
|
tokenBridgeAddress: string,
|
|
|
|
payerAddress: string,
|
2022-03-28 20:39:08 -07:00
|
|
|
signedVAA: Uint8Array,
|
|
|
|
feeRecipientAddress?: string
|
2021-08-17 18:19:15 -07:00
|
|
|
) {
|
2021-11-05 12:18:12 -07:00
|
|
|
const { parse_vaa } = await importCoreWasm();
|
2021-08-29 14:03:36 -07:00
|
|
|
const parsedVAA = parse_vaa(signedVAA);
|
|
|
|
const isSolanaNative =
|
|
|
|
Buffer.from(new Uint8Array(parsedVAA.payload)).readUInt16BE(65) ===
|
|
|
|
CHAIN_ID_SOLANA;
|
2021-08-17 18:19:15 -07:00
|
|
|
const { complete_transfer_wrapped_ix, complete_transfer_native_ix } =
|
2021-11-05 12:18:12 -07:00
|
|
|
await importTokenWasm();
|
2021-08-17 18:19:15 -07:00
|
|
|
const ixs = [];
|
|
|
|
if (isSolanaNative) {
|
|
|
|
ixs.push(
|
|
|
|
ixFromRust(
|
|
|
|
complete_transfer_native_ix(
|
|
|
|
tokenBridgeAddress,
|
|
|
|
bridgeAddress,
|
|
|
|
payerAddress,
|
2022-03-28 20:39:08 -07:00
|
|
|
signedVAA,
|
|
|
|
feeRecipientAddress
|
2021-08-17 18:19:15 -07:00
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
ixs.push(
|
|
|
|
ixFromRust(
|
|
|
|
complete_transfer_wrapped_ix(
|
|
|
|
tokenBridgeAddress,
|
|
|
|
bridgeAddress,
|
|
|
|
payerAddress,
|
2022-03-28 20:39:08 -07:00
|
|
|
signedVAA,
|
|
|
|
feeRecipientAddress
|
2021-08-17 18:19:15 -07:00
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
const transaction = new Transaction().add(...ixs);
|
|
|
|
const { blockhash } = await connection.getRecentBlockhash();
|
|
|
|
transaction.recentBlockhash = blockhash;
|
|
|
|
transaction.feePayer = new PublicKey(payerAddress);
|
|
|
|
return transaction;
|
|
|
|
}
|
2022-04-29 12:57:41 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This basically just submits the VAA to Algorand
|
|
|
|
* @param client AlgodV2 client
|
|
|
|
* @param tokenBridgeId Token bridge ID
|
|
|
|
* @param bridgeId Core bridge ID
|
|
|
|
* @param vaa The VAA to be redeemed
|
|
|
|
* @param acct Sending account
|
|
|
|
* @returns Transaction ID(s)
|
|
|
|
*/
|
|
|
|
export async function redeemOnAlgorand(
|
|
|
|
client: Algodv2,
|
|
|
|
tokenBridgeId: bigint,
|
|
|
|
bridgeId: bigint,
|
|
|
|
vaa: Uint8Array,
|
|
|
|
senderAddr: string
|
|
|
|
): Promise<TransactionSignerPair[]> {
|
|
|
|
return await _submitVAAAlgorand(
|
|
|
|
client,
|
|
|
|
tokenBridgeId,
|
|
|
|
bridgeId,
|
|
|
|
vaa,
|
|
|
|
senderAddr
|
|
|
|
);
|
|
|
|
}
|
2022-08-04 08:53:08 -07:00
|
|
|
|
|
|
|
export async function redeemOnNear(
|
2022-09-28 06:53:15 -07:00
|
|
|
provider: Provider,
|
|
|
|
account: string,
|
2022-08-04 08:53:08 -07:00
|
|
|
tokenBridge: string,
|
|
|
|
vaa: Uint8Array
|
2022-09-28 06:53:15 -07:00
|
|
|
): Promise<FunctionCallOptions[]> {
|
|
|
|
const options: FunctionCallOptions[] = [];
|
|
|
|
const p = _parseVAAAlgorand(vaa);
|
2022-08-04 08:53:08 -07:00
|
|
|
|
|
|
|
if (p.ToChain !== CHAIN_ID_NEAR) {
|
|
|
|
throw new Error("Not destined for NEAR");
|
|
|
|
}
|
|
|
|
|
2022-09-28 06:53:15 -07:00
|
|
|
const { found, value: receiver } = await hashLookup(
|
|
|
|
provider,
|
|
|
|
tokenBridge,
|
|
|
|
uint8ArrayToHex(p.ToAddress as Uint8Array)
|
|
|
|
);
|
2022-08-04 08:53:08 -07:00
|
|
|
|
2022-09-28 06:53:15 -07:00
|
|
|
if (!found) {
|
2022-08-04 08:53:08 -07:00
|
|
|
throw new Error(
|
|
|
|
"Unregistered receiver (receiving account is not registered)"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-09-28 06:53:15 -07:00
|
|
|
const token = await getForeignAssetNear(
|
|
|
|
provider,
|
2022-08-04 08:53:08 -07:00
|
|
|
tokenBridge,
|
|
|
|
p.FromChain as ChainId,
|
|
|
|
p.Contract as string
|
|
|
|
);
|
|
|
|
|
|
|
|
if (
|
|
|
|
(p.Contract as string) !==
|
|
|
|
"0000000000000000000000000000000000000000000000000000000000000000"
|
|
|
|
) {
|
2022-09-28 06:53:15 -07:00
|
|
|
if (token === "" || token === null) {
|
|
|
|
throw new Error("Unregistered token (has it been attested?)");
|
|
|
|
}
|
|
|
|
|
|
|
|
const bal = await callFunctionNear(
|
|
|
|
provider,
|
|
|
|
token as string,
|
|
|
|
"storage_balance_of",
|
|
|
|
{
|
|
|
|
account_id: receiver,
|
|
|
|
}
|
|
|
|
);
|
2022-08-04 08:53:08 -07:00
|
|
|
|
|
|
|
if (bal === null) {
|
2022-09-28 06:53:15 -07:00
|
|
|
options.push({
|
|
|
|
contractId: token as string,
|
|
|
|
methodName: "storage_deposit",
|
|
|
|
args: { account_id: receiver, registration_only: true },
|
|
|
|
gas: new BN("100000000000000"),
|
|
|
|
attachedDeposit: new BN("2000000000000000000000"), // 0.002 NEAR
|
|
|
|
});
|
2022-08-04 08:53:08 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
p.Fee !== undefined &&
|
|
|
|
Buffer.compare(
|
|
|
|
p.Fee,
|
|
|
|
Buffer.from(
|
|
|
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
|
|
"hex"
|
|
|
|
)
|
|
|
|
) !== 0
|
|
|
|
) {
|
2022-09-28 06:53:15 -07:00
|
|
|
const bal = await callFunctionNear(
|
|
|
|
provider,
|
2022-08-04 08:53:08 -07:00
|
|
|
token as string,
|
|
|
|
"storage_balance_of",
|
|
|
|
{
|
2022-09-28 06:53:15 -07:00
|
|
|
account_id: account,
|
2022-08-04 08:53:08 -07:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
if (bal === null) {
|
2022-09-28 06:53:15 -07:00
|
|
|
options.push({
|
|
|
|
contractId: token as string,
|
|
|
|
methodName: "storage_deposit",
|
|
|
|
args: { account_id: account, registration_only: true },
|
|
|
|
gas: new BN("100000000000000"),
|
|
|
|
attachedDeposit: new BN("2000000000000000000000"), // 0.002 NEAR
|
|
|
|
});
|
2022-08-04 08:53:08 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-28 06:53:15 -07:00
|
|
|
options.push({
|
2022-08-04 08:53:08 -07:00
|
|
|
contractId: tokenBridge,
|
|
|
|
methodName: "submit_vaa",
|
|
|
|
args: {
|
|
|
|
vaa: uint8ArrayToHex(vaa),
|
|
|
|
},
|
|
|
|
attachedDeposit: new BN("100000000000000000000000"),
|
|
|
|
gas: new BN("150000000000000"),
|
|
|
|
});
|
|
|
|
|
2022-09-28 06:53:15 -07:00
|
|
|
options.push({
|
2022-08-04 08:53:08 -07:00
|
|
|
contractId: tokenBridge,
|
|
|
|
methodName: "submit_vaa",
|
|
|
|
args: {
|
|
|
|
vaa: uint8ArrayToHex(vaa),
|
|
|
|
},
|
|
|
|
attachedDeposit: new BN("100000000000000000000000"),
|
|
|
|
gas: new BN("150000000000000"),
|
|
|
|
});
|
|
|
|
|
2022-09-28 06:53:15 -07:00
|
|
|
return options;
|
2022-08-04 08:53:08 -07:00
|
|
|
}
|
2022-10-24 15:12:02 -07:00
|
|
|
|
2022-10-24 22:15:21 -07:00
|
|
|
/**
|
|
|
|
* Register the token specified in the given VAA in the transfer recipient's account if necessary
|
|
|
|
* and complete the transfer.
|
|
|
|
* @param client Client used to transfer data to/from Aptos node
|
|
|
|
* @param tokenBridgeAddress Address of token bridge
|
|
|
|
* @param transferVAA Bytes of transfer VAA
|
|
|
|
* @returns Transaction payload
|
|
|
|
*/
|
2022-10-24 15:12:02 -07:00
|
|
|
export function redeemOnAptos(
|
|
|
|
client: AptosClient,
|
|
|
|
tokenBridgeAddress: string,
|
|
|
|
transferVAA: Uint8Array
|
|
|
|
): Promise<Types.EntryFunctionPayload> {
|
|
|
|
return completeTransferAndRegister(
|
|
|
|
client,
|
|
|
|
tokenBridgeAddress,
|
|
|
|
transferVAA
|
|
|
|
);
|
|
|
|
}
|