wormhole/sdk/js/src/token_bridge/redeem.ts

385 lines
10 KiB
TypeScript
Raw Normal View History

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";
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";
import { fromUint8Array } from "js-base64";
2022-04-29 12:57:41 -07:00
import { TransactionSignerPair, _submitVAAAlgorand } from "../algorand";
import { Bridge__factory } from "../ethers-contracts";
import { ixFromRust } from "../solana";
import { importCoreWasm, importTokenWasm } from "../solana/wasm";
import {
2022-08-04 08:53:08 -07:00
CHAIN_ID_NEAR,
CHAIN_ID_SOLANA,
2022-08-04 08:53:08 -07:00
ChainId,
MAX_VAA_DECIMALS,
WSOL_ADDRESS,
WSOL_DECIMALS,
2022-06-29 10:09:29 -07:00
uint8ArrayToHex,
callFunctionNear,
hashLookup,
} 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
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";
import { FunctionCallOptions } from "near-api-js/lib/account";
import { Provider } from "near-api-js/lib/providers";
import { MsgExecuteContract as XplaMsgExecuteContract } from "@xpla/xpla.js";
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> } = {}
) {
const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
2022-03-02 12:11:00 -08:00
const v = await bridge.completeTransfer(signedVAA, overrides);
const receipt = await v.wait();
return receipt;
}
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> } = {}
) {
const bridge = Bridge__factory.connect(tokenBridgeAddress, signer);
2022-03-02 12:11:00 -08:00
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),
},
});
}
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;
export function redeemOnXpla(
tokenBridgeAddress: string,
walletAddress: string,
signedVAA: Uint8Array
): XplaMsgExecuteContract {
return new XplaMsgExecuteContract(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,
Spy relayer cleanup (#1015) * initial spy-relayer * Update spy_relayer Dockerfile * added example mainnet config files * split out private keys into its own ENV variable * Update spy relayer supportedChains.json To remove the `walletPrivateKey` entries. All of the private keys have been split out into their own json file. * fixed evm private key env parse * missing solana accounts report 0 balance, rather than error * wallet address is logged in debug * spy_relayer: enabled prometheus default metrics Also set a prefix of `relayer_` * spy_relayer: updates to the prometheus bits * Use a single metric registry * Use a simpler metric name and add labels for individual wallets * spy_relayer: human readable app mode in the metrics [ listener | relayer | both ] * spy_relayer: unify metrics * remove the collection of default metrics * hardcode the `spy_relayer_` prefix on all custom metrics * fixed dep arrays, nullable terra token/balance info * attempt stack debug * debug pullTerraBalance * provider http or ws * update sdk * logging for tokenAddress is 0 * fix foreign address calc * fix calcLocalAddressesTerra * relayer/spy_relayer: update prometheus helpers Add / url handler for the ingress-gce stupid load balancer that doesn't support custom url healthchecks unless you make a BackendConfig custom resource definition. * logging refinement * use chain name in prometheus * adjust retry timeout calculation * spy_relayer: update prometheus bits * improved error handling * relayer ui improvements * prep sdk release * use latest sdk, manual redeem button * relaying ux improvements * gas price fix * shortened terra success log * use gh base relayer list * fix prometheus urls * Update prometheus metric name * only show TPS warning on mainnet * show relayer fee in source preview * fix unwrap check * add native bool to balance metric * logging improvements * add feeRecipientAddress to redeemOnSolana * gather solana fees * remove relayer ws support * add nativeCurrencySymbol to ChainConfigInfo * fix solana native symbol * demoteWorking option, logger contexts * scoped logging * bridge_ui: unwrap native * add evm wallet monitor test * solana vaa parsing fix * add monitorRedis * make Jeff's brain happy * log demoting keys * register redisQueue metric * human readable redisQueue metric * fix timestamp inconsistency * use scopedLogger for the first level of workers * pull wallet balances in parallel * more scoped logging * pick a solana fee * moving keys log improvement * update eth gas calculations based on recent txs * use postVaaSolanaWithRetry * split success and failures by chain * fix using terraCoin * check prom every 10s * batch getting evm token balances * batch calcLocalAddressesEVM * debug worker logging * log retry number * support Polygon? * reset status on demotion * enhance! * update avax fee Co-authored-by: Chase Moran <chasemoran45@gmail.com> Co-authored-by: Kevin Peters <kpeters@jumptrading.com> Co-authored-by: Evan Gray <battledingo@gmail.com>
2022-03-28 20:39:08 -07:00
signedVAA: Uint8Array,
feeRecipientAddress?: string
) {
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,
Spy relayer cleanup (#1015) * initial spy-relayer * Update spy_relayer Dockerfile * added example mainnet config files * split out private keys into its own ENV variable * Update spy relayer supportedChains.json To remove the `walletPrivateKey` entries. All of the private keys have been split out into their own json file. * fixed evm private key env parse * missing solana accounts report 0 balance, rather than error * wallet address is logged in debug * spy_relayer: enabled prometheus default metrics Also set a prefix of `relayer_` * spy_relayer: updates to the prometheus bits * Use a single metric registry * Use a simpler metric name and add labels for individual wallets * spy_relayer: human readable app mode in the metrics [ listener | relayer | both ] * spy_relayer: unify metrics * remove the collection of default metrics * hardcode the `spy_relayer_` prefix on all custom metrics * fixed dep arrays, nullable terra token/balance info * attempt stack debug * debug pullTerraBalance * provider http or ws * update sdk * logging for tokenAddress is 0 * fix foreign address calc * fix calcLocalAddressesTerra * relayer/spy_relayer: update prometheus helpers Add / url handler for the ingress-gce stupid load balancer that doesn't support custom url healthchecks unless you make a BackendConfig custom resource definition. * logging refinement * use chain name in prometheus * adjust retry timeout calculation * spy_relayer: update prometheus bits * improved error handling * relayer ui improvements * prep sdk release * use latest sdk, manual redeem button * relaying ux improvements * gas price fix * shortened terra success log * use gh base relayer list * fix prometheus urls * Update prometheus metric name * only show TPS warning on mainnet * show relayer fee in source preview * fix unwrap check * add native bool to balance metric * logging improvements * add feeRecipientAddress to redeemOnSolana * gather solana fees * remove relayer ws support * add nativeCurrencySymbol to ChainConfigInfo * fix solana native symbol * demoteWorking option, logger contexts * scoped logging * bridge_ui: unwrap native * add evm wallet monitor test * solana vaa parsing fix * add monitorRedis * make Jeff's brain happy * log demoting keys * register redisQueue metric * human readable redisQueue metric * fix timestamp inconsistency * use scopedLogger for the first level of workers * pull wallet balances in parallel * more scoped logging * pick a solana fee * moving keys log improvement * update eth gas calculations based on recent txs * use postVaaSolanaWithRetry * split success and failures by chain * fix using terraCoin * check prom every 10s * batch getting evm token balances * batch calcLocalAddressesEVM * debug worker logging * log retry number * support Polygon? * reset status on demotion * enhance! * update avax fee Co-authored-by: Chase Moran <chasemoran45@gmail.com> Co-authored-by: Kevin Peters <kpeters@jumptrading.com> Co-authored-by: Evan Gray <battledingo@gmail.com>
2022-03-28 20:39:08 -07:00
signedVAA,
feeRecipientAddress
)
)
);
} else {
ixs.push(
ixFromRust(
complete_transfer_wrapped_ix(
tokenBridgeAddress,
bridgeAddress,
payerAddress,
Spy relayer cleanup (#1015) * initial spy-relayer * Update spy_relayer Dockerfile * added example mainnet config files * split out private keys into its own ENV variable * Update spy relayer supportedChains.json To remove the `walletPrivateKey` entries. All of the private keys have been split out into their own json file. * fixed evm private key env parse * missing solana accounts report 0 balance, rather than error * wallet address is logged in debug * spy_relayer: enabled prometheus default metrics Also set a prefix of `relayer_` * spy_relayer: updates to the prometheus bits * Use a single metric registry * Use a simpler metric name and add labels for individual wallets * spy_relayer: human readable app mode in the metrics [ listener | relayer | both ] * spy_relayer: unify metrics * remove the collection of default metrics * hardcode the `spy_relayer_` prefix on all custom metrics * fixed dep arrays, nullable terra token/balance info * attempt stack debug * debug pullTerraBalance * provider http or ws * update sdk * logging for tokenAddress is 0 * fix foreign address calc * fix calcLocalAddressesTerra * relayer/spy_relayer: update prometheus helpers Add / url handler for the ingress-gce stupid load balancer that doesn't support custom url healthchecks unless you make a BackendConfig custom resource definition. * logging refinement * use chain name in prometheus * adjust retry timeout calculation * spy_relayer: update prometheus bits * improved error handling * relayer ui improvements * prep sdk release * use latest sdk, manual redeem button * relaying ux improvements * gas price fix * shortened terra success log * use gh base relayer list * fix prometheus urls * Update prometheus metric name * only show TPS warning on mainnet * show relayer fee in source preview * fix unwrap check * add native bool to balance metric * logging improvements * add feeRecipientAddress to redeemOnSolana * gather solana fees * remove relayer ws support * add nativeCurrencySymbol to ChainConfigInfo * fix solana native symbol * demoteWorking option, logger contexts * scoped logging * bridge_ui: unwrap native * add evm wallet monitor test * solana vaa parsing fix * add monitorRedis * make Jeff's brain happy * log demoting keys * register redisQueue metric * human readable redisQueue metric * fix timestamp inconsistency * use scopedLogger for the first level of workers * pull wallet balances in parallel * more scoped logging * pick a solana fee * moving keys log improvement * update eth gas calculations based on recent txs * use postVaaSolanaWithRetry * split success and failures by chain * fix using terraCoin * check prom every 10s * batch getting evm token balances * batch calcLocalAddressesEVM * debug worker logging * log retry number * support Polygon? * reset status on demotion * enhance! * update avax fee Co-authored-by: Chase Moran <chasemoran45@gmail.com> Co-authored-by: Kevin Peters <kpeters@jumptrading.com> Co-authored-by: Evan Gray <battledingo@gmail.com>
2022-03-28 20:39:08 -07:00
signedVAA,
feeRecipientAddress
)
)
);
}
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(
provider: Provider,
account: string,
2022-08-04 08:53:08 -07:00
tokenBridge: string,
vaa: Uint8Array
): 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");
}
const { found, value: receiver } = await hashLookup(
provider,
tokenBridge,
uint8ArrayToHex(p.ToAddress as Uint8Array)
);
2022-08-04 08:53:08 -07:00
if (!found) {
2022-08-04 08:53:08 -07:00
throw new Error(
"Unregistered receiver (receiving account is not registered)"
);
}
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"
) {
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) {
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
) {
const bal = await callFunctionNear(
provider,
2022-08-04 08:53:08 -07:00
token as string,
"storage_balance_of",
{
account_id: account,
2022-08-04 08:53:08 -07:00
}
);
if (bal === null) {
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
}
}
}
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"),
});
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"),
});
return options;
2022-08-04 08:53:08 -07:00
}