feat: add bridge
This commit is contained in:
parent
c196778193
commit
7666ff35f1
File diff suppressed because it is too large
Load Diff
|
@ -29,6 +29,7 @@
|
||||||
"craco-babel-loader": "^0.1.4",
|
"craco-babel-loader": "^0.1.4",
|
||||||
"craco-less": "^1.17.0",
|
"craco-less": "^1.17.0",
|
||||||
"echarts": "^4.9.0",
|
"echarts": "^4.9.0",
|
||||||
|
"eth-hooks": "^1.1.2",
|
||||||
"ethers": "^4.0.48",
|
"ethers": "^4.0.48",
|
||||||
"eventemitter3": "^4.0.7",
|
"eventemitter3": "^4.0.7",
|
||||||
"identicon.js": "^2.3.3",
|
"identicon.js": "^2.3.3",
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React, {createContext, FunctionComponent, useContext} from "react"
|
||||||
|
import {Connection} from "@solana/web3.js";
|
||||||
|
import {SolanaBridge} from "../core";
|
||||||
|
import { useConnection, useConnectionConfig } from "@oyster/common/dist/lib/contexts/connection";
|
||||||
|
import { utils } from '@oyster/common';
|
||||||
|
|
||||||
|
export const BridgeContext = createContext<SolanaBridge>(
|
||||||
|
new SolanaBridge(
|
||||||
|
"",
|
||||||
|
new Connection(""),
|
||||||
|
utils.programIds().wormhole.pubkey,
|
||||||
|
utils.programIds().token));
|
||||||
|
|
||||||
|
export const BridgeProvider: FunctionComponent = ({children}) => {
|
||||||
|
const { endpoint } = useConnectionConfig();
|
||||||
|
const connection = useConnection();
|
||||||
|
const programs = utils.programIds();
|
||||||
|
|
||||||
|
let bridge = new SolanaBridge(endpoint, connection, programs.wormhole.pubkey, programs.token);
|
||||||
|
return (
|
||||||
|
<BridgeContext.Provider value={bridge}>
|
||||||
|
{children}
|
||||||
|
</BridgeContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useBridge = () => {
|
||||||
|
const bridge = useContext(BridgeContext);
|
||||||
|
return bridge;
|
||||||
|
}
|
|
@ -0,0 +1,662 @@
|
||||||
|
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 * as bs58 from 'bs58';
|
||||||
|
import { utils } from '@oyster/common';
|
||||||
|
|
||||||
|
export interface AssetMeta {
|
||||||
|
chain: number;
|
||||||
|
decimals: number;
|
||||||
|
address: Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Lockup {
|
||||||
|
lockupAddress: PublicKey;
|
||||||
|
amount: BN;
|
||||||
|
toChain: number;
|
||||||
|
sourceAddress: PublicKey;
|
||||||
|
targetAddress: Uint8Array;
|
||||||
|
assetAddress: Uint8Array;
|
||||||
|
assetChain: number;
|
||||||
|
assetDecimals: number;
|
||||||
|
nonce: number;
|
||||||
|
vaa: Uint8Array;
|
||||||
|
vaaTime: number;
|
||||||
|
pokeCounter: number;
|
||||||
|
signatureAccount: PublicKey;
|
||||||
|
initialized: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Signature {
|
||||||
|
signature: number[];
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHAIN_ID_SOLANA = 1;
|
||||||
|
|
||||||
|
class SolanaBridge {
|
||||||
|
endpoint: string;
|
||||||
|
connection: solanaWeb3.Connection;
|
||||||
|
programID: PublicKey;
|
||||||
|
tokenProgram: PublicKey;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
endpoint: string,
|
||||||
|
connection: solanaWeb3.Connection,
|
||||||
|
programID: PublicKey,
|
||||||
|
tokenProgram: PublicKey,
|
||||||
|
) {
|
||||||
|
this.endpoint = endpoint;
|
||||||
|
this.programID = programID;
|
||||||
|
this.tokenProgram = tokenProgram;
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createLockAssetInstruction(
|
||||||
|
payer: PublicKey,
|
||||||
|
tokenAccount: PublicKey,
|
||||||
|
mint: PublicKey,
|
||||||
|
amount: BN,
|
||||||
|
targetChain: number,
|
||||||
|
targetAddress: Buffer,
|
||||||
|
asset: AssetMeta,
|
||||||
|
nonce: number,
|
||||||
|
): Promise<{ ix: TransactionInstruction; transferKey: PublicKey }> {
|
||||||
|
const dataLayout = BufferLayout.struct([
|
||||||
|
BufferLayout.u8('instruction'),
|
||||||
|
uint256('amount'),
|
||||||
|
BufferLayout.u8('targetChain'),
|
||||||
|
BufferLayout.blob(32, 'assetAddress'),
|
||||||
|
BufferLayout.u8('assetChain'),
|
||||||
|
BufferLayout.u8('assetDecimals'),
|
||||||
|
BufferLayout.blob(32, 'targetAddress'),
|
||||||
|
BufferLayout.seq(BufferLayout.u8(), 1),
|
||||||
|
BufferLayout.u32('nonce'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let nonceBuffer = Buffer.alloc(4);
|
||||||
|
nonceBuffer.writeUInt32LE(nonce, 0);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
let configKey = await this.getConfigKey();
|
||||||
|
let seeds: Array<Buffer> = [
|
||||||
|
Buffer.from('transfer'),
|
||||||
|
configKey.toBuffer(),
|
||||||
|
new Buffer([asset.chain]),
|
||||||
|
padBuffer(asset.address, 32),
|
||||||
|
new Buffer([targetChain]),
|
||||||
|
padBuffer(targetAddress, 32),
|
||||||
|
tokenAccount.toBuffer(),
|
||||||
|
nonceBuffer,
|
||||||
|
];
|
||||||
|
// @ts-ignore
|
||||||
|
let transferKey = (
|
||||||
|
await solanaWeb3.PublicKey.findProgramAddress(seeds, this.programID)
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
|
dataLayout.encode(
|
||||||
|
{
|
||||||
|
instruction: 1, // TransferOut instruction
|
||||||
|
amount: padBuffer(new Buffer(amount.toArray()), 32),
|
||||||
|
targetChain: targetChain,
|
||||||
|
assetAddress: padBuffer(asset.address, 32),
|
||||||
|
assetChain: asset.chain,
|
||||||
|
assetDecimals: asset.decimals,
|
||||||
|
targetAddress: padBuffer(targetAddress, 32),
|
||||||
|
nonce: nonce,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
const keys = [
|
||||||
|
{ pubkey: this.programID, isSigner: false, isWritable: false },
|
||||||
|
{
|
||||||
|
pubkey: solanaWeb3.SystemProgram.programId,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
{ pubkey: this.tokenProgram, isSigner: false, isWritable: false },
|
||||||
|
{
|
||||||
|
pubkey: solanaWeb3.SYSVAR_RENT_PUBKEY,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: solanaWeb3.SYSVAR_CLOCK_PUBKEY,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: solanaWeb3.SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
{ pubkey: tokenAccount, isSigner: false, isWritable: true },
|
||||||
|
{ pubkey: configKey, isSigner: false, isWritable: false },
|
||||||
|
|
||||||
|
{ pubkey: transferKey, isSigner: false, isWritable: true },
|
||||||
|
{ pubkey: mint, isSigner: false, isWritable: true },
|
||||||
|
{ pubkey: payer, isSigner: true, isWritable: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (asset.chain == CHAIN_ID_SOLANA) {
|
||||||
|
// @ts-ignore
|
||||||
|
let custodyKey = (
|
||||||
|
await solanaWeb3.PublicKey.findProgramAddress(
|
||||||
|
[Buffer.from('custody'), configKey.toBuffer(), mint.toBuffer()],
|
||||||
|
this.programID,
|
||||||
|
)
|
||||||
|
)[0];
|
||||||
|
keys.push({ pubkey: custodyKey, isSigner: false, isWritable: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ix: new TransactionInstruction({
|
||||||
|
keys,
|
||||||
|
programId: this.programID,
|
||||||
|
data,
|
||||||
|
}),
|
||||||
|
transferKey: transferKey,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
createPokeProposalInstruction(
|
||||||
|
proposalAccount: PublicKey,
|
||||||
|
): TransactionInstruction {
|
||||||
|
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]);
|
||||||
|
|
||||||
|
const data = Buffer.alloc(dataLayout.span);
|
||||||
|
dataLayout.encode(
|
||||||
|
{
|
||||||
|
instruction: 5, // PokeProposal instruction
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
|
||||||
|
const keys = [
|
||||||
|
{ pubkey: proposalAccount, isSigner: false, isWritable: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
return new TransactionInstruction({
|
||||||
|
keys,
|
||||||
|
programId: this.programID,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchAssetMeta fetches the AssetMeta for an SPL token
|
||||||
|
async fetchAssetMeta(mint: PublicKey): Promise<AssetMeta> {
|
||||||
|
// @ts-ignore
|
||||||
|
let configKey = await this.getConfigKey();
|
||||||
|
let seeds: Array<Buffer> = [
|
||||||
|
Buffer.from('meta'),
|
||||||
|
configKey.toBuffer(),
|
||||||
|
mint.toBuffer(),
|
||||||
|
];
|
||||||
|
// @ts-ignore
|
||||||
|
let metaKey = (
|
||||||
|
await solanaWeb3.PublicKey.findProgramAddress(seeds, this.programID)
|
||||||
|
)[0];
|
||||||
|
let metaInfo = await this.connection.getAccountInfo(metaKey);
|
||||||
|
if (metaInfo == null || metaInfo.lamports == 0) {
|
||||||
|
return {
|
||||||
|
address: mint.toBuffer(),
|
||||||
|
chain: CHAIN_ID_SOLANA,
|
||||||
|
decimals: 0,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const dataLayout = BufferLayout.struct([
|
||||||
|
BufferLayout.u8('assetChain'),
|
||||||
|
BufferLayout.blob(32, 'assetAddress'),
|
||||||
|
]);
|
||||||
|
let wrappedMeta = dataLayout.decode(metaInfo?.data);
|
||||||
|
|
||||||
|
return {
|
||||||
|
address: wrappedMeta.assetAddress,
|
||||||
|
chain: wrappedMeta.assetChain,
|
||||||
|
decimals: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchSignatureStatus fetches the signatures for a VAA
|
||||||
|
async fetchSignatureStatus(signatureStatus: PublicKey): Promise<Signature[]> {
|
||||||
|
let signatureInfo = await this.connection.getAccountInfo(
|
||||||
|
signatureStatus,
|
||||||
|
'single',
|
||||||
|
);
|
||||||
|
if (signatureInfo == null || signatureInfo.lamports == 0) {
|
||||||
|
throw new Error('not found');
|
||||||
|
} else {
|
||||||
|
const dataLayout = BufferLayout.struct([
|
||||||
|
BufferLayout.blob(20 * 65, 'signaturesRaw'),
|
||||||
|
]);
|
||||||
|
let rawSignatureInfo = dataLayout.decode(signatureInfo?.data);
|
||||||
|
|
||||||
|
let signatures: Signature[] = [];
|
||||||
|
for (let i = 0; i < 20; i++) {
|
||||||
|
let data = rawSignatureInfo.signaturesRaw.slice(65 * i, 65 * (i + 1));
|
||||||
|
let empty = true;
|
||||||
|
for (let v of data) {
|
||||||
|
if (v != 0) {
|
||||||
|
empty = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty) continue;
|
||||||
|
|
||||||
|
signatures.push({
|
||||||
|
signature: data,
|
||||||
|
index: i,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return signatures;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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): Promise<Lockup[]> {
|
||||||
|
let accountRes = await fetch(this.endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
id: 1,
|
||||||
|
method: 'getProgramAccounts',
|
||||||
|
params: [
|
||||||
|
this.programID.toString(),
|
||||||
|
{
|
||||||
|
commitment: 'single',
|
||||||
|
filters: [
|
||||||
|
{ dataSize: 1184 },
|
||||||
|
{
|
||||||
|
memcmp: {
|
||||||
|
offset: 33,
|
||||||
|
bytes: tokenAccount.toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
let raw_accounts = (await accountRes.json())['result'];
|
||||||
|
|
||||||
|
let accounts: Lockup[] = [];
|
||||||
|
for (let acc of raw_accounts) {
|
||||||
|
let pubkey = new PublicKey(acc.pubkey);
|
||||||
|
accounts.push(this.parseLockup(pubkey, bs58.decode(acc.account.data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return accounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
meta: AssetMeta,
|
||||||
|
): Promise<[TransactionInstruction[], solanaWeb3.Account]> {
|
||||||
|
const newAccount = new solanaWeb3.Account();
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const balanceNeeded = await Token.getMinBalanceRentForExemptAccount(
|
||||||
|
this.connection,
|
||||||
|
);
|
||||||
|
let create_ix = solanaWeb3.SystemProgram.createAccount({
|
||||||
|
fromPubkey: owner,
|
||||||
|
newAccountPubkey: newAccount.publicKey,
|
||||||
|
lamports: balanceNeeded,
|
||||||
|
space: spl.AccountLayout.span,
|
||||||
|
programId: utils.programIds().token,
|
||||||
|
}); // create the new account
|
||||||
|
|
||||||
|
const keys = [
|
||||||
|
{
|
||||||
|
pubkey: newAccount.publicKey,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pubkey: mint,
|
||||||
|
isSigner: false,
|
||||||
|
isWritable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
dataLayout.encode(
|
||||||
|
{
|
||||||
|
instruction: 1, // InitializeAccount instruction
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
let ix_init = {
|
||||||
|
keys,
|
||||||
|
programId: utils.programIds().token,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
|
||||||
|
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: utils.programIds().token,
|
||||||
|
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: utils.programIds().wormhole.pubkey,
|
||||||
|
data: wrappedData,
|
||||||
|
};
|
||||||
|
ixs.push(ix_wrapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
ixs.push(create_ix, ix_init);
|
||||||
|
|
||||||
|
return [ixs, newAccount];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConfigKey(): Promise<PublicKey> {
|
||||||
|
// @ts-ignore
|
||||||
|
return (
|
||||||
|
await solanaWeb3.PublicKey.findProgramAddress(
|
||||||
|
[Buffer.from('bridge')],
|
||||||
|
this.programID,
|
||||||
|
)
|
||||||
|
)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWrappedAssetMint(asset: AssetMeta): Promise<PublicKey> {
|
||||||
|
if (asset.chain === 1) {
|
||||||
|
return new PublicKey(asset.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
let configKey = await this.getConfigKey();
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTransferFee(): Promise<number> {
|
||||||
|
// Reference processor.rs::Bridge::transfer_fee
|
||||||
|
return (
|
||||||
|
(await this.connection.getMinimumBalanceForRentExemption(
|
||||||
|
(40 + 1340) * 2,
|
||||||
|
)) +
|
||||||
|
18 * 10000 * 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Taken from https://github.com/solana-labs/solana-program-library
|
||||||
|
// Licensed under Apache 2.0
|
||||||
|
|
||||||
|
export class u64 extends BN {
|
||||||
|
/**
|
||||||
|
* Convert to Buffer representation
|
||||||
|
*/
|
||||||
|
toBuffer(): Buffer {
|
||||||
|
const a = super.toArray().reverse();
|
||||||
|
const b = Buffer.from(a);
|
||||||
|
if (b.length === 8) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
assert(b.length < 8, 'u64 too large');
|
||||||
|
|
||||||
|
const zeroPad = Buffer.alloc(8);
|
||||||
|
b.copy(zeroPad);
|
||||||
|
return zeroPad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a u64 from Buffer representation
|
||||||
|
*/
|
||||||
|
static fromBuffer(buffer: Buffer): u64 {
|
||||||
|
assert(buffer.length === 8, `Invalid buffer length: ${buffer.length}`);
|
||||||
|
return new BN(
|
||||||
|
// @ts-ignore
|
||||||
|
[...buffer]
|
||||||
|
.reverse()
|
||||||
|
.map(i => `00${i.toString(16)}`.slice(-2))
|
||||||
|
.join(''),
|
||||||
|
16,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function padBuffer(b: Buffer, len: number): Buffer {
|
||||||
|
const zeroPad = Buffer.alloc(len);
|
||||||
|
b.copy(zeroPad, len - b.length);
|
||||||
|
return zeroPad;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class u256 extends BN {
|
||||||
|
/**
|
||||||
|
* Convert to Buffer representation
|
||||||
|
*/
|
||||||
|
toBuffer(): Buffer {
|
||||||
|
const a = super.toArray().reverse();
|
||||||
|
const b = Buffer.from(a);
|
||||||
|
if (b.length === 32) {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
assert(b.length < 32, 'u256 too large');
|
||||||
|
|
||||||
|
const zeroPad = Buffer.alloc(32);
|
||||||
|
b.copy(zeroPad);
|
||||||
|
return zeroPad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a u256 from Buffer representation
|
||||||
|
*/
|
||||||
|
static fromBuffer(buffer: number[]): u256 {
|
||||||
|
assert(buffer.length === 32, `Invalid buffer length: ${buffer.length}`);
|
||||||
|
return new BN(
|
||||||
|
// @ts-ignore
|
||||||
|
[...buffer]
|
||||||
|
.reverse()
|
||||||
|
.map(i => `00${i.toString(16)}`.slice(-2))
|
||||||
|
.join(''),
|
||||||
|
16,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout for a public key
|
||||||
|
*/
|
||||||
|
export const publicKey = (property: string = 'publicKey'): Object => {
|
||||||
|
return BufferLayout.blob(32, property);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout for a 64bit unsigned value
|
||||||
|
*/
|
||||||
|
export const uint64 = (property: string = 'uint64'): Object => {
|
||||||
|
return BufferLayout.blob(8, property);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout for a 256-bit unsigned value
|
||||||
|
*/
|
||||||
|
export const uint256 = (property: string = 'uint256'): Object => {
|
||||||
|
return BufferLayout.blob(32, property);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Layout for a Rust String type
|
||||||
|
*/
|
||||||
|
export const rustString = (property: string = 'string') => {
|
||||||
|
const rsl = BufferLayout.struct(
|
||||||
|
[
|
||||||
|
BufferLayout.u32('length'),
|
||||||
|
BufferLayout.u32('lengthPadding'),
|
||||||
|
BufferLayout.blob(BufferLayout.offset(BufferLayout.u32(), -8), 'chars'),
|
||||||
|
],
|
||||||
|
property,
|
||||||
|
);
|
||||||
|
const _decode = rsl.decode.bind(rsl);
|
||||||
|
const _encode = rsl.encode.bind(rsl);
|
||||||
|
|
||||||
|
rsl.decode = (buffer: Buffer, offset: number) => {
|
||||||
|
const data = _decode(buffer, offset);
|
||||||
|
return data.chars.toString('utf8');
|
||||||
|
};
|
||||||
|
|
||||||
|
rsl.encode = (str: string, buffer: Buffer, offset: number) => {
|
||||||
|
const data = {
|
||||||
|
chars: Buffer.from(str, 'utf8'),
|
||||||
|
};
|
||||||
|
return _encode(data, buffer, offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
return rsl;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { SolanaBridge };
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './bridge';
|
||||||
|
export * from './utils';
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { PublicKey } from '@solana/web3.js';
|
||||||
|
import { keccak256 } from 'ethers/utils';
|
||||||
|
import { utils } from '@oyster/common';
|
||||||
|
|
||||||
|
export const WRAPPED_MASTER = '9A5e27995309a03f8B583feBdE7eF289FcCdC6Ae';
|
||||||
|
|
||||||
|
// derive the ERC20 address of a Solana SPL asset wrapped on ETH.
|
||||||
|
export function deriveERC20Address(key: PublicKey) {
|
||||||
|
const { wormhole } = utils.programIds();
|
||||||
|
|
||||||
|
let hashData = '0xff' + wormhole.bridge.slice(2);
|
||||||
|
hashData += keccak256(Buffer.concat([new Buffer([1]), key.toBuffer()])).slice(
|
||||||
|
2,
|
||||||
|
); // asset_id
|
||||||
|
hashData += keccak256(
|
||||||
|
'0x3d602d80600a3d3981f3363d3d373d3d3d363d73' +
|
||||||
|
WRAPPED_MASTER +
|
||||||
|
'5af43d82803e903d91602b57fd5bf3',
|
||||||
|
).slice(2); // Bytecode
|
||||||
|
|
||||||
|
return keccak256(hashData).slice(26);
|
||||||
|
}
|
Loading…
Reference in New Issue