Skip to content

Commit

Permalink
Merge pull request #76 from kev1n-peters/portal-solana-support
Browse files Browse the repository at this point in the history
Solana adapter support
  • Loading branch information
0xngmi authored Nov 27, 2023
2 parents 9d238b1 + 2a64367 commit 1a8c85e
Show file tree
Hide file tree
Showing 13 changed files with 278 additions and 66 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"dependencies": {
"@defillama/sdk": "^4.0.64",
"@solana/web3.js": "^1.87.3",
"async-retry": "^1.3.1",
"axios": "^0.21.0",
"axios-rate-limit": "^1.3.0",
Expand Down
3 changes: 3 additions & 0 deletions src/adapters/portal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Portal Bridge Adapter

The Portal bridge adapter returns `EventData` for transactions involving token locking, unlocking, minting, or burning. In the case of burning and minting, the `to` and `from` addresses are set to the Ethereum zero-address to exclude wormhole-wrapped assets from the volume calculation. However, if a wormhole-wrapped asset is burned without being transferred to its origin chain, the `to` address is set to the token bridge address to include it in the volume calculation. This is consistent with DefiLlama's methodology of not double-counting transfers in the volume calculation.
61 changes: 61 additions & 0 deletions src/adapters/portal/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Wormhole: Portal core and token bridge contract addresses
// https://docs.wormhole.com/wormhole/blockchain-environments/environments
export const contractAddresses = {
ethereum: {
tokenBridge: "0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
coreBridge: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B",
},
polygon: {
tokenBridge: "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE",
coreBridge: "0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7",
},
fantom: {
tokenBridge: "0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2",
coreBridge: "0x126783A6Cb203a3E35344528B26ca3a0489a1485",
},
avax: {
tokenBridge: "0x0e082F06FF657D94310cB8cE8B0D9a04541d8052",
coreBridge: "0x54a8e5f9c4CbA08F9943965859F6c34eAF03E26c",
},
bsc: {
tokenBridge: "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7",
coreBridge: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B",
},
aurora: {
tokenBridge: "0x51b5123a7b0F9b2bA265f9c4C8de7D78D52f510F",
coreBridge: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E",
},
celo: {
tokenBridge: "0x796Dff6D74F3E27060B71255Fe517BFb23C93eed",
coreBridge: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E",
},
klaytn: {
tokenBridge: "0x5b08ac39EAED75c0439FC750d9FE7E1F9dD0193F",
coreBridge: "0x0C21603c4f3a6387e241c0091A7EA39E43E90bb7",
},
moonbeam: {
tokenBridge: "0xb1731c586ca89a23809861c6103f0b96b3f57d92",
coreBridge: "0xC8e2b0cD52Cf01b0Ce87d389Daa3d414d4cE29f3",
},
optimism: {
tokenBridge: "0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b",
coreBridge: "0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722",
},
arbitrum: {
tokenBridge: "0x0b2402144Bb366A632D14B83F244D2e0e21bD39c",
coreBridge: "0xa5f208e072434bC67592E4C49C1B991BA79BCA46",
},
base: {
tokenBridge: "0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627",
coreBridge: "0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6",
},
solana: {
tokenBridge: "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb",
coreBridge: "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth",
},
} as {
[chain: string]: {
tokenBridge: string;
coreBridge: string;
};
};
90 changes: 27 additions & 63 deletions src/adapters/portal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,11 @@ import { Chain } from "@defillama/sdk/build/general";
import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions";
import { getTxsBlockRangeEtherscan, getLock } from "../../helpers/etherscan";
import { EventData } from "../../utils/types";
import { getProvider } from "@defillama/sdk/build/general";
import { ethers } from "ethers";
import { PromisePool } from "@supercharge/promise-pool";

// Wormhole: Portal core and token bridge contract addresses
// https://docs.wormhole.com/wormhole/blockchain-environments/environments
const contractAddresses = {
ethereum: {
tokenBridge: "0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
coreBridge: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B",
},
polygon: {
tokenBridge: "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE",
coreBridge: "0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7",
},
fantom: {
tokenBridge: "0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2",
coreBridge: "0x126783A6Cb203a3E35344528B26ca3a0489a1485",
},
avax: {
tokenBridge: "0x0e082F06FF657D94310cB8cE8B0D9a04541d8052",
coreBridge: "0x54a8e5f9c4CbA08F9943965859F6c34eAF03E26c",
},
bsc: {
tokenBridge: "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7",
coreBridge: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B",
},
aurora: {
tokenBridge: "0x51b5123a7b0F9b2bA265f9c4C8de7D78D52f510F",
coreBridge: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E",
},
celo: {
tokenBridge: "0x796Dff6D74F3E27060B71255Fe517BFb23C93eed",
coreBridge: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E",
},
klaytn: {
tokenBridge: "0x5b08ac39EAED75c0439FC750d9FE7E1F9dD0193F",
coreBridge: "0x0C21603c4f3a6387e241c0091A7EA39E43E90bb7",
},
moonbeam: {
tokenBridge: "0xb1731c586ca89a23809861c6103f0b96b3f57d92",
coreBridge: "0xC8e2b0cD52Cf01b0Ce87d389Daa3d414d4cE29f3",
},
optimism: {
tokenBridge: "0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b",
coreBridge: "0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722",
},
arbitrum: {
tokenBridge: "0x0b2402144Bb366A632D14B83F244D2e0e21bD39c",
coreBridge: "0xa5f208e072434bC67592E4C49C1B991BA79BCA46",
},
base: {
tokenBridge: "0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627",
coreBridge: "0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6",
},
sui: {
tokenBridge: "0xc57508ee0d4595e5a8728974a4a93a787d38f339757230d441e895422c07aba9", // object ID
coreBridge: "0xaeab97f96cf9877fee2883315d459552b2b921edc16d7ceac6eab944dd88919c", // object ID
},
} as {
[chain: string]: {
tokenBridge: string;
coreBridge: string;
};
};
import { contractAddresses } from "./consts";
import { getProvider } from "@defillama/sdk/build/general";
import axios from "axios";

const completeTransferSigs = [
ethers.utils.id("completeTransferAndUnwrapETH(bytes)"),
Expand Down Expand Up @@ -327,6 +267,29 @@ const constructParams = (chain: string) => {
};
};

interface SolanaEvent {
blockNumber: number;
txHash: string;
from: string;
to: string;
token: string;
amount: string;
isDeposit: boolean;
}

const getSolanaEvents = async (fromSlot: number, toSlot: number): Promise<EventData[]> => {
const response = await axios.get<SolanaEvent[]>(
`https://europe-west3-wormhole-message-db-mainnet.cloudfunctions.net/get-solana-events?fromSlot=${fromSlot}&toSlot=${toSlot}`
);
if (response.status !== 200) {
throw new Error(`Failed to fetch Solana events: ${response.statusText}`);
}
return response.data.map((event) => ({
...event,
amount: ethers.BigNumber.from(event.amount),
}));
};

const adapter: BridgeAdapter = {
ethereum: constructParams("ethereum"),
polygon: constructParams("polygon"),
Expand All @@ -340,6 +303,7 @@ const adapter: BridgeAdapter = {
optimism: constructParams("optimism"),
arbitrum: constructParams("arbitrum"),
base: constructParams("base"),
solana: getSolanaEvents,
};

export default adapter;
152 changes: 151 additions & 1 deletion src/adapters/portal/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,155 @@ const testSui = async () => {
console.log("sui tests passed");
};

const sleep = async (ms: number) => await new Promise((resolve) => setTimeout(resolve, ms));

const testSolana = async () => {
// CompleteNative
// https://explorer.solana.com/tx/3pEZohiewvkQXxqTGiADrqJjQRJLz3twAtntsmktRSbHkUxXswCkM3XrKEzGaTNxF1jDeK6p726jVheRZJUmrJ1T
let blockNumber = 220570267;
let event = await getEvent(blockNumber, "solana");
assertEqual(
{
blockNumber,
txHash: "3pEZohiewvkQXxqTGiADrqJjQRJLz3twAtntsmktRSbHkUxXswCkM3XrKEzGaTNxF1jDeK6p726jVheRZJUmrJ1T",
from: "2nQNF8F9LLWMqdjymiLK2u8HoHMvYa4orCXsp3w65fQ2",
to: "FyUckyjFpEmrG8LiCbQSdwaYYQEytE9VTt2rc4VoRzRK",
token: "So11111111111111111111111111111111111111112",
amount: ethers.BigNumber.from("100000"),
isDeposit: false,
},
event
);
// sleep so we don't get rate limited
sleep(5000);

// CompleteWrapped
// https://explorer.solana.com/tx/8po9N198xBQUspS1jjgSw96ubNNwhEoCggXiutWcPRmshZUpfwcAoe3gFUzS57WiSfcDsFhaL63LhMZCw89Fymg
blockNumber = 220548178;
event = await getEvent(blockNumber, "solana");
assertEqual(
{
blockNumber,
txHash: "8po9N198xBQUspS1jjgSw96ubNNwhEoCggXiutWcPRmshZUpfwcAoe3gFUzS57WiSfcDsFhaL63LhMZCw89Fymg",
from: ethers.constants.AddressZero,
to: "FYoCmjuGAWm9SmkCRfnCEigMP5TPidMrpLzoSgV6xBBa",
token: "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM",
amount: ethers.BigNumber.from("50647953"),
isDeposit: false,
},
event
);
sleep(5000);

// TransferWrapped - target chain != origin chain
// https://explorer.solana.com/tx/28TbBAgVVwTo6bGCUU6hS4bjCgNREAWMuax6B8nwCA4uT6L1CUhwT65bnMb2Eort7JMXQxGoGNNsK1H6eGQThces
blockNumber = 220563907;
event = await getEvent(blockNumber, "solana");
assertEqual(
{
blockNumber,
txHash: "28TbBAgVVwTo6bGCUU6hS4bjCgNREAWMuax6B8nwCA4uT6L1CUhwT65bnMb2Eort7JMXQxGoGNNsK1H6eGQThces",
from: "8LS9m7c9SfPFdbnv6jHs5RYWXqzVkxLDcLJNG6j1ntUQ",
to: "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb",
token: "A9mUU4qviSctJVPJdBJWkb28deg915LYJKrzQ19ji3FM",
amount: ethers.BigNumber.from("2109000000"),
isDeposit: false,
},
event
);
sleep(5000);

// TransferNative - WSOL
// https://explorer.solana.com/tx/61nqe3nxkJ3zCbRuKZ9xAtxb1Rbyc8uBWJbRPVigeUjA7A7dbfATdu4BanP9RniHNNqnUWj2JV6tVnQm28DArU27 (WSOL)
blockNumber = 219412303;
event = await getEvent(blockNumber, "solana");
assertEqual(
{
blockNumber,
txHash: "61nqe3nxkJ3zCbRuKZ9xAtxb1Rbyc8uBWJbRPVigeUjA7A7dbfATdu4BanP9RniHNNqnUWj2JV6tVnQm28DArU27",
from: "7suUQ9d7jPLCXj9H8472APFbXphC59xoXGnf2AofD7bX",
to: "2nQNF8F9LLWMqdjymiLK2u8HoHMvYa4orCXsp3w65fQ2",
token: "So11111111111111111111111111111111111111112",
amount: ethers.BigNumber.from("100000000"),
isDeposit: true,
},
event
);
sleep(5000);

// CompleteNativeWithPayload
// https://explorer.solana.com/tx/2i5UpmfW748CxHvYvW6udWwg68C4NhfqGQXNjB8JkwraHtXckP8Xst3AY55YPv4qdjEeKKxY67Gsw2tqM7jLZ9gT
blockNumber = 218544281;
event = await getEvent(blockNumber, "solana");
assertEqual(
{
blockNumber,
txHash: "2i5UpmfW748CxHvYvW6udWwg68C4NhfqGQXNjB8JkwraHtXckP8Xst3AY55YPv4qdjEeKKxY67Gsw2tqM7jLZ9gT",
from: "2nQNF8F9LLWMqdjymiLK2u8HoHMvYa4orCXsp3w65fQ2",
to: "DrJjs6ziLYc9En8tq912mwdt5F5gRo9uRkWJRNEG9hDt",
token: "So11111111111111111111111111111111111111112",
amount: ethers.BigNumber.from("70000000"),
isDeposit: false,
},
event
);
sleep(5000);

// CompleteWrappedWithPayload
// https://explorer.solana.com/tx/297Jkp5AJbSFFCw38VvSeYY9kGZLJvWrBsjvaS8kfPdLbqsDUbvdRs3TWjfJV9dwjiU7VA115oWrq2xcKwMTd8SV
blockNumber = 220139975;
event = await getEvent(blockNumber, "solana");
assertEqual(
{
blockNumber,
txHash: "297Jkp5AJbSFFCw38VvSeYY9kGZLJvWrBsjvaS8kfPdLbqsDUbvdRs3TWjfJV9dwjiU7VA115oWrq2xcKwMTd8SV",
from: ethers.constants.AddressZero,
to: "Bqun5jc7m16uqVTtJf8sMzJ6qPRc3kjidBKGcUNfxAJy",
token: "G1vJEgzepqhnVu35BN4jrkv3wVwkujYWFFCxhbEZ1CZr",
amount: ethers.BigNumber.from("250000000"),
isDeposit: false,
},
event
);
sleep(5000);

// TransferWrappedWithPayload
// https://explorer.solana.com/tx/2KMyCkFr2v6dxik8V7QR6ny1oJKY7bs2AZqTTXTpwV32aAhBCZC4i5nnXzHaRPaV25JC8BSwwv8t3QPsNUPbugde
blockNumber = 220549172;
event = await getEvent(blockNumber, "solana");
assertEqual(
{
blockNumber,
txHash: "2KMyCkFr2v6dxik8V7QR6ny1oJKY7bs2AZqTTXTpwV32aAhBCZC4i5nnXzHaRPaV25JC8BSwwv8t3QPsNUPbugde",
from: "4RrFMkY3A5zWdizT61Px222qmSTJqnVszDeBZZNSoAH6",
to: ethers.constants.AddressZero,
token: "7vfCXTUXx5WJV5JADk17DUJ4ksgau7utNKj4b963voxs",
amount: ethers.BigNumber.from("77898286"),
isDeposit: false,
},
event
);
sleep(5000);

// TransferNativeWithPayload
// https://explorer.solana.com/tx/2x1HP58teKmEYEZNTvQ9wnQEB3XLA8cD5phAqzuQKd6YHzdhkuMo16za6EQKsrB6a2G3s3QhxRh52qnhcFeT34xK
blockNumber = 219917822;
event = await getEvent(blockNumber, "solana");
assertEqual(
{
blockNumber,
txHash: "2x1HP58teKmEYEZNTvQ9wnQEB3XLA8cD5phAqzuQKd6YHzdhkuMo16za6EQKsrB6a2G3s3QhxRh52qnhcFeT34xK",
from: "DrJjs6ziLYc9En8tq912mwdt5F5gRo9uRkWJRNEG9hDt",
to: "2nQNF8F9LLWMqdjymiLK2u8HoHMvYa4orCXsp3w65fQ2",
token: "So11111111111111111111111111111111111111112",
amount: ethers.BigNumber.from("58000000"),
isDeposit: true,
},
event
);
sleep(5000);
};

(async () => {
await Promise.all([
testNoEventsFound(),
Expand All @@ -549,6 +698,7 @@ const testSui = async () => {
testAvalanche(),
testOptimism(),
testKlaytn(),
testSui(),
// testSui(),
// testSolana(),
]);
})();
1 change: 1 addition & 0 deletions src/data/bridgeNetworkData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export default [
"Optimism",
"Arbitrum",
"Base",
"Solana"
],
chainMapping: {
avalanche: "avax", // this is needed temporarily, need to fix and remove
Expand Down
2 changes: 1 addition & 1 deletion src/helpers/processTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Chain } from "@defillama/sdk/build/general";
import { get } from "lodash";
import { ContractEventParams, PartialContractEventParams } from "../helpers/bridgeAdapter.type";
import { EventData } from "../utils/types";
import { getProvider } from "@defillama/sdk/build/general";
import { PromisePool } from "@supercharge/promise-pool";
import { getProvider } from "../utils/provider";

const EventKeyTypes = {
blockNumber: "number",
Expand Down
14 changes: 14 additions & 0 deletions src/helpers/solana.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Connection } from "@solana/web3.js";

export const getConnection = (): Connection => {
const rpc = process.env["SOLANA_RPC"] ?? "https://api.mainnet-beta.solana.com";
const connection = new Connection(rpc);
const getBlock = async (block: number) => {
return new Connection(rpc).getBlock(block, {
maxSupportedTransactionVersion: 0,
}) as any;
};

connection.getBlock = getBlock;
return connection;
};
2 changes: 1 addition & 1 deletion src/utils/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export const runAllAdaptersTimestampRange = async (
const chainContractsAreOn = bridgeNetwork.chainMapping?.[chain as Chain]
? bridgeNetwork.chainMapping?.[chain as Chain]
: chain;
if (chainContractsAreOn === "tron" || chainContractsAreOn === "sui") {
if (chainContractsAreOn === "tron" || chainContractsAreOn === "sui" || chainContractsAreOn === "solana") {
console.info(`Skipping running adapter ${bridgeDbName} on chain ${chainContractsAreOn}.`);
return;
}
Expand Down
Loading

0 comments on commit 1a8c85e

Please sign in to comment.