bridge_ui: attestFrom eth and sol
Change-Id: I2eed25b47bcac8891e059d0e11aa624aba802c47
This commit is contained in:
parent
8444363ac8
commit
012c30b30b
|
@ -7,13 +7,16 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import { ethers } from "ethers";
|
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||||
import useEthereumBalance from "../hooks/useEthereumBalance";
|
import useEthereumBalance from "../hooks/useEthereumBalance";
|
||||||
import useSolanaBalance from "../hooks/useSolanaBalance";
|
import useSolanaBalance from "../hooks/useSolanaBalance";
|
||||||
import useWrappedAsset from "../hooks/useWrappedAsset";
|
import useWrappedAsset from "../hooks/useWrappedAsset";
|
||||||
|
import attestFrom, {
|
||||||
|
attestFromEth,
|
||||||
|
attestFromSolana,
|
||||||
|
} from "../utils/attestFrom";
|
||||||
import {
|
import {
|
||||||
ChainId,
|
ChainId,
|
||||||
CHAINS,
|
CHAINS,
|
||||||
|
@ -109,16 +112,29 @@ function Transfer() {
|
||||||
decimals: solDecimals,
|
decimals: solDecimals,
|
||||||
uiAmount: solBalance,
|
uiAmount: solBalance,
|
||||||
} = useSolanaBalance(assetAddress, solPK, fromChain === CHAIN_ID_SOLANA);
|
} = useSolanaBalance(assetAddress, solPK, fromChain === CHAIN_ID_SOLANA);
|
||||||
const { isLoading: isCheckingWrapped, wrappedAsset } = useWrappedAsset(
|
const {
|
||||||
toChain,
|
isLoading: isCheckingWrapped,
|
||||||
fromChain,
|
isWrapped,
|
||||||
assetAddress,
|
wrappedAsset,
|
||||||
provider
|
} = useWrappedAsset(toChain, fromChain, assetAddress, provider);
|
||||||
);
|
console.log(isCheckingWrapped, isWrapped, wrappedAsset);
|
||||||
console.log(isCheckingWrapped, wrappedAsset);
|
const handleAttestClick = useCallback(() => {
|
||||||
// TODO: make a helper function for this
|
// TODO: more generic way of calling these
|
||||||
const isWrapped = true;
|
if (attestFrom[fromChain]) {
|
||||||
// wrappedAsset && wrappedAsset !== ethers.constants.AddressZero;
|
if (
|
||||||
|
fromChain === CHAIN_ID_ETH &&
|
||||||
|
attestFrom[fromChain] === attestFromEth
|
||||||
|
) {
|
||||||
|
attestFromEth(provider, assetAddress);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
fromChain === CHAIN_ID_SOLANA &&
|
||||||
|
attestFrom[fromChain] === attestFromSolana
|
||||||
|
) {
|
||||||
|
attestFromSolana(wallet, solPK?.toString(), assetAddress, solDecimals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [fromChain, provider, wallet, solPK, assetAddress, solDecimals]);
|
||||||
// TODO: dynamically get "to" wallet
|
// TODO: dynamically get "to" wallet
|
||||||
const handleTransferClick = useCallback(() => {
|
const handleTransferClick = useCallback(() => {
|
||||||
// TODO: more generic way of calling these
|
// TODO: more generic way of calling these
|
||||||
|
@ -169,12 +185,18 @@ function Transfer() {
|
||||||
fromChain === CHAIN_ID_ETH
|
fromChain === CHAIN_ID_ETH
|
||||||
);
|
);
|
||||||
const balance = Number(ethBalance) || solBalance;
|
const balance = Number(ethBalance) || solBalance;
|
||||||
|
const isAttestImplemented = !!attestFrom[fromChain];
|
||||||
const isTransferImplemented = !!transferFrom[fromChain];
|
const isTransferImplemented = !!transferFrom[fromChain];
|
||||||
const isProviderConnected = !!provider;
|
const isProviderConnected = !!provider;
|
||||||
const isRecipientAvailable = !!solPK;
|
const isRecipientAvailable = !!solPK;
|
||||||
const isAddressDefined = !!assetAddress;
|
const isAddressDefined = !!assetAddress;
|
||||||
const isAmountPositive = Number(amount) > 0; // TODO: this needs per-chain, bn parsing
|
const isAmountPositive = Number(amount) > 0; // TODO: this needs per-chain, bn parsing
|
||||||
const isBalanceAtLeastAmount = balance >= Number(amount); // TODO: ditto
|
const isBalanceAtLeastAmount = balance >= Number(amount); // TODO: ditto
|
||||||
|
const canAttemptAttest =
|
||||||
|
isAttestImplemented &&
|
||||||
|
isProviderConnected &&
|
||||||
|
isRecipientAvailable &&
|
||||||
|
isAddressDefined;
|
||||||
const canAttemptTransfer =
|
const canAttemptTransfer =
|
||||||
isTransferImplemented &&
|
isTransferImplemented &&
|
||||||
isProviderConnected &&
|
isProviderConnected &&
|
||||||
|
@ -267,7 +289,8 @@ function Transfer() {
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={isCheckingWrapped}
|
disabled={isCheckingWrapped || !canAttemptAttest}
|
||||||
|
onClick={handleAttestClick}
|
||||||
className={classes.transferButton}
|
className={classes.transferButton}
|
||||||
>
|
>
|
||||||
Attest
|
Attest
|
||||||
|
@ -286,13 +309,25 @@ function Transfer() {
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{isCheckingWrapped ? null : (
|
{isCheckingWrapped ? null : canAttemptAttest ? (
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
<br />
|
<br />
|
||||||
This token does not exist on {CHAINS_BY_ID[toChain].name}. Someone
|
This token does not exist on {CHAINS_BY_ID[toChain].name}. Someone
|
||||||
must attest the the token to the target chain before it can be
|
must attest the the token to the target chain before it can be
|
||||||
transferred.
|
transferred.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
) : (
|
||||||
|
<Typography variant="body2" color="error">
|
||||||
|
{!isAttestImplemented
|
||||||
|
? `Transfer is not yet implemented for ${CHAINS_BY_ID[fromChain].name}`
|
||||||
|
: !isProviderConnected
|
||||||
|
? "The source wallet is not connected"
|
||||||
|
: !isRecipientAvailable
|
||||||
|
? "The receiving wallet is not connected"
|
||||||
|
: !isAddressDefined
|
||||||
|
? "Please provide an asset address"
|
||||||
|
: ""}
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ChainId, CHAIN_ID_ETH } from "../utils/consts";
|
import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts";
|
||||||
import { wrappedAssetEth } from "../utils/wrappedAsset";
|
import {
|
||||||
|
getAttestedAssetEth,
|
||||||
|
getAttestedAssetSol,
|
||||||
|
} from "../utils/getAttestedAsset";
|
||||||
export interface WrappedAssetState {
|
export interface WrappedAssetState {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
isWrapped: boolean;
|
||||||
wrappedAsset: string | null;
|
wrappedAsset: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,19 +19,38 @@ function useWrappedAsset(
|
||||||
) {
|
) {
|
||||||
const [state, setState] = useState<WrappedAssetState>({
|
const [state, setState] = useState<WrappedAssetState>({
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
isWrapped: false,
|
||||||
wrappedAsset: null,
|
wrappedAsset: null,
|
||||||
});
|
});
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
(async () => {
|
(async () => {
|
||||||
if (provider && checkChain === CHAIN_ID_ETH) {
|
if (checkChain === CHAIN_ID_ETH && provider) {
|
||||||
setState({ isLoading: true, wrappedAsset: null });
|
setState({ isLoading: true, isWrapped: false, wrappedAsset: null });
|
||||||
const asset = await wrappedAssetEth(provider, originChain, originAsset);
|
const asset = await getAttestedAssetEth(
|
||||||
|
provider,
|
||||||
|
originChain,
|
||||||
|
originAsset
|
||||||
|
);
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setState({ isLoading: false, wrappedAsset: asset });
|
setState({
|
||||||
|
isLoading: false,
|
||||||
|
isWrapped: !!asset && asset !== ethers.constants.AddressZero,
|
||||||
|
wrappedAsset: asset,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (checkChain === CHAIN_ID_SOLANA) {
|
||||||
|
setState({ isLoading: true, isWrapped: false, wrappedAsset: null });
|
||||||
|
const asset = await getAttestedAssetSol(originChain, originAsset);
|
||||||
|
if (!cancelled) {
|
||||||
|
setState({
|
||||||
|
isLoading: false,
|
||||||
|
isWrapped: !!asset,
|
||||||
|
wrappedAsset: asset,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setState({ isLoading: false, wrappedAsset: null });
|
setState({ isLoading: false, isWrapped: false, wrappedAsset: null });
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -1,9 +1,33 @@
|
||||||
|
import {
|
||||||
|
AccountMeta,
|
||||||
|
PublicKey,
|
||||||
|
TransactionInstruction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
import {
|
import {
|
||||||
GrpcWebImpl,
|
GrpcWebImpl,
|
||||||
PublicrpcClientImpl,
|
PublicrpcClientImpl,
|
||||||
} from "../proto/publicrpc/v1/publicrpc";
|
} from "../proto/publicrpc/v1/publicrpc";
|
||||||
import { ChainId } from "../utils/consts";
|
import { ChainId } from "../utils/consts";
|
||||||
|
|
||||||
|
// begin from clients\solana\main.ts
|
||||||
|
export function ixFromRust(data: any): TransactionInstruction {
|
||||||
|
let keys: Array<AccountMeta> = data.accounts.map(accountMetaFromRust);
|
||||||
|
return new TransactionInstruction({
|
||||||
|
programId: new PublicKey(data.program_id),
|
||||||
|
data: Buffer.from(data.data),
|
||||||
|
keys: keys,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function accountMetaFromRust(meta: any): AccountMeta {
|
||||||
|
return {
|
||||||
|
pubkey: new PublicKey(meta.pubkey),
|
||||||
|
isSigner: meta.is_signer,
|
||||||
|
isWritable: meta.is_writable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// end from clients\solana\main.ts
|
||||||
|
|
||||||
export async function getSignedVAA(
|
export async function getSignedVAA(
|
||||||
emitterChain: ChainId,
|
emitterChain: ChainId,
|
||||||
emitterAddress: string,
|
emitterAddress: string,
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
import Wallet from "@project-serum/sol-wallet-adapter";
|
||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
SystemProgram,
|
||||||
|
Transaction,
|
||||||
|
} from "@solana/web3.js";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import { arrayify, zeroPad } from "ethers/lib/utils";
|
||||||
|
import { Bridge__factory, Implementation__factory } from "../ethers-contracts";
|
||||||
|
import { getSignedVAA, ixFromRust } from "../sdk";
|
||||||
|
import {
|
||||||
|
CHAIN_ID_ETH,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
ETH_BRIDGE_ADDRESS,
|
||||||
|
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
SOLANA_HOST,
|
||||||
|
SOL_BRIDGE_ADDRESS,
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
} from "./consts";
|
||||||
|
|
||||||
|
// TODO: this should probably be extended from the context somehow so that the signatures match
|
||||||
|
// TODO: allow for / handle cancellation?
|
||||||
|
// TODO: overall better input checking and error handling
|
||||||
|
export function attestFromEth(
|
||||||
|
provider: ethers.providers.Web3Provider | undefined,
|
||||||
|
tokenAddress: string
|
||||||
|
) {
|
||||||
|
if (!provider) return;
|
||||||
|
const signer = provider.getSigner();
|
||||||
|
if (!signer) return;
|
||||||
|
//TODO: more catches
|
||||||
|
(async () => {
|
||||||
|
const signerAddress = await signer.getAddress();
|
||||||
|
console.log("Signer:", signerAddress);
|
||||||
|
console.log("Token:", tokenAddress);
|
||||||
|
const nonceConst = Math.random() * 100000;
|
||||||
|
const nonceBuffer = Buffer.alloc(4);
|
||||||
|
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||||
|
console.log("Initiating attestation");
|
||||||
|
console.log("Nonce:", nonceBuffer);
|
||||||
|
const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer);
|
||||||
|
const v = await bridge.attestToken(tokenAddress, nonceBuffer);
|
||||||
|
const receipt = await v.wait();
|
||||||
|
// TODO: dangerous!(?)
|
||||||
|
const bridgeLog = receipt.logs.filter((l) => {
|
||||||
|
console.log(l.address, ETH_BRIDGE_ADDRESS);
|
||||||
|
return l.address === ETH_BRIDGE_ADDRESS;
|
||||||
|
})[0];
|
||||||
|
const {
|
||||||
|
args: { sequence },
|
||||||
|
} = Implementation__factory.createInterface().parseLog(bridgeLog);
|
||||||
|
console.log("SEQ:", sequence);
|
||||||
|
const emitterAddress = Buffer.from(
|
||||||
|
zeroPad(arrayify(ETH_TOKEN_BRIDGE_ADDRESS), 32)
|
||||||
|
).toString("hex");
|
||||||
|
const { vaaBytes } = await getSignedVAA(
|
||||||
|
CHAIN_ID_ETH,
|
||||||
|
emitterAddress,
|
||||||
|
sequence
|
||||||
|
);
|
||||||
|
console.log("SIGNED VAA:", vaaBytes);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: need to check transfer native vs transfer wrapped
|
||||||
|
// TODO: switch out targetProvider for generic address (this likely involves getting these in their respective contexts)
|
||||||
|
export function attestFromSolana(
|
||||||
|
wallet: Wallet | undefined,
|
||||||
|
payerAddress: string | undefined, //TODO: we may not need this since we have wallet
|
||||||
|
mintAddress: string,
|
||||||
|
decimals: number
|
||||||
|
) {
|
||||||
|
if (!wallet || !wallet.publicKey || !payerAddress) return;
|
||||||
|
(async () => {
|
||||||
|
const nonceConst = Math.random() * 100000;
|
||||||
|
const nonceBuffer = Buffer.alloc(4);
|
||||||
|
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||||
|
const nonce = nonceBuffer.readUInt32LE(0);
|
||||||
|
console.log("program:", SOL_TOKEN_BRIDGE_ADDRESS);
|
||||||
|
console.log("bridge:", SOL_BRIDGE_ADDRESS);
|
||||||
|
console.log("payer:", payerAddress);
|
||||||
|
console.log("token:", mintAddress);
|
||||||
|
console.log("nonce:", nonce);
|
||||||
|
const bridge = await import("bridge");
|
||||||
|
const feeAccount = await bridge.fee_collector_address(SOL_BRIDGE_ADDRESS);
|
||||||
|
const bridgeStatePK = new PublicKey(
|
||||||
|
bridge.state_address(SOL_BRIDGE_ADDRESS)
|
||||||
|
);
|
||||||
|
// TODO: share connection in context?
|
||||||
|
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||||
|
const bridgeStateAccountInfo = await connection.getAccountInfo(
|
||||||
|
bridgeStatePK
|
||||||
|
);
|
||||||
|
if (bridgeStateAccountInfo?.data === undefined) {
|
||||||
|
throw new Error("bridge state not found");
|
||||||
|
}
|
||||||
|
const bridgeState = bridge.parse_state(
|
||||||
|
new Uint8Array(bridgeStateAccountInfo?.data)
|
||||||
|
);
|
||||||
|
const transferIx = SystemProgram.transfer({
|
||||||
|
fromPubkey: new PublicKey(payerAddress),
|
||||||
|
toPubkey: new PublicKey(feeAccount),
|
||||||
|
lamports: bridgeState.config.fee,
|
||||||
|
});
|
||||||
|
// TODO: pass in connection
|
||||||
|
// Add transfer instruction to transaction
|
||||||
|
const { attest_ix, emitter_address } = await import("token-bridge");
|
||||||
|
const ix = ixFromRust(
|
||||||
|
attest_ix(
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
SOL_BRIDGE_ADDRESS,
|
||||||
|
payerAddress,
|
||||||
|
mintAddress,
|
||||||
|
decimals,
|
||||||
|
mintAddress, // TODO: automate on wasm side
|
||||||
|
nonce
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const transaction = new Transaction().add(transferIx, ix);
|
||||||
|
const { blockhash } = await connection.getRecentBlockhash();
|
||||||
|
transaction.recentBlockhash = blockhash;
|
||||||
|
transaction.feePayer = new PublicKey(payerAddress);
|
||||||
|
// Sign transaction, broadcast, and confirm
|
||||||
|
const signed = await wallet.signTransaction(transaction);
|
||||||
|
console.log("SIGNED", signed);
|
||||||
|
const txid = await connection.sendRawTransaction(signed.serialize());
|
||||||
|
console.log("SENT", txid);
|
||||||
|
const conf = await connection.confirmTransaction(txid);
|
||||||
|
console.log("CONFIRMED", conf);
|
||||||
|
const info = await connection.getTransaction(txid);
|
||||||
|
console.log("INFO", info);
|
||||||
|
// TODO: better parsing, safer
|
||||||
|
const SEQ_LOG = "Program log: Sequence: ";
|
||||||
|
const sequence = info?.meta?.logMessages
|
||||||
|
?.filter((msg) => msg.startsWith(SEQ_LOG))[0]
|
||||||
|
.replace(SEQ_LOG, "");
|
||||||
|
if (!sequence) {
|
||||||
|
throw new Error("sequence not found");
|
||||||
|
}
|
||||||
|
console.log("SEQ", sequence);
|
||||||
|
const emitterAddress = Buffer.from(
|
||||||
|
zeroPad(
|
||||||
|
new PublicKey(emitter_address(SOL_TOKEN_BRIDGE_ADDRESS)).toBytes(),
|
||||||
|
32
|
||||||
|
)
|
||||||
|
).toString("hex");
|
||||||
|
const { vaaBytes } = await getSignedVAA(
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
emitterAddress,
|
||||||
|
sequence
|
||||||
|
);
|
||||||
|
console.log("SIGNED VAA:", vaaBytes);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
const attestFrom = {
|
||||||
|
[CHAIN_ID_ETH]: attestFromEth,
|
||||||
|
[CHAIN_ID_SOLANA]: attestFromSolana,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default attestFrom;
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { Connection, PublicKey } from "@solana/web3.js";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import { arrayify, isHexString, zeroPad } from "ethers/lib/utils";
|
||||||
|
import { Bridge__factory } from "../ethers-contracts";
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
SOLANA_HOST,
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
} from "./consts";
|
||||||
|
|
||||||
|
export async function getAttestedAssetEth(
|
||||||
|
provider: ethers.providers.Web3Provider,
|
||||||
|
originChain: ChainId,
|
||||||
|
originAsset: string
|
||||||
|
) {
|
||||||
|
const tokenBridge = Bridge__factory.connect(
|
||||||
|
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
provider
|
||||||
|
);
|
||||||
|
// TODO: address conversion may be more complex than this
|
||||||
|
const originAssetBytes = zeroPad(
|
||||||
|
originChain === CHAIN_ID_SOLANA
|
||||||
|
? new PublicKey(originAsset).toBytes()
|
||||||
|
: arrayify(originAsset),
|
||||||
|
32
|
||||||
|
);
|
||||||
|
return await tokenBridge.wrappedAsset(originChain, originAssetBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAttestedAssetSol(
|
||||||
|
originChain: ChainId,
|
||||||
|
originAsset: string
|
||||||
|
) {
|
||||||
|
if (!isHexString(originAsset)) return null;
|
||||||
|
const { wrapped_address } = await import("token-bridge");
|
||||||
|
// TODO: address conversion may be more complex than this
|
||||||
|
const originAssetBytes = zeroPad(
|
||||||
|
arrayify(originAsset, { hexPad: "left" }),
|
||||||
|
32
|
||||||
|
);
|
||||||
|
const wrappedAddress = wrapped_address(
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
originAssetBytes,
|
||||||
|
originChain
|
||||||
|
);
|
||||||
|
const wrappedAddressPK = new PublicKey(wrappedAddress);
|
||||||
|
// TODO: share connection in context?
|
||||||
|
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||||
|
const wrappedAssetAccountInfo = await connection.getAccountInfo(
|
||||||
|
wrappedAddressPK
|
||||||
|
);
|
||||||
|
console.log("WAAI", wrappedAssetAccountInfo);
|
||||||
|
return wrappedAssetAccountInfo ? wrappedAddressPK.toString() : null;
|
||||||
|
}
|
|
@ -1,12 +1,10 @@
|
||||||
import Wallet from "@project-serum/sol-wallet-adapter";
|
import Wallet from "@project-serum/sol-wallet-adapter";
|
||||||
import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
import {
|
import {
|
||||||
AccountMeta,
|
|
||||||
Connection,
|
Connection,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
SystemProgram,
|
SystemProgram,
|
||||||
Transaction,
|
Transaction,
|
||||||
TransactionInstruction,
|
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
import { ethers } from "ethers";
|
import { ethers } from "ethers";
|
||||||
import { arrayify, formatUnits, parseUnits, zeroPad } from "ethers/lib/utils";
|
import { arrayify, formatUnits, parseUnits, zeroPad } from "ethers/lib/utils";
|
||||||
|
@ -15,7 +13,7 @@ import {
|
||||||
Implementation__factory,
|
Implementation__factory,
|
||||||
TokenImplementation__factory,
|
TokenImplementation__factory,
|
||||||
} from "../ethers-contracts";
|
} from "../ethers-contracts";
|
||||||
import { getSignedVAA } from "../sdk";
|
import { getSignedVAA, ixFromRust } from "../sdk";
|
||||||
import {
|
import {
|
||||||
ChainId,
|
ChainId,
|
||||||
CHAIN_ID_ETH,
|
CHAIN_ID_ETH,
|
||||||
|
@ -32,6 +30,7 @@ import {
|
||||||
// TODO: overall better input checking and error handling
|
// TODO: overall better input checking and error handling
|
||||||
export function transferFromEth(
|
export function transferFromEth(
|
||||||
provider: ethers.providers.Web3Provider | undefined,
|
provider: ethers.providers.Web3Provider | undefined,
|
||||||
|
// TODO: specify signer
|
||||||
tokenAddress: string,
|
tokenAddress: string,
|
||||||
amount: string,
|
amount: string,
|
||||||
recipientChain: ChainId,
|
recipientChain: ChainId,
|
||||||
|
@ -100,26 +99,6 @@ export function transferFromEth(
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: should we share this with client? ooh, should client use the SDK ;)
|
|
||||||
// begin from clients\solana\main.ts
|
|
||||||
function ixFromRust(data: any): TransactionInstruction {
|
|
||||||
let keys: Array<AccountMeta> = data.accounts.map(accountMetaFromRust);
|
|
||||||
return new TransactionInstruction({
|
|
||||||
programId: new PublicKey(data.program_id),
|
|
||||||
data: Buffer.from(data.data),
|
|
||||||
keys: keys,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function accountMetaFromRust(meta: any): AccountMeta {
|
|
||||||
return {
|
|
||||||
pubkey: new PublicKey(meta.pubkey),
|
|
||||||
isSigner: meta.is_signer,
|
|
||||||
isWritable: meta.is_writable,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// end from clients\solana\main.ts
|
|
||||||
|
|
||||||
// TODO: need to check transfer native vs transfer wrapped
|
// TODO: need to check transfer native vs transfer wrapped
|
||||||
// TODO: switch out targetProvider for generic address (this likely involves getting these in their respective contexts)
|
// TODO: switch out targetProvider for generic address (this likely involves getting these in their respective contexts)
|
||||||
export function transferFromSolana(
|
export function transferFromSolana(
|
||||||
|
@ -166,6 +145,7 @@ export function transferFromSolana(
|
||||||
const bridgeStatePK = new PublicKey(
|
const bridgeStatePK = new PublicKey(
|
||||||
bridge.state_address(SOL_BRIDGE_ADDRESS)
|
bridge.state_address(SOL_BRIDGE_ADDRESS)
|
||||||
);
|
);
|
||||||
|
// TODO: share connection in context?
|
||||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||||
const bridgeStateAccountInfo = await connection.getAccountInfo(
|
const bridgeStateAccountInfo = await connection.getAccountInfo(
|
||||||
bridgeStatePK
|
bridgeStatePK
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { PublicKey } from "@solana/web3.js";
|
|
||||||
import { ethers } from "ethers";
|
|
||||||
import { arrayify, zeroPad } from "ethers/lib/utils";
|
|
||||||
import { Bridge__factory } from "../ethers-contracts";
|
|
||||||
import { ChainId, CHAIN_ID_SOLANA, ETH_TOKEN_BRIDGE_ADDRESS } from "./consts";
|
|
||||||
|
|
||||||
export function wrappedAssetEth(
|
|
||||||
provider: ethers.providers.Web3Provider,
|
|
||||||
originChain: ChainId,
|
|
||||||
originAsset: string
|
|
||||||
) {
|
|
||||||
const tokenBridge = Bridge__factory.connect(
|
|
||||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
|
||||||
provider
|
|
||||||
);
|
|
||||||
// TODO: address conversion may be more complex than this
|
|
||||||
const originAssetBytes = zeroPad(
|
|
||||||
originChain === CHAIN_ID_SOLANA
|
|
||||||
? new PublicKey(originAsset).toBytes()
|
|
||||||
: arrayify(originAsset),
|
|
||||||
32
|
|
||||||
);
|
|
||||||
return tokenBridge.wrappedAsset(originChain, originAssetBytes);
|
|
||||||
}
|
|
Loading…
Reference in New Issue