Merge remote-tracking branch 'origin/master'

This commit is contained in:
Leo 2020-10-15 13:39:10 +02:00
commit d7d76854c0
17 changed files with 2028 additions and 16223 deletions

View File

@ -167,6 +167,9 @@ uint256 amount
#### Transfer of assets Foreign Chain -> Root Chain
If this is the first time the asset is transferred to the root chain, the user inititates a `CreateWrapped` instruction
on the root chain to initialize the wrapped asset.
The user creates a token account for the wrapped asset on the root chain.
The user sends a chain native asset to the bridge on the foreign chain using the `Lock` function.

View File

@ -24,6 +24,20 @@ Pokes a `TransferOutProposal` so it is reprocessed by the guardians.
| ----- | ------ | ------------ | ------ | --------- | ----- | ------- |
| 0 | proposal | TransferOutProposal | | ✅ | | ✅ |
#### CreateWrappedAsset
Creates a new `WrappedAsset` to be used to create accounts and later receive transfers on chain.
| Index | Name | Type | signer | writeable | empty | derived |
| ----- | -------- | ------------------- | ------ | --------- | ----- | ------- |
| 0 | sys | SystemProgram | | | | |
| 1 | token_program | SplToken | | | | |
| 2 | rent | Sysvar | | | | ✅ |
| 3 | bridge | BridgeConfig | | | | |
| 4 | payer | Account | ✅ | | | |
| 5 | wrapped_mint | WrappedAsset | | | ✅ | ✅ |
| 6 | wrapped_meta_account | WrappedAssetMeta | | ✅ | ✅ | ✅ |
#### VerifySignatures
Checks secp checks (in the previous instruction) and stores results.

View File

@ -12,7 +12,7 @@ use solana_sdk::{
use crate::{
instruction::BridgeInstruction::{
Initialize, PokeProposal, PostVAA, TransferOut, VerifySignatures,
CreateWrapped, Initialize, PokeProposal, PostVAA, TransferOut, VerifySignatures,
},
state::{AssetMeta, Bridge, BridgeConfig},
vaa::{VAABody, VAA},
@ -139,6 +139,9 @@ pub enum BridgeInstruction {
/// Verifies signature instructions
VerifySignatures(VerifySigPayload),
/// Creates a new wrapped asset
CreateWrapped(AssetMeta),
}
impl BridgeInstruction {
@ -175,6 +178,11 @@ impl BridgeInstruction {
VerifySignatures(*payload)
}
7 => {
let payload: &AssetMeta = unpack(input)?;
CreateWrapped(*payload)
}
_ => return Err(ProgramError::InvalidInstructionData),
})
}
@ -239,6 +247,14 @@ impl BridgeInstruction {
};
*value = payload;
}
Self::CreateWrapped(payload) => {
output.resize(size_of::<AssetMeta>() + 1, 0);
output[0] = 7;
#[allow(clippy::cast_ptr_alignment)]
let value =
unsafe { &mut *(&mut output[size_of::<u8>()] as *mut u8 as *mut AssetMeta) };
*value = payload;
}
}
Ok(output)
}
@ -435,6 +451,7 @@ pub fn post_vaa(
program_id,
&bridge_key,
t.asset.chain,
t.asset.decimals,
t.asset.address,
)?;
let wrapped_meta_key =
@ -454,6 +471,41 @@ pub fn post_vaa(
})
}
/// Creates a 'CreateWrapped' instruction.
pub fn create_wrapped(
program_id: &Pubkey,
payer: &Pubkey,
meta: AssetMeta,
) -> Result<Instruction, ProgramError> {
let data = BridgeInstruction::CreateWrapped(meta).serialize()?;
let bridge_key = Bridge::derive_bridge_id(program_id)?;
let wrapped_mint_key = Bridge::derive_wrapped_asset_id(
program_id,
&bridge_key,
meta.chain,
meta.decimals,
meta.address,
)?;
let wrapped_meta_key =
Bridge::derive_wrapped_meta_id(program_id, &bridge_key, &wrapped_mint_key)?;
let accounts = vec![
AccountMeta::new_readonly(solana_sdk::system_program::id(), false),
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new(bridge_key, false),
AccountMeta::new(*payer, true),
AccountMeta::new(wrapped_mint_key, false),
AccountMeta::new(wrapped_meta_key, false),
];
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates an 'PokeProposal' instruction.
#[cfg(not(target_arch = "bpf"))]
pub fn poke_proposal(

View File

@ -90,6 +90,10 @@ impl Bridge {
Self::process_verify_signatures(program_id, accounts, &p)
}
CreateWrapped(meta) => {
info!("Instruction: CreateWrapped");
Self::process_create_wrapped(program_id, accounts, &meta)
}
_ => panic!(""),
}
}
@ -385,6 +389,7 @@ impl Bridge {
program_id,
bridge_info.key,
t.asset.chain,
t.asset.decimals,
t.asset.address,
)?;
if expected_mint_address != *mint_info.key {
@ -657,7 +662,6 @@ impl Bridge {
accounts,
account_info_iter,
bridge_info,
payer_info,
&mut bridge,
&v,
)
@ -755,7 +759,6 @@ impl Bridge {
accounts: &[AccountInfo],
account_info_iter: &mut Iter<AccountInfo>,
bridge_info: &AccountInfo,
payer_info: &AccountInfo,
bridge: &mut Bridge,
b: &BodyTransfer,
) -> ProgramResult {
@ -786,52 +789,16 @@ impl Bridge {
b.amount,
)?;
} else {
// Create wrapped asset if it does not exist
if mint_info.data_is_empty() {
let meta_info = next_account_info(account_info_iter)?;
// Foreign chain asset, mint wrapped asset
let expected_mint_address = Bridge::derive_wrapped_asset_id(
program_id,
bridge_info.key,
b.asset.chain,
b.asset.address,
)?;
if expected_mint_address != *mint_info.key {
return Err(Error::InvalidDerivedAccount.into());
}
// Create wrapped mint
Self::create_wrapped_mint(
program_id,
accounts,
&bridge.config.token_program,
mint_info.key,
bridge_info.key,
payer_info.key,
&b.asset,
b.asset.decimals,
)?;
// Check and create wrapped asset meta to allow reverse resolution of info
let wrapped_meta_seeds =
Bridge::derive_wrapped_meta_seeds(bridge_info.key, mint_info.key);
Bridge::check_and_create_account::<WrappedAssetMeta>(
program_id,
accounts,
meta_info.key,
payer_info.key,
program_id,
&wrapped_meta_seeds,
)?;
let mut wrapped_meta_data = meta_info.data.borrow_mut();
let wrapped_meta: &mut WrappedAssetMeta =
Bridge::unpack_unchecked(&mut wrapped_meta_data)?;
wrapped_meta.is_initialized = true;
wrapped_meta.address = b.asset.address;
wrapped_meta.chain = b.asset.chain;
// Foreign chain asset, mint wrapped asset
let expected_mint_address = Bridge::derive_wrapped_asset_id(
program_id,
bridge_info.key,
b.asset.chain,
b.asset.decimals,
b.asset.address,
)?;
if expected_mint_address != *mint_info.key {
return Err(Error::InvalidDerivedAccount.into());
}
// This automatically asserts that the mint was created by this account by using
@ -900,6 +867,69 @@ impl Bridge {
Ok(())
}
/// Creates a new wrapped asset
pub fn process_create_wrapped(
program_id: &Pubkey,
accounts: &[AccountInfo],
a: &AssetMeta,
) -> ProgramResult {
info!("create wrapped");
let account_info_iter = &mut accounts.iter();
next_account_info(account_info_iter)?; // System program
next_account_info(account_info_iter)?; // Token program
next_account_info(account_info_iter)?; // Rent sysvar
let bridge_info = next_account_info(account_info_iter)?;
let payer_info = next_account_info(account_info_iter)?;
let mint_info = next_account_info(account_info_iter)?;
let wrapped_meta_info = next_account_info(account_info_iter)?;
let bridge = Bridge::bridge_deserialize(bridge_info)?;
// Foreign chain asset, mint wrapped asset
let expected_mint_address = Bridge::derive_wrapped_asset_id(
program_id,
bridge_info.key,
a.chain,
a.decimals,
a.address,
)?;
if expected_mint_address != *mint_info.key {
return Err(Error::InvalidDerivedAccount.into());
}
// Create wrapped mint
Self::create_wrapped_mint(
program_id,
accounts,
&bridge.config.token_program,
mint_info.key,
bridge_info.key,
payer_info.key,
&a,
a.decimals,
)?;
// Check and create wrapped asset meta to allow reverse resolution of info
let wrapped_meta_seeds = Bridge::derive_wrapped_meta_seeds(bridge_info.key, mint_info.key);
Bridge::check_and_create_account::<WrappedAssetMeta>(
program_id,
accounts,
wrapped_meta_info.key,
payer_info.key,
program_id,
&wrapped_meta_seeds,
)?;
let mut wrapped_meta_data = wrapped_meta_info.data.borrow_mut();
let wrapped_meta: &mut WrappedAssetMeta = Bridge::unpack_unchecked(&mut wrapped_meta_data)?;
wrapped_meta.is_initialized = true;
wrapped_meta.address = a.address;
wrapped_meta.chain = a.chain;
Ok(())
}
}
/// Implementation of actions
@ -1030,7 +1060,7 @@ impl Bridge {
mint,
payer,
token_program,
&Self::derive_wrapped_asset_seeds(bridge, asset.chain, asset.address),
&Self::derive_wrapped_asset_seeds(bridge, asset.chain, asset.decimals, asset.address),
)?;
let ix = spl_token::instruction::initialize_mint(
token_program,

View File

@ -285,12 +285,14 @@ impl Bridge {
pub fn derive_wrapped_asset_seeds(
bridge_key: &Pubkey,
asset_chain: u8,
asset_decimal: u8,
asset: ForeignAddress,
) -> Vec<Vec<u8>> {
vec![
"wrapped".as_bytes().to_vec(),
bridge_key.to_bytes().to_vec(),
asset_chain.as_bytes().to_vec(),
asset_decimal.as_bytes().to_vec(),
asset.as_bytes().to_vec(),
]
}
@ -413,11 +415,12 @@ impl Bridge {
program_id: &Pubkey,
bridge_key: &Pubkey,
asset_chain: u8,
asset_decimal: u8,
asset: ForeignAddress,
) -> Result<Pubkey, Error> {
Ok(Self::derive_key(
program_id,
&Self::derive_wrapped_asset_seeds(bridge_key, asset_chain, asset),
&Self::derive_wrapped_asset_seeds(bridge_key, asset_chain, asset_decimal, asset),
)?
.0)
}

View File

@ -1024,12 +1024,21 @@ fn main() {
.required(true)
.help("Chain ID of the asset"),
)
.arg(
Arg::with_name("decimals")
.validator(is_u8)
.value_name("DECIMALS")
.takes_value(true)
.index(3)
.required(true)
.help("Decimals of the asset"),
)
.arg(
Arg::with_name("token")
.validator(is_hex)
.value_name("TOKEN_ADDRESS")
.takes_value(true)
.index(3)
.index(4)
.required(true)
.help("Token address of the asset"),
)
@ -1162,6 +1171,7 @@ fn main() {
("wrapped-address", Some(arg_matches)) => {
let bridge = pubkey_of(arg_matches, "bridge").unwrap();
let chain = value_t_or_exit!(arg_matches, "chain", u8);
let decimals = value_t_or_exit!(arg_matches, "decimals", u8);
let addr_string: String = value_of(arg_matches, "token").unwrap();
let addr_data = hex::decode(addr_string).unwrap();
@ -1170,7 +1180,8 @@ fn main() {
let bridge_key = Bridge::derive_bridge_id(&bridge).unwrap();
let wrapped_key =
Bridge::derive_wrapped_asset_id(&bridge, &bridge_key, chain, token_addr).unwrap();
Bridge::derive_wrapped_asset_id(&bridge, &bridge_key, chain, decimals, token_addr)
.unwrap();
println!("Wrapped address: {}", wrapped_key);
return;
}

View File

@ -42,7 +42,7 @@ echo "Created token account $account"
cli mint "$token" 10000000000 "$account"
# Do lock transactions <3
while : ; do
cli lock "$bridge_address" "$account" "$token" 10 "$chain_id_ethereum" "$RANDOM" "$recipient_address"
sleep 5
done
#while : ; do
# cli lock "$bridge_address" "$account" "$token" 10 "$chain_id_ethereum" "$RANDOM" "$recipient_address"
sleep 5000
#done

2006
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,35 +3,37 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@project-serum/sol-wallet-adapter": "^0.1.0",
"@solana/spl-token": "^0.0.5",
"@solana/web3.js": "^0.71.4",
"@project-serum/sol-wallet-adapter": "^0.1.1",
"@solana/spl-token": "^0.0.11",
"@solana/web3.js": "^0.80.2",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-router-dom": "^5.1.5",
"@types/bs58": "^4.0.1",
"antd": "^4.4.1",
"@types/jest": "^24.0.0",
"@types/lodash.debounce": "^4.0.6",
"@types/node": "^12.12.67",
"@types/react": "^16.9.52",
"@types/react-dom": "^16.9.0",
"@types/react-router-dom": "^5.1.6",
"antd": "^4.7.0",
"bs58": "^4.0.1",
"buffer": "^5.6.0",
"buffer-layout": "^1.2.0",
"ethers": "^4.0.44",
"ethers": "^4.0.48",
"lodash.debounce": "^4.0.8",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"react-scripts": "^3.4.3",
"typescript": "~3.7.2",
"web3": "^1.2.9",
"bs58": "^4.0.1"
"web3": "^1.3.0"
},
"devDependencies": {
"npm": "^6.14.6",
"yarn": "^1.22.4",
"typechain": "^2.0.0",
"@typechain/ethers-v4": "^1.0.0"
"@typechain/ethers-v4": "^1.0.0",
"npm": "^6.14.8",
"typechain": "^2.0.1",
"yarn": "^1.22.10"
},
"scripts": {
"start": "react-scripts start",

View File

@ -11,6 +11,7 @@ import TransferSolana from "../pages/TransferSolana";
import WalletContext from '../providers/WalletContext';
import Wallet from "@project-serum/sol-wallet-adapter";
import {BridgeProvider} from "../providers/BridgeContext";
import Assistant from "../pages/Assistant";
const {Header, Content, Footer} = Layout;
@ -37,6 +38,7 @@ function App() {
<Layout style={{height: '100%'}}>
<Router>
<Header style={{position: 'fixed', zIndex: 1, width: '100%'}}>
<Link to="/assistant" style={{paddingRight: 20}}>Assistant</Link>
<Link to="/" style={{paddingRight: 20}}>Ethereum</Link>
<Link to="/solana">Solana</Link>
<div className="logo"/>
@ -50,6 +52,9 @@ function App() {
<SolanaTokenProvider>
<Switch>
<Route path="/assistant">
<Assistant/>
</Route>
<Route path="/solana">
<TransferSolana/>
</Route>

View File

@ -0,0 +1,304 @@
import React, {useContext, useEffect, useState} from "react";
import {Button, Empty, Form, Input, message, Modal, Select} from "antd";
import solanaWeb3, {Account, Connection, PublicKey, Transaction} from "@solana/web3.js";
import ClientContext from "../providers/ClientContext";
import {SlotContext} from "../providers/SlotContext";
import {SolanaTokenContext} from "../providers/SolanaTokenContext";
import {BridgeContext} from "../providers/BridgeContext";
import {WrappedAssetFactory} from "../contracts/WrappedAssetFactory";
import {WalletOutlined} from '@ant-design/icons';
import {BRIDGE_ADDRESS} from "../config";
import {WormholeFactory} from "../contracts/WormholeFactory";
import {ethers} from "ethers";
import debounce from "lodash.debounce"
import BN from "bignumber.js";
import {BigNumber} from "ethers/utils";
import {AssetMeta, SolanaBridge} from "../utils/bridge";
import KeyContext from "../providers/KeyContext";
import {ChainID} from "../pages/Assistant";
const {confirm} = Modal;
const {Option} = Select;
interface TransferInitiatorParams {
onFromNetworkChanged?: (v: ChainID) => void
dataChanged?: (d: TransferInitiatorData) => void
}
export interface CoinInfo {
address: string,
name: string,
balance: BigNumber,
decimals: number,
allowance: BigNumber,
isWrapped: boolean,
chainID: number,
assetAddress: Buffer,
mint: string,
}
export interface TransferInitiatorData {
fromNetwork: ChainID,
fromCoinInfo: CoinInfo
toNetwork: ChainID,
toAddress: Buffer,
amount: BigNumber,
}
// @ts-ignore
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
export const defaultCoinInfo = {
address: "",
name: "",
balance: new BigNumber(0),
decimals: 0,
allowance: new BigNumber(0),
isWrapped: false,
chainID: 0,
assetAddress: new Buffer(0),
mint: ""
}
let debounceUpdater = debounce((e) => e(), 500)
async function createWrapped(c: Connection, b: SolanaBridge, key: Account, meta: AssetMeta, mint: PublicKey) {
try {
let tx = new Transaction();
// @ts-ignore
let [ix_account, newSigner] = await b.createWrappedAssetAndAccountInstructions(key.publicKey, mint, meta);
let recentHash = await c.getRecentBlockhash();
tx.recentBlockhash = recentHash.blockhash
tx.add(...ix_account)
tx.sign(key, newSigner)
message.loading({content: "Waiting for transaction to be confirmed...", key: "tx", duration: 1000})
await c.sendTransaction(tx, [key, newSigner], {preflightCommitment: "single"})
message.success({content: "Creation succeeded!", key: "tx"})
} catch (e) {
console.log(e)
message.error({content: "Creation failed", key: "tx"})
}
}
export default function TransferInitiator(params: TransferInitiatorParams) {
let c = useContext<solanaWeb3.Connection>(ClientContext);
let slot = useContext(SlotContext);
let b = useContext(SolanaTokenContext);
let bridge = useContext(BridgeContext);
let k = useContext(KeyContext);
let [fromNetwork, setFromNetwork] = useState(ChainID.ETH);
let [toNetwork, setToNetwork] = useState(ChainID.SOLANA);
let [fromAddress, setFromAddress] = useState("");
let [fromAddressValid, setFromAddressValid] = useState(false)
let [coinInfo, setCoinInfo] = useState<CoinInfo>(defaultCoinInfo);
let [toAddress, setToAddress] = useState("");
let [toAddressValid, setToAddressValid] = useState(false)
let [amount, setAmount] = useState(new BigNumber(0));
let [amountValid, setAmountValid] = useState(true);
let [wrappedMint, setWrappedMint] = useState("")
const updateBalance = async () => {
if (fromNetwork == ChainID.SOLANA) {
let acc = b.balances.find(value => value.account.toString() == fromAddress)
if (!acc) {
setFromAddressValid(false);
setCoinInfo(defaultCoinInfo);
return
}
console.log(acc.assetMeta)
setCoinInfo({
address: fromAddress,
name: "",
balance: acc.balance,
allowance: new BigNumber(0),
decimals: acc.assetMeta.decimals,
isWrapped: acc.assetMeta.chain != ChainID.SOLANA,
chainID: acc.assetMeta.chain,
assetAddress: acc.assetMeta.address,
// Solana specific
mint: acc.mint,
})
setFromAddressValid(true);
} else {
try {
let e = WrappedAssetFactory.connect(fromAddress, provider);
let addr = await signer.getAddress();
let balance = await e.balanceOf(addr);
let decimals = await e.decimals();
let symbol = await e.symbol();
let allowance = await e.allowance(addr, BRIDGE_ADDRESS);
let info = {
address: fromAddress,
name: symbol,
balance: balance,
allowance: allowance,
decimals: decimals,
isWrapped: false,
chainID: 2,
assetAddress: new Buffer(fromAddress.slice(2), "hex"),
mint: "",
}
let b = WormholeFactory.connect(BRIDGE_ADDRESS, provider);
let isWrapped = await b.isWrappedAsset(fromAddress)
if (isWrapped) {
info.chainID = await e.assetChain()
info.assetAddress = new Buffer((await e.assetAddress()).slice(2), "hex")
info.isWrapped = true
}
let wrappedMint = await bridge.getWrappedAssetMint({
chain: info.chainID,
address: info.assetAddress,
decimals: Math.min(decimals, 9),
});
console.log(decimals)
setWrappedMint(wrappedMint.toString())
setCoinInfo(info)
setFromAddressValid(true)
} catch (e) {
setCoinInfo(defaultCoinInfo);
setFromAddressValid(false)
}
}
}
useEffect(() => {
debounceUpdater(updateBalance)
}, [fromNetwork, fromAddress])
useEffect(() => {
if (toNetwork == ChainID.ETH) {
setToAddressValid(toAddress.length == 42 && toAddress.match(/0[xX][0-9a-fA-F]+/) != null)
} else {
setToAddressValid(toAddress != "")
}
}, [toNetwork, toAddress])
useEffect(() => {
setAmountValid(amount.lte(coinInfo.balance) && amount.gt(0))
}, [amount])
useEffect(() => {
if (params.dataChanged) {
params.dataChanged({
fromCoinInfo: coinInfo,
fromNetwork,
toNetwork,
toAddress: toAddressValid ? (toNetwork == ChainID.ETH ? new Buffer(toAddress.slice(2), "hex") : new PublicKey(toAddress).toBuffer()) : new Buffer(0),
amount: amount,
});
}
}, [fromNetwork, fromAddressValid, coinInfo, toNetwork, toAddress, toAddressValid, amount])
return (
<>
<Form layout={"vertical"}>
<Form.Item label="From" name="layout" validateStatus={fromAddressValid ? "success" : "error"}>
<Input.Group compact={true}>
<Select style={{width: '30%'}} defaultValue={ChainID.ETH} className="select-before"
value={fromNetwork}
onChange={(v) => {
setFromNetwork(v);
setFromAddress("");
if (v === toNetwork) {
setToNetwork(v == ChainID.ETH ? ChainID.SOLANA : ChainID.ETH);
}
if (params.onFromNetworkChanged) params.onFromNetworkChanged(v);
}}>
<Option value={ChainID.ETH}>Ethereum</Option>
<Option value={ChainID.SOLANA}>Solana</Option>
</Select>
{fromNetwork == ChainID.ETH &&
<Input style={{width: '70%'}} placeholder="ERC20 address"
onChange={(e) => setFromAddress(e.target.value)}
suffix={coinInfo.name}/>}
{fromNetwork == ChainID.SOLANA &&
<>
<Select style={{width: '70%'}} placeholder="Pick a token account"
onChange={(e) => {
setFromAddress(e.toString())
}}>
{b.balances.map((v) => <Option
value={v.account.toString()}>{v.account.toString()}</Option>)}
</Select>
</>
}
</Input.Group>
</Form.Item>
<Form.Item label="Amount" name="layout"
validateStatus={amountValid ? "success" : "error"}>
<Input type={"number"} placeholder={"Amount"}
addonAfter={`Balance: ${coinInfo.balance.div(Math.pow(10, coinInfo.decimals))}`}
onChange={(v) => {
if (v.target.value === "") {
setAmount(new BigNumber(0));
return
}
setAmount(new BigNumber(new BN(v.target.value).multipliedBy(new BN(Math.pow(10, coinInfo.decimals))).toFixed(0)))
}}/>
</Form.Item>
<Form.Item label="Recipient" name="layout" validateStatus={toAddressValid ? "success" : "error"}>
<Input.Group compact={true}>
<Select style={{width: '30%'}} defaultValue={ChainID.SOLANA} className="select-before"
value={toNetwork}
onChange={(v) => {
setToNetwork(v)
if (v === fromNetwork) {
setFromNetwork(v == ChainID.ETH ? ChainID.SOLANA : ChainID.ETH);
}
setToAddress("");
}}>
<Option value={ChainID.ETH}>Ethereum</Option>
<Option value={ChainID.SOLANA}>Solana</Option>
</Select>
{toNetwork == ChainID.ETH &&
<Input style={{width: '70%'}} placeholder="Account address"
onChange={(e) => setToAddress(e.target.value)}/>}
{toNetwork == ChainID.SOLANA &&
<>
<Select style={{width: '60%'}} onChange={(e) => setToAddress(e.toString())}
placeholder="Pick a token account or create a new one"
notFoundContent={<Empty description="No accounts. Create a new one."/>}>
{b.balances.filter((v) => v.mint == wrappedMint).map((v) =>
<Option
value={v.account.toString()}>{v.account.toString()}</Option>)}
</Select>
<Button style={{width: '10%'}} disabled={!fromAddressValid} onClick={() => {
confirm({
title: 'Do you want to create a new token account?',
icon: <WalletOutlined/>,
content: (<>This will create a new token account for the
token: <code>{wrappedMint}</code></>),
onOk() {
console.log(coinInfo.decimals)
createWrapped(c, bridge, k, {
chain: coinInfo.chainID,
address: coinInfo.assetAddress,
decimals: Math.min(coinInfo.decimals, 9)
}, new PublicKey(wrappedMint))
},
onCancel() {
console.log('Cancel');
},
})
}}>+</Button>
</>
}
</Input.Group>
</Form.Item>
</Form>
</>
);
}

412
web/src/pages/Assistant.tsx Normal file
View File

@ -0,0 +1,412 @@
import React, {useContext, useState} from 'react';
import ClientContext from "../providers/ClientContext";
import * as solanaWeb3 from '@solana/web3.js';
import {PublicKey, Transaction} from '@solana/web3.js';
import {Button, message, Progress, Space, Spin, Steps} from "antd";
import {ethers} from "ethers";
import {Erc20Factory} from "../contracts/Erc20Factory";
import {Arrayish, BigNumber, BigNumberish} from "ethers/utils";
import {WormholeFactory} from "../contracts/WormholeFactory";
import {BRIDGE_ADDRESS, TOKEN_PROGRAM} from "../config";
import {SolanaTokenContext} from "../providers/SolanaTokenContext";
import {BridgeContext} from "../providers/BridgeContext";
import KeyContext from "../providers/KeyContext";
import TransferInitiator, {defaultCoinInfo, TransferInitiatorData} from "../components/TransferInitiator";
import * as spl from "@solana/spl-token";
import BN from "bn.js"
import {SlotContext} from "../providers/SlotContext";
const {Step} = Steps;
// @ts-ignore
window.ethereum.enable();
// @ts-ignore
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
interface LoadingInfo {
loading: boolean,
message: string,
progress?: ProgressInfo,
}
interface ProgressInfo {
completion: number,
content: string
}
export enum ChainID {
SOLANA = 1,
ETH
}
async function approveAssets(asset: string,
amount: BigNumberish) {
let e = Erc20Factory.connect(asset, signer);
try {
message.loading({content: "Signing transaction...", key: "eth_tx", duration: 1000})
let res = await e.approve(BRIDGE_ADDRESS, amount)
message.loading({content: "Waiting for transaction to be mined...", key: "eth_tx", duration: 1000})
await res.wait(1);
message.success({content: "Approval on ETH succeeded!", key: "eth_tx"})
} catch (e) {
message.error({content: "Approval failed", key: "eth_tx"})
}
}
function Assistant() {
let c = useContext<solanaWeb3.Connection>(ClientContext);
let tokenAccounts = useContext(SolanaTokenContext);
let bridge = useContext(BridgeContext);
let k = useContext(KeyContext);
let slot = useContext(SlotContext);
let [fromNetwork, setFromNetwork] = useState(ChainID.ETH)
let [transferData, setTransferData] = useState<TransferInitiatorData>({
fromCoinInfo: defaultCoinInfo,
fromNetwork: 0,
toNetwork: 0,
toAddress: new Buffer(0),
amount: new BigNumber(0),
});
let [loading, setLoading] = useState<LoadingInfo>({
loading: false,
message: "",
progress: undefined
})
let [current, setCurrent] = useState(0);
let nextStep = (from: string) => {
setLoading({
...loading,
loading: false
})
if (from == "approve") {
lockAssets(transferData.fromCoinInfo?.address, transferData.amount, transferData.toAddress, transferData.toNetwork)
} else if (from == "lock") {
// Await approvals or allow to submit guardian shit
if (fromNetwork == ChainID.ETH && transferData.toNetwork == ChainID.SOLANA) {
awaitCompletionEth()
} else if (fromNetwork == ChainID.SOLANA && transferData.toNetwork == ChainID.ETH) {
awaitCompletionSolana()
}
} else if (from == "vaa") {
postVAAOnEth()
}
setCurrent((v) => v + 1)
}
const lockAssets = async function (asset: string,
amount: BigNumberish,
recipient: Arrayish,
target_chain: BigNumberish) {
let wh = WormholeFactory.connect(BRIDGE_ADDRESS, signer);
try {
setLoading({
...loading,
loading: true,
message: "Allow transfer in Metamask...",
})
let res = await wh.lockAssets(asset, amount, recipient, target_chain, 10, false)
setLoading({
...loading,
loading: true,
message: "Waiting for transaction to be mined...",
})
await res.wait(1);
message.success({content: "Transfer on ETH succeeded!", key: "eth_tx"})
nextStep("lock");
} catch (e) {
message.error({content: "Transfer failed", key: "eth_tx"})
setCurrent(0);
setLoading({
...loading,
loading: false,
})
}
}
const approveAssets = async function (asset: string,
amount: BigNumberish) {
let e = Erc20Factory.connect(asset, signer);
try {
setLoading({
...loading,
loading: true,
message: "Allow approval in Metamask...",
})
let res = await e.approve(BRIDGE_ADDRESS, amount)
setLoading({
...loading,
loading: true,
message: "Waiting for transaction to be mined...",
})
await res.wait(1);
message.success({content: "Approval on ETH succeeded!", key: "eth_tx"})
nextStep("approve")
} catch (e) {
message.error({content: "Approval failed", key: "eth_tx"})
setCurrent(0);
setLoading({
loading: false,
...loading
})
}
}
const initiateTransfer = () => {
if (fromNetwork == ChainID.ETH && transferData.fromCoinInfo) {
nextStep("init")
if (transferData.fromCoinInfo?.allowance.lt(transferData.amount)) {
approveAssets(transferData.fromCoinInfo?.address, transferData.amount)
} else {
lockAssets(transferData.fromCoinInfo?.address, transferData.amount, transferData.toAddress, transferData.toNetwork)
}
} else if (fromNetwork == ChainID.SOLANA && transferData.fromCoinInfo) {
nextStep("init")
solanaTransfer();
}
}
let transferProposal: PublicKey;
let transferVAA = new Uint8Array(0);
const solanaTransfer = async () => {
setLoading({
...loading,
loading: true,
message: "Locking tokens on Solana...",
})
let {ix: lock_ix, transferKey} = await bridge.createLockAssetInstruction(k.publicKey, new PublicKey(transferData.fromCoinInfo.address),
new PublicKey(transferData.fromCoinInfo.mint), new BN(transferData.amount.toString()),
transferData.toNetwork, transferData.toAddress,
{
chain: transferData.fromCoinInfo.chainID,
address: transferData.fromCoinInfo.assetAddress,
decimals: transferData.fromCoinInfo.decimals,
}, Math.random() * 100000);
let ix = spl.Token.createApproveInstruction(TOKEN_PROGRAM, new PublicKey(transferData.fromCoinInfo.address), await bridge.getConfigKey(), k.publicKey, [], transferData.amount.toNumber())
let recentHash = await c.getRecentBlockhash();
let tx = new Transaction();
tx.recentBlockhash = recentHash.blockhash
tx.add(ix)
tx.add(lock_ix)
tx.sign(k)
try {
await c.sendTransaction(tx, [k])
message.success({content: "Transfer succeeded", key: "transfer"})
} catch (e) {
message.error({content: "Transfer failed", key: "transfer"})
}
transferProposal = transferKey
nextStep("lock")
}
const executeVAAOnETH = async (vaa: Uint8Array) => {
let wh = WormholeFactory.connect(BRIDGE_ADDRESS, signer)
setLoading({
...loading,
loading: true,
message: "Sign the claim...",
})
let tx = await wh.submitVAA(vaa)
setLoading({
...loading,
loading: true,
message: "Waiting for tokens unlock to be mined...",
})
await tx.wait(1)
message.success({content: "Execution of VAA succeeded", key: "eth_tx"})
nextStep("submit")
}
const awaitCompletionEth = () => {
let startBlock = provider.blockNumber;
let completed = false;
let blockHandler = (blockNumber: number) => {
if (blockNumber - startBlock < 5) {
setLoading({
loading: true,
message: "Awaiting ETH confirmations",
progress: {
completion: (blockNumber - startBlock) / 5 * 100,
content: `${blockNumber - startBlock}/${5}`
}
})
} else if (!completed) {
provider.removeListener("block", blockHandler)
setLoading({loading: true, message: "Awaiting completion on Solana"})
}
}
provider.on("block", blockHandler)
let accountChangeListener = c.onAccountChange(new PublicKey(transferData.toAddress), () => {
if (completed) return;
completed = true;
provider.removeListener("block", blockHandler)
c.removeAccountChangeListener(accountChangeListener);
nextStep("await")
}, "single")
}
const awaitCompletionSolana = () => {
let completed = false;
let startSlot = slot;
let slotUpdateListener = c.onSlotChange((slot) => {
if (completed) return;
if (slot.slot - startSlot < 32) {
setLoading({
loading: true,
message: "Awaiting confirmations",
progress: {
completion: (slot.slot - startSlot) / 32 * 100,
content: `${slot.slot - startSlot}/${32}`
}
})
} else {
setLoading({loading: true, message: "Awaiting guardians (TODO ping)"})
}
})
let accountChangeListener = c.onAccountChange(transferProposal, async (a) => {
if (completed) return;
let lockup = bridge.parseLockup(transferProposal, a.data);
let vaa = lockup.vaa;
console.log(lockup)
for (let i = vaa.length; i > 0; i--) {
if (vaa[i] == 0xff) {
vaa = vaa.slice(0, i)
break
}
}
// Probably a poke
if (vaa.filter(v => v != 0).length == 0) {
return
}
completed = true;
c.removeAccountChangeListener(accountChangeListener);
c.removeSlotChangeListener(slotUpdateListener);
let signatures = await bridge.fetchSignatureStatus(lockup.signatureAccount);
let sigData = Buffer.of(...signatures.reduce((previousValue, currentValue) => {
previousValue.push(currentValue.index)
previousValue.push(...currentValue.signature)
return previousValue
}, new Array<number>()))
vaa = Buffer.concat([vaa.slice(0, 5), Buffer.of(signatures.length), sigData, vaa.slice(6)])
transferVAA = vaa
nextStep("vaa")
}, "single")
}
const postVAAOnEth = () => {
executeVAAOnETH(transferVAA);
}
const steps = [
{
title: 'Initiate Transfer',
content: (
<>
<TransferInitiator onFromNetworkChanged={setFromNetwork} dataChanged={(d) => {
setTransferData(d);
}}/>
<Button onClick={initiateTransfer}>Transfer</Button>
</>),
},
];
if (fromNetwork == ChainID.ETH) {
if (transferData.fromCoinInfo && transferData.fromCoinInfo.allowance.lt(transferData.amount)) {
steps.push({
title: 'Approval',
content: (<></>),
})
}
steps.push(...[
{
title: 'Transfer',
content: (<></>),
},
{
title: 'Wait for confirmations',
content: (<></>),
},
{
title: 'Done',
content: (<><Space align="center" style={{width: "100%", paddingTop: "128px", paddingBottom: "128px"}}
direction="vertical">
<Progress type="circle" percent={100} format={() => 'Done'}/>
<b>Your transfer has been completed</b>
</Space></>),
},
])
} else {
steps.push(...[
{
title: 'Transfer',
content: (<></>),
},
{
title: 'Wait for approval',
content: (<></>),
},
{
title: 'Claim tokens on ETH',
content: (<></>),
},
{
title: 'Done',
content: (<><Space align="center" style={{width: "100%", paddingTop: "128px", paddingBottom: "128px"}}
direction="vertical">
<Progress type="circle" percent={100} format={() => 'Done'}/>
<b>Your transfer has been completed</b>
</Space></>),
},
])
}
return (
<>
<Steps current={current}>
{steps.map(item => (
<Step key={item.title} title={item.title}/>
))}
</Steps>
<div className="steps-content"
style={{marginTop: "24px", marginBottom: "24px"}}>
{loading.loading ? loading.progress ? (
<Space align="center" style={{width: "100%", paddingTop: "128px", paddingBottom: "128px"}}
direction="vertical">
<ProgressIndicator {...loading.progress}/>
<b>{loading.message}</b>
</Space>) :
<Space align="center" style={{width: "100%", paddingTop: "128px", paddingBottom: "128px"}}
direction="vertical">
<Spin size={"large"}/>
<b>{loading.message}</b>
</Space> : steps[current].content}
</div>
</>
);
}
let ProgressIndicator = (params: { completion: number, content: string }) => {
return (<Progress type="circle" percent={params.completion} format={() => params.content}/>)
}
export default Assistant;

View File

@ -60,7 +60,7 @@ async function createWrapped(c: Connection, b: SolanaBridge, key: Account, meta:
let tx = new Transaction();
// @ts-ignore
let [ix_account, newSigner] = await b.createWrappedAssetAndAccountInstructions(key.publicKey, mint);
let [ix_account, newSigner] = await b.createWrappedAssetAndAccountInstructions(key.publicKey, mint, meta);
let recentHash = await c.getRecentBlockhash();
tx.recentBlockhash = recentHash.blockhash
tx.add(...ix_account)
@ -69,6 +69,7 @@ async function createWrapped(c: Connection, b: SolanaBridge, key: Account, meta:
await c.sendTransaction(tx, [key, newSigner])
message.success({content: "Creation succeeded!", key: "tx"})
} catch (e) {
console.log(e)
message.error({content: "Creation failed", key: "tx"})
}
}
@ -152,7 +153,8 @@ function Transfer() {
let getWrappedInfo = async () => {
let wrappedMint = await bridge.getWrappedAssetMint({
chain: coinInfo.chainID,
address: coinInfo.assetAddress
address: coinInfo.assetAddress,
decimals: Math.min(coinInfo.decimals, 9)
});
setWrappedMint(wrappedMint.toString())
@ -241,7 +243,8 @@ function Transfer() {
onClick={() => {
createWrapped(c, bridge, k, {
chain: coinInfo.chainID,
address: coinInfo.assetAddress
address: coinInfo.assetAddress,
decimals: Math.min(coinInfo.decimals, 9)
}, new PublicKey(wrappedMint))
}}>Create new</Button></Col>
</Row>

View File

@ -3,7 +3,7 @@ import ClientContext from "../providers/ClientContext";
import * as solanaWeb3 from '@solana/web3.js';
import {PublicKey, Transaction} from '@solana/web3.js';
import * as spl from '@solana/spl-token';
import {Button, Card, Col, Divider, Form, Input, InputNumber, List, message, Row, Select} from "antd";
import {Button, Col, Form, Input, InputNumber, message, Row, Select} from "antd";
import {BigNumber} from "ethers/utils";
import SplBalances from "../components/SplBalances";
import {SlotContext} from "../providers/SlotContext";
@ -56,6 +56,7 @@ function TransferSolana() {
getCoinInfo()
}, [address])
return (
<>
<Row gutter={12}>
@ -70,11 +71,12 @@ function TransferSolana() {
let send = async () => {
message.loading({content: "Transferring tokens...", key: "transfer"}, 1000)
let lock_ix = await bridge.createLockAssetInstruction(k.publicKey, fromAccount, new PublicKey(coinInfo.mint), transferAmount, values["target_chain"], recipient,
let {ix: lock_ix} = await bridge.createLockAssetInstruction(k.publicKey, fromAccount, new PublicKey(coinInfo.mint), transferAmount, values["target_chain"], recipient,
{
chain: coinInfo.chainID,
address: coinInfo.wrappedAddress
}, Math.random()*100000);
address: coinInfo.wrappedAddress,
decimals: Math.min(coinInfo.decimals, 9)
}, Math.random() * 100000);
let ix = spl.Token.createApproveInstruction(TOKEN_PROGRAM, fromAccount, await bridge.getConfigKey(), k.publicKey, [], transferAmount.toNumber())
let recentHash = await c.getRecentBlockhash();
@ -91,8 +93,9 @@ function TransferSolana() {
}
}
send()
}} layout={"vertical"} >
<Form.Item name="address" validateStatus={addressValid ? "success" : "error"} label={"Token Account:"}>
}} layout={"vertical"}>
<Form.Item name="address" validateStatus={addressValid ? "success" : "error"}
label={"Token Account:"}>
<Input
addonAfter={`Balance: ${coinInfo.balance.div(new BigNumber(Math.pow(10, coinInfo.decimals)))}`}
name="address"

View File

@ -48,6 +48,7 @@ export const SolanaTokenProvider: FunctionComponent = ({children}) => {
if (!am) {
throw new Error("could not derive asset meta")
}
am.decimals = acc.account.data.parsed.info.tokenAmount.decimals;
meta.push(am)
}
let balances: Array<BalanceInfo> = await res.value.map((v, i) => {

View File

@ -2,14 +2,16 @@ import * as solanaWeb3 from "@solana/web3.js";
import {PublicKey, TransactionInstruction} from "@solana/web3.js";
import BN from 'bn.js';
import assert from "assert";
import * as spl from '@solana/spl-token';
import {Token} from '@solana/spl-token';
// @ts-ignore
import * as BufferLayout from 'buffer-layout'
import {Token} from "@solana/spl-token";
import {SOLANA_HOST, TOKEN_PROGRAM} from "../config";
import {SOLANA_BRIDGE_PROGRAM, SOLANA_HOST, TOKEN_PROGRAM} from "../config";
import * as bs58 from "bs58";
export interface AssetMeta {
chain: number,
decimals: number,
address: Buffer
}
@ -57,7 +59,7 @@ class SolanaBridge {
targetAddress: Buffer,
asset: AssetMeta,
nonce: number,
): Promise<TransactionInstruction> {
): Promise<{ ix: TransactionInstruction, transferKey: PublicKey }> {
const dataLayout = BufferLayout.struct([
BufferLayout.u8('instruction'),
uint256('amount'),
@ -90,7 +92,7 @@ class SolanaBridge {
targetChain: targetChain,
assetAddress: padBuffer(asset.address, 32),
assetChain: asset.chain,
assetDecimals: 0, // This is fetched on chain
assetDecimals: asset.decimals,
targetAddress: padBuffer(targetAddress, 32),
nonce: nonce,
},
@ -107,7 +109,7 @@ class SolanaBridge {
{pubkey: configKey, isSigner: false, isWritable: false},
{pubkey: transferKey, isSigner: false, isWritable: true},
{pubkey: mint, isSigner: false, isWritable: false},
{pubkey: mint, isSigner: false, isWritable: true},
{pubkey: payer, isSigner: true, isWritable: true},
];
@ -118,11 +120,14 @@ class SolanaBridge {
}
return new TransactionInstruction({
keys,
programId: this.programID,
data,
});
return {
ix: new TransactionInstruction({
keys,
programId: this.programID,
data,
}),
transferKey: transferKey,
};
}
createPokeProposalInstruction(
@ -163,6 +168,7 @@ class SolanaBridge {
return {
address: mint.toBuffer(),
chain: CHAIN_ID_SOLANA,
decimals: 0,
}
} else {
const dataLayout = BufferLayout.struct([
@ -173,7 +179,8 @@ class SolanaBridge {
return {
address: wrappedMeta.assetAddress,
chain: wrappedMeta.assetChain
chain: wrappedMeta.assetChain,
decimals: 0
}
}
}
@ -182,8 +189,7 @@ class SolanaBridge {
async fetchSignatureStatus(
signatureStatus: PublicKey,
): Promise<Signature[]> {
let signatureInfo = await this.connection.getAccountInfo(signatureStatus);
console.log(signatureStatus.toBase58())
let signatureInfo = await this.connection.getAccountInfo(signatureStatus, "single");
if (signatureInfo == null || signatureInfo.lamports == 0) {
throw new Error("not found")
} else {
@ -214,6 +220,46 @@ class SolanaBridge {
}
}
parseLockup(address: PublicKey, data: Buffer): Lockup {
const dataLayout = BufferLayout.struct([
uint256('amount'),
BufferLayout.u8('toChain'),
BufferLayout.blob(32, 'sourceAddress'),
BufferLayout.blob(32, 'targetAddress'),
BufferLayout.blob(32, 'assetAddress'),
BufferLayout.u8('assetChain'),
BufferLayout.u8('assetDecimals'),
BufferLayout.seq(BufferLayout.u8(), 1), // 4 byte alignment because a u32 is following
BufferLayout.u32('nonce'),
BufferLayout.blob(1001, 'vaa'),
BufferLayout.seq(BufferLayout.u8(), 3), // 4 byte alignment because a u32 is following
BufferLayout.u32('vaaTime'),
BufferLayout.u32('lockupTime'),
BufferLayout.u8('pokeCounter'),
BufferLayout.blob(32, 'signatureAccount'),
BufferLayout.u8('initialized'),
]);
let parsedAccount = dataLayout.decode(data)
return {
lockupAddress: address,
amount: new BN(parsedAccount.amount, 2, "le"),
assetAddress: parsedAccount.assetAddress,
assetChain: parsedAccount.assetChain,
assetDecimals: parsedAccount.assetDecimals,
initialized: parsedAccount.initialized == 1,
nonce: parsedAccount.nonce,
sourceAddress: new PublicKey(parsedAccount.sourceAddress),
targetAddress: parsedAccount.targetAddress,
toChain: parsedAccount.toChain,
vaa: parsedAccount.vaa,
vaaTime: parsedAccount.vaaTime,
signatureAccount: new PublicKey(parsedAccount.signatureAccount),
pokeCounter: parsedAccount.pokeCounter
}
}
// fetchAssetMeta fetches the AssetMeta for an SPL token
async fetchTransferProposals(
tokenAccount: PublicKey,
@ -240,44 +286,9 @@ class SolanaBridge {
})
let raw_accounts = (await accountRes.json())["result"];
const dataLayout = BufferLayout.struct([
uint256('amount'),
BufferLayout.u8('toChain'),
BufferLayout.blob(32, 'sourceAddress'),
BufferLayout.blob(32, 'targetAddress'),
BufferLayout.blob(32, 'assetAddress'),
BufferLayout.u8('assetChain'),
BufferLayout.u8('assetDecimals'),
BufferLayout.seq(BufferLayout.u8(), 1), // 4 byte alignment because a u32 is following
BufferLayout.u32('nonce'),
BufferLayout.blob(1001, 'vaa'),
BufferLayout.seq(BufferLayout.u8(), 3), // 4 byte alignment because a u32 is following
BufferLayout.u32('vaaTime'),
BufferLayout.u32('lockupTime'),
BufferLayout.u8('pokeCounter'),
BufferLayout.blob(32, 'signatureAccount'),
BufferLayout.u8('initialized'),
]);
let accounts: Lockup[] = [];
for (let acc of raw_accounts) {
let parsedAccount = dataLayout.decode(bs58.decode(acc.account.data))
accounts.push({
lockupAddress: acc.pubkey,
amount: new BN(parsedAccount.amount, 2, "le"),
assetAddress: parsedAccount.assetAddress,
assetChain: parsedAccount.assetChain,
assetDecimals: parsedAccount.assetDecimals,
initialized: parsedAccount.initialized == 1,
nonce: parsedAccount.nonce,
sourceAddress: new PublicKey(parsedAccount.sourceAddress),
targetAddress: parsedAccount.targetAddress,
toChain: parsedAccount.toChain,
vaa: parsedAccount.vaa,
vaaTime: parsedAccount.vaaTime,
signatureAccount: new PublicKey(parsedAccount.signatureAccount),
pokeCounter: parsedAccount.pokeCounter
})
accounts.push(this.parseLockup(acc.pubkey, bs58.decode(acc.account.data)))
}
return accounts
@ -285,16 +296,16 @@ class SolanaBridge {
AccountLayout = BufferLayout.struct([publicKey('mint'), publicKey('owner'), uint64('amount'), BufferLayout.u32('option'), publicKey('delegate'), BufferLayout.u8('is_initialized'), BufferLayout.u8('is_native'), BufferLayout.u16('padding'), uint64('delegatedAmount')]);
async createWrappedAssetAndAccountInstructions(owner: PublicKey, mint: PublicKey): Promise<[TransactionInstruction[], solanaWeb3.Account]> {
async createWrappedAssetAndAccountInstructions(owner: PublicKey, mint: PublicKey, meta: AssetMeta): Promise<[TransactionInstruction[], solanaWeb3.Account]> {
const newAccount = new solanaWeb3.Account();
// @ts-ignore
const balanceNeeded = await Token.getMinBalanceRentForExemptAccount(this.connection);
let transaction = solanaWeb3.SystemProgram.createAccount({
let create_ix = solanaWeb3.SystemProgram.createAccount({
fromPubkey: owner,
newAccountPubkey: newAccount.publicKey,
lamports: balanceNeeded,
space: this.AccountLayout.span,
space: spl.AccountLayout.span,
programId: TOKEN_PROGRAM,
}); // create the new account
@ -310,6 +321,10 @@ class SolanaBridge {
pubkey: owner,
isSigner: false,
isWritable: false
}, {
pubkey: solanaWeb3.SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false
}];
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]);
const data = Buffer.alloc(dataLayout.span);
@ -323,7 +338,61 @@ class SolanaBridge {
data
}
return [[transaction.instructions[0], ix_init], newAccount]
let ixs: TransactionInstruction[] = [];
let configKey = await this.getConfigKey();
let wrappedKey = await this.getWrappedAssetMint(meta);
let wrappedAcc = await this.connection.getAccountInfo(wrappedKey, "single");
if (!wrappedAcc) {
let metaKey = await this.getWrappedAssetMeta(wrappedKey);
const wa_keys = [{
pubkey: solanaWeb3.SystemProgram.programId,
isSigner: false,
isWritable: false
}, {
pubkey: TOKEN_PROGRAM,
isSigner: false,
isWritable: false
}, {
pubkey: solanaWeb3.SYSVAR_RENT_PUBKEY,
isSigner: false,
isWritable: false
}, {
pubkey: configKey,
isSigner: false,
isWritable: false
}, {
pubkey: owner,
isSigner: true,
isWritable: true
}, {
pubkey: wrappedKey,
isSigner: false,
isWritable: true
}, {
pubkey: metaKey,
isSigner: false,
isWritable: true
}];
const wrappedDataLayout = BufferLayout.struct([BufferLayout.u8('instruction'), BufferLayout.blob(32, "assetAddress"), BufferLayout.u8('chain'), BufferLayout.u8('decimals')]);
const wrappedData = Buffer.alloc(wrappedDataLayout.span);
wrappedDataLayout.encode({
instruction: 7, // CreateWrapped instruction
assetAddress: padBuffer(meta.address, 32),
chain: meta.chain,
decimals: meta.decimals
}, wrappedData);
let ix_wrapped = {
keys: wa_keys,
programId: SOLANA_BRIDGE_PROGRAM,
data: wrappedData
}
ixs.push(ix_wrapped);
}
ixs.push(create_ix, ix_init)
return [ixs, newAccount]
}
async getConfigKey(): Promise<PublicKey> {
@ -337,11 +406,18 @@ class SolanaBridge {
}
let configKey = await this.getConfigKey();
let seeds: Array<Buffer> = [Buffer.from("wrapped"), configKey.toBuffer(), Buffer.of(asset.chain),
let seeds: Array<Buffer> = [Buffer.from("wrapped"), configKey.toBuffer(), Buffer.of(asset.chain), Buffer.of(asset.decimals),
padBuffer(asset.address, 32)];
// @ts-ignore
return (await solanaWeb3.PublicKey.findProgramAddress(seeds, this.programID))[0];
}
async getWrappedAssetMeta(mint: PublicKey): Promise<PublicKey> {
let configKey = await this.getConfigKey();
let seeds: Array<Buffer> = [Buffer.from("meta"), configKey.toBuffer(), mint.toBuffer()];
// @ts-ignore
return (await solanaWeb3.PublicKey.findProgramAddress(seeds, this.programID))[0];
}
}
// Taken from https://github.com/solana-labs/solana-program-library

File diff suppressed because it is too large Load Diff