examples: most basic rest relayer
Change-Id: I207644487224586ba064f9d5b6f0b547abe8cd96
This commit is contained in:
parent
f085e4e15c
commit
acf186c783
|
@ -0,0 +1,3 @@
|
|||
ETH_NODE_URL=ws://localhost:8545
|
||||
ETH_PRIVATE_KEY=0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d
|
||||
ETH_TOKEN_BRIDGE_ADDRESS=0x0290FB167208Af455bB137780163b7B7a9a10C16
|
|
@ -0,0 +1,34 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# ethereum contracts
|
||||
/contracts
|
||||
/src/ethers-contracts
|
||||
|
||||
# tsproto output
|
||||
/src/proto
|
||||
|
||||
# build
|
||||
/lib
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"transform": {
|
||||
"^.+\\.(t|j)sx?$": "ts-jest"
|
||||
},
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
"transformIgnorePatterns": ["/node_modules/"]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"name": "@certusone/wormhole-examples",
|
||||
"version": "0.0.1",
|
||||
"description": "SDK for interacting with Wormhole",
|
||||
"homepage": "https://wormholenetwork.com",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
],
|
||||
"repository": "https://github.com/certusone/wormhole/tree/dev.v2/examples",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "npm run build && node lib/index.js",
|
||||
"test": "jest --config jestconfig.json --verbose"
|
||||
},
|
||||
"keywords": [
|
||||
"wormhole",
|
||||
"bridge",
|
||||
"token",
|
||||
"sdk",
|
||||
"solana",
|
||||
"ethereum",
|
||||
"terra",
|
||||
"bsc"
|
||||
],
|
||||
"author": "certusone",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/long": "^4.0.1",
|
||||
"@types/node": "^16.6.1",
|
||||
"axios": "^0.24.0",
|
||||
"esm": "^3.2.25",
|
||||
"ethers": "^5.4.4",
|
||||
"jest": "^27.3.1",
|
||||
"prettier": "^2.3.2",
|
||||
"ts-jest": "^27.0.7",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@certusone/wormhole-sdk": "file:../../sdk/js",
|
||||
"@improbable-eng/grpc-web": "^0.14.0",
|
||||
"@solana/spl-token": "^0.1.8",
|
||||
"@solana/web3.js": "^1.24.0",
|
||||
"@terra-money/terra.js": "^2.0.14",
|
||||
"body-parser": "^1.19.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"express": "^4.17.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import { describe, expect, it } from "@jest/globals";
|
||||
import { Connection, PublicKey } from "@solana/web3.js";
|
||||
|
||||
// see devnet.md
|
||||
export const ETH_NODE_URL = "ws://localhost:8545";
|
||||
export const ETH_PRIVATE_KEY =
|
||||
"0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d";
|
||||
export const ETH_PUBLIC_KEY = "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
||||
export const ETH_CORE_BRIDGE_ADDRESS =
|
||||
"0xC89Ce4735882C9F0f0FE26686c53074E09B0D550";
|
||||
export const ETH_TOKEN_BRIDGE_ADDRESS =
|
||||
"0x0290FB167208Af455bB137780163b7B7a9a10C16";
|
||||
export const SOLANA_HOST = "http://localhost:8899";
|
||||
export const SOLANA_PRIVATE_KEY = new Uint8Array([
|
||||
14, 173, 153, 4, 176, 224, 201, 111, 32, 237, 183, 185, 159, 247, 22, 161, 89,
|
||||
84, 215, 209, 212, 137, 10, 92, 157, 49, 29, 192, 101, 164, 152, 70, 87, 65,
|
||||
8, 174, 214, 157, 175, 126, 98, 90, 54, 24, 100, 177, 247, 77, 19, 112, 47,
|
||||
44, 165, 109, 233, 102, 14, 86, 109, 29, 134, 145, 132, 141,
|
||||
]);
|
||||
export const SOLANA_CORE_BRIDGE_ADDRESS =
|
||||
"Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
|
||||
export const SOLANA_TOKEN_BRIDGE_ADDRESS =
|
||||
"B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE";
|
||||
export const TEST_ERC20 = "0x2D8BE6BF0baA74e0A907016679CaE9190e80dD0A";
|
||||
export const TEST_SOLANA_TOKEN = "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ";
|
||||
export const WORMHOLE_RPC_HOSTS = ["http://localhost:7071"];
|
||||
|
||||
describe("consts should exist", () => {
|
||||
it("has Solana test token", () => {
|
||||
expect.assertions(1);
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
return expect(
|
||||
connection.getAccountInfo(new PublicKey(TEST_SOLANA_TOKEN))
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,106 @@
|
|||
import {
|
||||
CHAIN_ID_ETH,
|
||||
CHAIN_ID_SOLANA,
|
||||
getEmitterAddressSolana,
|
||||
hexToUint8Array,
|
||||
nativeToHexString,
|
||||
parseSequenceFromLogSolana,
|
||||
transferFromSolana,
|
||||
uint8ArrayToHex,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/cjs/rpc/getSignedVAAWithRetry";
|
||||
import { setDefaultWasm } from "@certusone/wormhole-sdk/lib/cjs/solana/wasm";
|
||||
import { parseUnits } from "@ethersproject/units";
|
||||
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||
import { jest, test } from "@jest/globals";
|
||||
import {
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
Token,
|
||||
TOKEN_PROGRAM_ID,
|
||||
} from "@solana/spl-token";
|
||||
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
||||
import axios from "axios";
|
||||
import {
|
||||
ETH_PUBLIC_KEY,
|
||||
SOLANA_CORE_BRIDGE_ADDRESS,
|
||||
SOLANA_HOST,
|
||||
SOLANA_PRIVATE_KEY,
|
||||
SOLANA_TOKEN_BRIDGE_ADDRESS,
|
||||
TEST_SOLANA_TOKEN,
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
} from "./consts";
|
||||
|
||||
const RELAYER_URL = "http://localhost:3001/relay";
|
||||
|
||||
setDefaultWasm("node");
|
||||
|
||||
jest.setTimeout(60000);
|
||||
|
||||
test("Send Solana SPL to Ethereum", (done) => {
|
||||
(async () => {
|
||||
try {
|
||||
const targetAddress = ETH_PUBLIC_KEY;
|
||||
// create a keypair for Solana
|
||||
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||
const payerAddress = keypair.publicKey.toString();
|
||||
// find the associated token account
|
||||
const fromAddress = (
|
||||
await Token.getAssociatedTokenAddress(
|
||||
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||
TOKEN_PROGRAM_ID,
|
||||
new PublicKey(TEST_SOLANA_TOKEN),
|
||||
keypair.publicKey
|
||||
)
|
||||
).toString();
|
||||
// transfer the test token
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const amount = parseUnits("1", 9).toBigInt();
|
||||
const transaction = await transferFromSolana(
|
||||
connection,
|
||||
SOLANA_CORE_BRIDGE_ADDRESS,
|
||||
SOLANA_TOKEN_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
fromAddress,
|
||||
TEST_SOLANA_TOKEN,
|
||||
amount,
|
||||
hexToUint8Array(nativeToHexString(targetAddress, CHAIN_ID_ETH) || ""),
|
||||
CHAIN_ID_ETH
|
||||
);
|
||||
// sign, send, and confirm transaction
|
||||
transaction.partialSign(keypair);
|
||||
const txid = await connection.sendRawTransaction(transaction.serialize());
|
||||
console.log("Solana transaction:", txid);
|
||||
await connection.confirmTransaction(txid);
|
||||
const info = await connection.getTransaction(txid);
|
||||
if (!info) {
|
||||
throw new Error(
|
||||
"An error occurred while fetching the transaction info"
|
||||
);
|
||||
}
|
||||
// get the sequence from the logs (needed to fetch the vaa)
|
||||
const sequence = parseSequenceFromLogSolana(info);
|
||||
const emitterAddress = await getEmitterAddressSolana(
|
||||
SOLANA_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
// poll until the guardian(s) witness and sign the vaa
|
||||
const { vaaBytes: signedVAA } = await getSignedVAAWithRetry(
|
||||
WORMHOLE_RPC_HOSTS,
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence,
|
||||
{
|
||||
transport: NodeHttpTransport(),
|
||||
}
|
||||
);
|
||||
const result = await axios.post(RELAYER_URL, {
|
||||
chainId: CHAIN_ID_ETH,
|
||||
signedVAA: uint8ArrayToHex(signedVAA),
|
||||
});
|
||||
console.log(result);
|
||||
done();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
done("An error occurred while trying to send from Solana to Ethereum");
|
||||
}
|
||||
})();
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
import {
|
||||
CHAIN_ID_ETH,
|
||||
hexToUint8Array,
|
||||
redeemOnEth,
|
||||
} from "@certusone/wormhole-sdk";
|
||||
import { ethers } from "ethers";
|
||||
require("dotenv").config();
|
||||
if (!process.env.ETH_NODE_URL) {
|
||||
console.error("Missing environment variable ETH_NODE_URL");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!process.env.ETH_PRIVATE_KEY) {
|
||||
console.error("Missing environment variable ETH_PRIVATE_KEY");
|
||||
process.exit(1);
|
||||
}
|
||||
if (!process.env.ETH_TOKEN_BRIDGE_ADDRESS) {
|
||||
console.error("Missing environment variable ETH_TOKEN_BRIDGE_ADDRESS");
|
||||
process.exit(1);
|
||||
}
|
||||
const SUPPORTED_CHAINS = [CHAIN_ID_ETH];
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const bodyParser = require("body-parser");
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
app.post("/relay", async (req, res) => {
|
||||
console.log(req.body);
|
||||
const chainId = req.body?.chainId;
|
||||
if (!SUPPORTED_CHAINS.includes(chainId)) {
|
||||
res.status(400).json({ error: "Unsupported chainId" });
|
||||
return;
|
||||
}
|
||||
const signedVAA = req.body?.signedVAA;
|
||||
if (!signedVAA) {
|
||||
res.status(400).json({ error: "signedVAA is required" });
|
||||
}
|
||||
const provider = new ethers.providers.WebSocketProvider(
|
||||
process.env.ETH_NODE_URL
|
||||
);
|
||||
const signer = new ethers.Wallet(process.env.ETH_PRIVATE_KEY, provider);
|
||||
const receipt = await redeemOnEth(
|
||||
process.env.ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
signer,
|
||||
hexToUint8Array(signedVAA)
|
||||
);
|
||||
provider.destroy();
|
||||
res.status(200).json(receipt);
|
||||
});
|
||||
app.listen(3001, () => {
|
||||
console.log("Server running on port 3001");
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["es2019"],
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "**/__tests__/*"]
|
||||
}
|
Loading…
Reference in New Issue