sdk: move terra functions to sdk

Change-Id: Idb255bc0c63189f69f5e79efce4a801e2aeedf46
This commit is contained in:
Reisen 2021-08-20 11:22:15 +00:00
parent cea46cdfbe
commit 2a317ab923
18 changed files with 2548 additions and 113 deletions

View File

@ -10,6 +10,7 @@ import { zeroPad } from "ethers/lib/utils";
import { useCallback, useEffect, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useEthereumProvider } from "../../contexts/EthereumProviderContext";
import { useConnectedWallet } from "@terra-money/wallet-provider";
import { useSolanaWallet } from "../../contexts/SolanaWalletContext";
import {
selectTransferAmount,
@ -58,6 +59,7 @@ function Send() {
const isSendComplete = useSelector(selectTransferIsSendComplete);
const { signer, signerAddress } = useEthereumProvider();
const { wallet } = useSolanaWallet();
const terraWallet = useConnectedWallet();
const solPK = wallet?.publicKey;
const sourceParsedTokenAccount = useSelector(
selectTransferSourceParsedTokenAccount
@ -154,7 +156,13 @@ function Send() {
(async () => {
dispatch(setIsSending(true));
try {
const vaaBytes = await transferFromTerra(undefined);
const vaaBytes = await transferFromTerra(
terraWallet,
sourceAsset,
amount,
"",
targetChain,
);
console.log("bytes in transfer", vaaBytes);
vaaBytes && dispatch(setSignedVAAHex(uint8ArrayToHex(vaaBytes)));
} catch (e) {

View File

@ -31,7 +31,7 @@ function Source() {
const sourceAsset = useSelector(selectTransferSourceAsset);
const uiAmountString = useSelector(selectTransferSourceBalanceString);
const amount = useSelector(selectTransferAmount);
const isSourceComplete = useSelector(selectTransferIsSourceComplete);
const isSourceComplete = true; // useSelector(selectTransferIsSourceComplete);
const shouldLockFields = useSelector(selectTransferShouldLockFields);
const handleSourceChange = useCallback(
(event) => {

View File

@ -1,17 +1,19 @@
import {
attestFromEth as attestEthTx,
attestFromSolana as attestSolanaTx,
attestFromTerra as attestTerraTx,
CHAIN_ID_ETH,
CHAIN_ID_SOLANA,
CHAIN_ID_TERRA,
getEmitterAddressEth,
getEmitterAddressSolana,
getEmitterAddressTerra,
parseSequenceFromLogEth,
parseSequenceFromLogSolana,
parseSequenceFromLogTerra,
} from "@certusone/wormhole-sdk";
import Wallet from "@project-serum/sol-wallet-adapter";
import { Connection } from "@solana/web3.js";
import { MsgExecuteContract } from "@terra-money/terra.js";
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
import { ethers } from "ethers";
import {
@ -24,6 +26,7 @@ import {
} from "./consts";
import { getSignedVAAWithRetry } from "./getSignedVAAWithRetry";
import { signSendConfirmAndGet } from "./solana";
import { waitForTerraExecution } from "./terra";
export async function attestFromEth(
signer: ethers.Signer | undefined,
@ -80,24 +83,21 @@ export async function attestFromTerra(
wallet: TerraConnectedWallet | undefined,
asset: string | undefined
) {
const nonceConst = Math.random() * 100000;
const nonceBuffer = Buffer.alloc(4);
nonceBuffer.writeUInt32LE(nonceConst, 0);
wallet &&
(await wallet.post({
msgs: [
new MsgExecuteContract(
wallet.terraAddress,
TERRA_TOKEN_BRIDGE_ADDRESS,
{
register_asset_hook: {
asset_id: asset,
},
},
{ uluna: 1000 }
),
],
memo: "Create Wrapped",
}));
return null;
if (!wallet || !asset) return;
const infoMaybe = await attestTerraTx(
TERRA_TOKEN_BRIDGE_ADDRESS,
wallet,
asset
);
const info = await waitForTerraExecution(wallet, infoMaybe);
const sequence = parseSequenceFromLogTerra(info);
const emitterAddress = await getEmitterAddressTerra(
TERRA_TOKEN_BRIDGE_ADDRESS
);
const result = await getSignedVAAWithRetry(
CHAIN_ID_TERRA,
emitterAddress,
sequence
);
return result && result.vaaBytes;
}

View File

@ -2,12 +2,11 @@ import {
postVaaSolana,
createWrappedOnEth as createWrappedOnEthTx,
createWrappedOnSolana as createWrappedOnSolanaTx,
createWrappedOnTerra as createWrappedOnTerraTx,
} from "@certusone/wormhole-sdk";
import Wallet from "@project-serum/sol-wallet-adapter";
import { Connection } from "@solana/web3.js";
import { ethers } from "ethers";
import { fromUint8Array } from "js-base64";
import { MsgExecuteContract } from "@terra-money/terra.js";
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
import {
ETH_TOKEN_BRIDGE_ADDRESS,
@ -31,26 +30,12 @@ export async function createWrappedOnTerra(
wallet: TerraConnectedWallet | undefined,
signedVAA: Uint8Array
) {
console.log("creating wrapped");
console.log("PROGRAM:", TERRA_TOKEN_BRIDGE_ADDRESS);
console.log("BRIDGE:", TERRA_BRIDGE_ADDRESS);
console.log("VAA:", signedVAA);
wallet &&
(await wallet.post({
msgs: [
new MsgExecuteContract(
wallet.terraAddress,
TERRA_TOKEN_BRIDGE_ADDRESS,
{
submit_vaa: {
data: fromUint8Array(signedVAA),
},
},
{ uluna: 1000 }
),
],
memo: "Create Wrapped",
}));
if (!wallet) return;
await createWrappedOnTerraTx(
TERRA_TOKEN_BRIDGE_ADDRESS,
wallet,
signedVAA
);
}
export async function createWrappedOnSolana(

View File

@ -11,6 +11,7 @@ import {
ETH_TOKEN_BRIDGE_ADDRESS,
SOLANA_HOST,
SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_TEST_TOKEN_ADDRESS,
} from "./consts";
export interface StateSafeWormholeWrappedInfo {
@ -57,7 +58,7 @@ export async function getOriginalAssetTerra(
mintAddress: string
): Promise<StateSafeWormholeWrappedInfo> {
return {
assetAddress: "",
assetAddress: TERRA_TEST_TOKEN_ADDRESS,
chainId: 3,
isWrapped: false,
};

View File

@ -0,0 +1,15 @@
import { TxResult, ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
import { TxInfo, LCDClient } from "@terra-money/terra.js";
// TODO: Loop txInfo for timed out transactions.
// lcd.tx.txInfo(transaction.result.txhash);
export async function waitForTerraExecution(
wallet: TerraConnectedWallet,
transaction: TxResult
) {
const lcd = new LCDClient({
URL: wallet.network.lcd,
chainID: "columbus-4",
});
return transaction;
}

View File

@ -7,12 +7,15 @@ import {
getEmitterAddressSolana,
parseSequenceFromLogEth,
parseSequenceFromLogSolana,
parseSequenceFromLogTerra,
transferFromEth as transferFromEthTx,
transferFromSolana as transferFromSolanaTx,
} from "@certusone/wormhole-sdk";
import { Wallet as TerraWallet } from "@terra-money/wallet-provider";
import { fromUint8Array } from 'js-base64';
import { ConnectedWallet as TerraConnectedWallet, TxResult } from "@terra-money/wallet-provider";
import Wallet from "@project-serum/sol-wallet-adapter";
import { Connection } from "@solana/web3.js";
import { MsgExecuteContract } from "@terra-money/terra.js";
import { ethers } from "ethers";
import { arrayify, parseUnits, zeroPad } from "ethers/lib/utils";
import { hexToUint8Array } from "./array";
@ -22,6 +25,8 @@ import {
SOLANA_HOST,
SOL_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS,
TERRA_TOKEN_BRIDGE_ADDRESS,
TERRA_TEST_TOKEN_ADDRESS,
} from "./consts";
import { getSignedVAAWithRetry } from "./getSignedVAAWithRetry";
import { signSendConfirmAndGet } from "./solana";
@ -114,7 +119,44 @@ export async function transferFromSolana(
}
export async function transferFromTerra(
wallet: TerraWallet | undefined,
wallet: TerraConnectedWallet | undefined,
asset: string,
amount: string,
targetAddressStr: string | undefined,
targetChain: ChainId,
) {
return null;
if (!wallet) return;
const result: TxResult = wallet && await wallet.post({
msgs: [
new MsgExecuteContract(
wallet.terraAddress,
TERRA_TOKEN_BRIDGE_ADDRESS,
{
initiate_transfer: {
asset: TERRA_TEST_TOKEN_ADDRESS,
amount: amount,
recipient_chain: targetChain,
recipient: targetAddressStr,
fee: 1000,
nonce: 0,
},
},
{ uluna: 1000 }
),
],
memo: "Complete Transfer",
});
console.log(result);
const sequence = parseSequenceFromLogTerra(result);
console.log(sequence);
const emitterAddress = await getEmitterAddressSolana(
SOL_TOKEN_BRIDGE_ADDRESS
);
console.log(emitterAddress);
const { vaaBytes } = await getSignedVAAWithRetry(
CHAIN_ID_TERRA,
emitterAddress,
sequence
);
return vaaBytes;
}

2360
sdk/js/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -38,6 +38,7 @@
"@typechain/ethers-v5": "^7.0.1",
"@types/long": "^4.0.1",
"@types/node": "^16.6.1",
"@types/react": "^17.0.19",
"copy-dir": "^1.3.0",
"ethers": "^5.4.4",
"prettier": "^2.3.2",
@ -50,6 +51,9 @@
"@project-serum/sol-wallet-adapter": "^0.2.5",
"@solana/spl-token": "^0.1.8",
"@solana/web3.js": "^1.24.0",
"@terra-money/terra.js": "^1.8.10",
"@terra-money/wallet-provider": "^1.2.4",
"js-base64": "^3.6.1",
"protobufjs": "^6.11.2",
"rxjs": "^7.3.0"
}

View File

@ -13,3 +13,9 @@ export async function getEmitterAddressSolana(programAddress: string) {
zeroPad(new PublicKey(emitter_address(programAddress)).toBytes(), 32)
).toString("hex");
}
export async function getEmitterAddressTerra(programAddress: string) {
// Testnet Hardcoded
// TODO: HumanAddr -> CanonicalAddr
return "000000000000000000000000784999135aaa8a3ca5914468852fdddbddd8789d";
}

View File

@ -1,5 +1,6 @@
import { TransactionResponse } from "@solana/web3.js";
import { ContractReceipt } from "ethers";
import { TxResult } from "@terra-money/wallet-provider";
import { Implementation__factory } from "../ethers-contracts";
export function parseSequenceFromLogEth(
@ -17,6 +18,24 @@ export function parseSequenceFromLogEth(
return sequence.toString();
}
export function parseSequenceFromLogTerra(result: TxResult): string {
// Scan for the Sequence attribute in all the outputs of the transaction.
// TODO: Make this not horrible.
let sequence = "";
const jsonLog = JSON.parse(result.result.raw_log);
jsonLog.map((row: any) => {
row.events.map((event: any) => {
event.attributes.map((attribute: any) => {
if(attribute.key === "message.sequence") {
sequence = attribute.value;
}
});
});
});
console.log('Terra Sequence: ', sequence);
return sequence.toString();
}
const SOLANA_SEQ_LOG = "Program log: Sequence: ";
export function parseSequenceFromLogSolana(info: TransactionResponse) {
// TODO: better parsing, safer

View File

@ -3,6 +3,8 @@ import { ethers } from "ethers";
import { Bridge__factory } from "../ethers-contracts";
import { getBridgeFeeIx, ixFromRust } from "../solana";
import { createNonce } from "../utils/createNonce";
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
import { MsgExecuteContract } from "@terra-money/terra.js";
export async function attestFromEth(
tokenBridgeAddress: string,
@ -15,6 +17,30 @@ export async function attestFromEth(
return receipt;
}
export async function attestFromTerra(
tokenBridgeAddress: string,
wallet: TerraConnectedWallet,
asset: string,
) {
const nonce = Math.round(Math.random() * 100000);
return await wallet.post({
msgs: [
new MsgExecuteContract(
wallet.terraAddress,
tokenBridgeAddress,
{
create_asset_meta: {
asset_address: asset,
nonce: nonce,
},
},
{ uluna: 10000 }
),
],
memo: "Create Wrapped",
});
}
export async function attestFromSolana(
connection: Connection,
bridgeAddress: string,

View File

@ -1,7 +1,10 @@
import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { ethers } from "ethers";
import { Bridge__factory } from "../ethers-contracts";
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
import { MsgExecuteContract } from "@terra-money/terra.js";
import { ixFromRust } from "../solana";
import { fromUint8Array } from "js-base64";
export async function createWrappedOnEth(
tokenBridgeAddress: string,
@ -14,6 +17,31 @@ export async function createWrappedOnEth(
return receipt;
}
export async function createWrappedOnTerra(
tokenBridgeAddress: string,
wallet: TerraConnectedWallet,
signedVAA: Uint8Array,
) {
console.log(tokenBridgeAddress);
console.log(wallet.terraAddress);
console.log(signedVAA);
await wallet.post({
msgs: [
new MsgExecuteContract(
wallet.terraAddress,
tokenBridgeAddress,
{
submit_vaa: {
data: fromUint8Array(signedVAA),
},
},
{ uluna: 1000 }
),
],
memo: "Create Wrapped",
});
}
export async function createWrappedOnSolana(
connection: Connection,
bridgeAddress: string,

View File

@ -2,6 +2,7 @@ import { Connection, PublicKey } from "@solana/web3.js";
import { ethers } from "ethers";
import { Bridge__factory } from "../ethers-contracts";
import { ChainId } from "../utils";
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
/**
* Returns a foreign asset address on Ethereum for a provided native chain and asset address, AddressZero if it does not exist
@ -25,6 +26,15 @@ export async function getForeignAssetEth(
}
}
export async function getForeignAssetTerra(
tokenBridgeAddress: string,
wallet: TerraConnectedWallet,
originChain: ChainId,
originAsset: Uint8Array
) {
return null;
}
/**
* Returns a foreign asset address on Solana for a provided native chain and asset address
* @param connection

View File

@ -1,6 +1,7 @@
import { Connection, PublicKey } from "@solana/web3.js";
import { ethers } from "ethers";
import { Bridge__factory } from "../ethers-contracts";
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
/**
* Returns whether or not an asset address on Ethereum is a wormhole wrapped asset
@ -19,6 +20,14 @@ export async function getIsWrappedAssetEth(
return await tokenBridge.isWrappedAsset(assetAddress);
}
export async function getIsWrappedAssetTerra(
tokenBridgeAddress: string,
wallet: TerraConnectedWallet,
assetAddress: string
) {
return false;
}
/**
* Returns whether or not an asset on Solana is a wormhole wrapped asset
* @param connection

View File

@ -2,8 +2,9 @@ import { Connection, PublicKey } from "@solana/web3.js";
import { ethers } from "ethers";
import { arrayify } from "ethers/lib/utils";
import { TokenImplementation__factory } from "../ethers-contracts";
import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils";
import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA, CHAIN_ID_TERRA } from "../utils";
import { getIsWrappedAssetEth } from "./getIsWrappedAsset";
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
export interface WormholeWrappedInfo {
isWrapped: boolean;
@ -48,6 +49,18 @@ export async function getOriginalAssetEth(
};
}
export async function getOriginalAssetTerra(
tokenBridgeAddress: string,
wallet: TerraConnectedWallet,
wrappedAddress: string
): Promise<WormholeWrappedInfo> {
return {
isWrapped: false,
chainId: CHAIN_ID_TERRA,
assetAddress: arrayify(""),
};
}
/**
* Returns a origin chain and asset address on {originChain} for a provided Wormhole wrapped address
* @param connection

View File

@ -7,6 +7,8 @@ import {
} from "../ethers-contracts";
import { getBridgeFeeIx, ixFromRust } from "../solana";
import { ChainId, CHAIN_ID_SOLANA, createNonce } from "../utils";
import { ConnectedWallet as TerraConnectedWallet } from "@terra-money/wallet-provider";
import { MsgExecuteContract } from "@terra-money/terra.js";
export async function transferFromEth(
tokenBridgeAddress: string,
@ -39,6 +41,37 @@ export async function transferFromEth(
return receipt;
}
export async function transferFromTerra(
wallet: TerraConnectedWallet,
tokenBridgeAddress: string,
tokenAddress: string,
amount: ethers.BigNumberish,
recipientChain: ChainId,
recipientAddress: Uint8Array
) {
const nonce = Math.round(Math.random() * 100000);
return await wallet.post({
msgs: [
new MsgExecuteContract(
wallet.terraAddress,
tokenBridgeAddress,
{
initiate_transfer: {
asset: tokenAddress,
amount: amount,
recipient_chain: recipientChain,
recipient: recipientAddress,
fee: 1000,
nonce: nonce,
},
},
{ uluna: 10000 }
),
],
memo: "Complete Transfer",
});
}
export async function transferFromSolana(
connection: Connection,
bridgeAddress: string,

View File

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \