examples: beginnings of an examples codebase
Change-Id: I3c84aa954aef80307dba400df1182489eb6eedb7
This commit is contained in:
parent
d678cb4662
commit
5ed2b2a06d
|
@ -0,0 +1,33 @@
|
||||||
|
# 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.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,58 @@
|
||||||
|
{
|
||||||
|
"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 -r esm src/runner.js"
|
||||||
|
},
|
||||||
|
"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",
|
||||||
|
"@openzeppelin/contracts": "^4.2.0",
|
||||||
|
"@typechain/ethers-v5": "^7.0.1",
|
||||||
|
"@types/jest": "^27.0.2",
|
||||||
|
"@types/long": "^4.0.1",
|
||||||
|
"@types/node": "^16.6.1",
|
||||||
|
"@types/react": "^17.0.19",
|
||||||
|
"copy-dir": "^1.3.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": "^0.0.10",
|
||||||
|
"@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",
|
||||||
|
"@terra-money/wallet-provider": "^2.2.0",
|
||||||
|
"bech32": "^2.0.0",
|
||||||
|
"js-base64": "^3.6.1",
|
||||||
|
"protobufjs": "^6.11.2",
|
||||||
|
"rxjs": "^7.3.0"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
parseNFTPayload,
|
||||||
|
parseTransferPayload,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
|
import { BigNumber } from "@ethersproject/bignumber";
|
||||||
|
import { formatUnits } from "@ethersproject/units";
|
||||||
|
import { getSignedVAABySequence } from "./core/guardianQuery";
|
||||||
|
import { redeem } from "./core/redeem";
|
||||||
|
|
||||||
|
/*
|
||||||
|
The intent behind this module is to represent a backend process which waits for the guardian network to produce a signedVAA
|
||||||
|
for the bridged tokens, and then submits the VAA to the target chain. This allows the end user to pay fees only in currency from
|
||||||
|
the source chain, rather than on both chains.
|
||||||
|
|
||||||
|
*/
|
||||||
|
export async function relay(
|
||||||
|
sourceChain: ChainId,
|
||||||
|
sequence: string,
|
||||||
|
isNftTransfer: boolean
|
||||||
|
) {
|
||||||
|
//The transaction has already been submitted by the client,
|
||||||
|
//so the relayer first needs to wait for the guardian network to
|
||||||
|
//reach consensus and emit the signedVAA.
|
||||||
|
const vaaBytes = await getSignedVAABySequence(
|
||||||
|
sourceChain, //Emitter address is always the bridge contract address on the source chain
|
||||||
|
sequence,
|
||||||
|
isNftTransfer
|
||||||
|
);
|
||||||
|
|
||||||
|
//The VAA is in the generic format of the Wormhole Core bridge. The VAA payload contains the information needed to redeem the tokens.
|
||||||
|
const transferInformation = await parsePayload(
|
||||||
|
await parseVaa(vaaBytes),
|
||||||
|
isNftTransfer
|
||||||
|
);
|
||||||
|
//If the relayer is unwilling to submit VAAs at a potential monetary loss, it should first assess if this will be a profitable action.
|
||||||
|
const shouldAttempt = await processFee(transferInformation, isNftTransfer);
|
||||||
|
|
||||||
|
if (shouldAttempt) {
|
||||||
|
try {
|
||||||
|
await redeem(transferInformation.targetChain, vaaBytes, isNftTransfer);
|
||||||
|
} catch (e) {
|
||||||
|
//Because VAAs are broadcasted publicly, there is a possibility that the VAA
|
||||||
|
//will be redeemed by a different relayer. This case should be detected separately from
|
||||||
|
//other errors, as it is a do-not-retry scenario.
|
||||||
|
//This error will be deterministic, but dependent upon the implementation
|
||||||
|
//of the specific wallet provider used. As such, the detection of this error
|
||||||
|
//will need to be implemented for each provider separately.
|
||||||
|
if (isAlreadyRedeemedError(e as any)) {
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//This function converts the raw VAA into a useable javascript object.
|
||||||
|
async function parseVaa(bytes: Uint8Array) {
|
||||||
|
//parse_vaa is based on wasm
|
||||||
|
const { parse_vaa } = await import(
|
||||||
|
"@certusone/wormhole-sdk/lib/solana/core/bridge"
|
||||||
|
);
|
||||||
|
return parse_vaa(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
//This takes the generic parsedVAA format from the Core Bridge, and parses the payload content into the
|
||||||
|
//protocol specific information of the Token & NFT bridges.
|
||||||
|
//Note: there are an unlimited variety of VAA formats, and it should not be assumed that a random VAA is one of these
|
||||||
|
//two types.
|
||||||
|
async function parsePayload(parsedVaa: any, isNftTransfer: boolean) {
|
||||||
|
const buffered = Buffer.from(new Uint8Array(parsedVaa.payload));
|
||||||
|
return isNftTransfer
|
||||||
|
? parseNFTPayload(buffered)
|
||||||
|
: parseTransferPayload(buffered);
|
||||||
|
}
|
||||||
|
|
||||||
|
//This is a toy function for the purpose of determining a VAA's profitability.
|
||||||
|
async function processFee(transferInformation: any, isNftTransfer: boolean) {
|
||||||
|
if (isNftTransfer) {
|
||||||
|
//NFTs are always relayed at a loss, because there is no fee field on the VAA.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetAssetDecimals = 8; //This will have to be pulled from either the chain or third party provider
|
||||||
|
const targetAssetUnitPrice = 100; //Accurate price quotes are important for determining profitability.
|
||||||
|
const feeValue =
|
||||||
|
parseFloat(
|
||||||
|
formatUnits(
|
||||||
|
BigNumber.from((transferInformation.fee || BigInt(0)) as bigint),
|
||||||
|
targetAssetDecimals
|
||||||
|
)
|
||||||
|
) * targetAssetUnitPrice;
|
||||||
|
|
||||||
|
const estimatedCurrencyFees = 0.01;
|
||||||
|
const estimatedCurrencyPrice = -1;
|
||||||
|
const transactionCost = estimatedCurrencyFees * estimatedCurrencyPrice;
|
||||||
|
|
||||||
|
return feeValue > transactionCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALREADY_REDEEMED = "Already Redeemed";
|
||||||
|
function isAlreadyRedeemedError(e: Error) {
|
||||||
|
return e?.message === ALREADY_REDEEMED;
|
||||||
|
}
|
|
@ -0,0 +1,224 @@
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_BSC,
|
||||||
|
CHAIN_ID_ETH,
|
||||||
|
CHAIN_ID_POLYGON,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
CHAIN_ID_TERRA,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
|
import { Signer } from "@ethersproject/abstract-signer";
|
||||||
|
import { clusterApiUrl } from "@solana/web3.js";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import { getAddress } from "ethers/lib/utils";
|
||||||
|
|
||||||
|
//Devnet here means the locahost kubernetes environment used by the certusone/wormhole official git repository.
|
||||||
|
//Testnet is the official Wormhole testnet
|
||||||
|
export type Environment = "devnet" | "testnet" | "mainnet";
|
||||||
|
export const CLUSTER: Environment = "devnet" as Environment; //This is the currently selected environment.
|
||||||
|
|
||||||
|
export const SOLANA_HOST = process.env.REACT_APP_SOLANA_API_URL
|
||||||
|
? process.env.REACT_APP_SOLANA_API_URL
|
||||||
|
: CLUSTER === "mainnet"
|
||||||
|
? clusterApiUrl("mainnet-beta")
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? clusterApiUrl("testnet")
|
||||||
|
: "http://localhost:8899";
|
||||||
|
|
||||||
|
export const TERRA_HOST =
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? {
|
||||||
|
URL: "https://lcd.terra.dev",
|
||||||
|
chainID: "columbus-5",
|
||||||
|
name: "mainnet",
|
||||||
|
}
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? {
|
||||||
|
URL: "https://bombay-lcd.terra.dev",
|
||||||
|
chainID: "bombay-12",
|
||||||
|
name: "testnet",
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
URL: "http://localhost:1317",
|
||||||
|
chainID: "columbus-5",
|
||||||
|
name: "localterra",
|
||||||
|
};
|
||||||
|
export const ETH_BRIDGE_ADDRESS = getAddress(
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "0x44F3e7c20850B3B5f3031114726A9240911D912a"
|
||||||
|
: "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550"
|
||||||
|
);
|
||||||
|
export const ETH_NFT_BRIDGE_ADDRESS = getAddress(
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "0x6FFd7EdE62328b3Af38FCD61461Bbfc52F5651fE"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "0x26b4afb60d6c903165150c6f0aa14f8016be4aec" // TODO: test address
|
||||||
|
: "0x26b4afb60d6c903165150c6f0aa14f8016be4aec"
|
||||||
|
);
|
||||||
|
export const ETH_TOKEN_BRIDGE_ADDRESS = getAddress(
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "0x3ee18B2214AFF97000D974cf647E7C347E8fa585"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "0xa6CDAddA6e4B6704705b065E01E52e2486c0FBf6"
|
||||||
|
: "0x0290FB167208Af455bB137780163b7B7a9a10C16"
|
||||||
|
);
|
||||||
|
export const BSC_BRIDGE_ADDRESS = getAddress(
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550" // TODO: test address
|
||||||
|
: "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550"
|
||||||
|
);
|
||||||
|
export const BSC_NFT_BRIDGE_ADDRESS = getAddress(
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "0x26b4afb60d6c903165150c6f0aa14f8016be4aec" // TODO: test address
|
||||||
|
: "0x26b4afb60d6c903165150c6f0aa14f8016be4aec"
|
||||||
|
);
|
||||||
|
export const BSC_TOKEN_BRIDGE_ADDRESS = getAddress(
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "0x0290FB167208Af455bB137780163b7B7a9a10C16" // TODO: test address
|
||||||
|
: "0x0290FB167208Af455bB137780163b7B7a9a10C16"
|
||||||
|
);
|
||||||
|
export const POLYGON_BRIDGE_ADDRESS = getAddress(
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550" // TODO: test address
|
||||||
|
: "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550"
|
||||||
|
);
|
||||||
|
export const POLYGON_NFT_BRIDGE_ADDRESS = getAddress(
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "0x90BBd86a6Fe93D3bc3ed6335935447E75fAb7fCf"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "0x26b4afb60d6c903165150c6f0aa14f8016be4aec" // TODO: test address
|
||||||
|
: "0x26b4afb60d6c903165150c6f0aa14f8016be4aec"
|
||||||
|
);
|
||||||
|
export const POLYGON_TOKEN_BRIDGE_ADDRESS = getAddress(
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "0x0290FB167208Af455bB137780163b7B7a9a10C16" // TODO: test address
|
||||||
|
: "0x0290FB167208Af455bB137780163b7B7a9a10C16"
|
||||||
|
);
|
||||||
|
export const SOL_BRIDGE_ADDRESS =
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "Brdguy7BmNB4qwEbcqqMbyV5CyJd2sxQNUn6NEpMSsUb"
|
||||||
|
: "Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o";
|
||||||
|
export const SOL_NFT_BRIDGE_ADDRESS =
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "WnFt12ZrnzZrFZkt2xsNsaNWoQribnuQ5B5FrDbwDhD"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA" // TODO: test address
|
||||||
|
: "NFTWqJR8YnRVqPDvTJrYuLrQDitTG5AScqbeghi4zSA";
|
||||||
|
export const SOL_TOKEN_BRIDGE_ADDRESS =
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "A4Us8EhCC76XdGAN17L4KpRNEK423nMivVHZzZqFqqBg"
|
||||||
|
: "B6RHG3mfcckmrYN1UhmJzyS1XX3fZKbkeUcpJe9Sy3FE";
|
||||||
|
export const TERRA_BRIDGE_ADDRESS =
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "terra1dq03ugtd40zu9hcgdzrsq6z2z4hwhc9tqk2uy5"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5"
|
||||||
|
: "terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5";
|
||||||
|
export const TERRA_TOKEN_BRIDGE_ADDRESS =
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? "terra10nmmwe8r3g99a9newtqa7a75xfgs2e8z87r2sf"
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4"
|
||||||
|
: "terra10pyejy66429refv3g35g2t7am0was7ya7kz2a4";
|
||||||
|
|
||||||
|
export const getBridgeAddressForChain = (chainId: ChainId) =>
|
||||||
|
chainId === CHAIN_ID_SOLANA
|
||||||
|
? SOL_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_ETH
|
||||||
|
? ETH_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_BSC
|
||||||
|
? BSC_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_TERRA
|
||||||
|
? TERRA_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_POLYGON
|
||||||
|
? POLYGON_BRIDGE_ADDRESS
|
||||||
|
: "";
|
||||||
|
export const getNFTBridgeAddressForChain = (chainId: ChainId) =>
|
||||||
|
chainId === CHAIN_ID_SOLANA
|
||||||
|
? SOL_NFT_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_ETH
|
||||||
|
? ETH_NFT_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_BSC
|
||||||
|
? BSC_NFT_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_POLYGON
|
||||||
|
? POLYGON_NFT_BRIDGE_ADDRESS
|
||||||
|
: "";
|
||||||
|
export const getTokenBridgeAddressForChain = (chainId: ChainId) =>
|
||||||
|
chainId === CHAIN_ID_SOLANA
|
||||||
|
? SOL_TOKEN_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_ETH
|
||||||
|
? ETH_TOKEN_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_BSC
|
||||||
|
? BSC_TOKEN_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_TERRA
|
||||||
|
? TERRA_TOKEN_BRIDGE_ADDRESS
|
||||||
|
: chainId === CHAIN_ID_POLYGON
|
||||||
|
? POLYGON_TOKEN_BRIDGE_ADDRESS
|
||||||
|
: "";
|
||||||
|
|
||||||
|
export const WORMHOLE_RPC_HOSTS =
|
||||||
|
CLUSTER === "mainnet"
|
||||||
|
? [
|
||||||
|
"https://wormhole-v2-mainnet-api.certus.one",
|
||||||
|
"https://wormhole.inotel.ro",
|
||||||
|
"https://wormhole-v2-mainnet-api.mcf.rocks",
|
||||||
|
"https://wormhole-v2-mainnet-api.chainlayer.network",
|
||||||
|
"https://wormhole-v2-mainnet-api.staking.fund",
|
||||||
|
"https://wormhole-v2-mainnet-api.chainlayer.network",
|
||||||
|
]
|
||||||
|
: CLUSTER === "testnet"
|
||||||
|
? [
|
||||||
|
"https://wormhole-v2-testnet-api.certus.one",
|
||||||
|
"https://wormhole-v2-testnet-api.mcf.rocks",
|
||||||
|
"https://wormhole-v2-testnet-api.chainlayer.network",
|
||||||
|
"https://wormhole-v2-testnet-api.staking.fund",
|
||||||
|
"https://wormhole-v2-testnet-api.chainlayer.network",
|
||||||
|
]
|
||||||
|
: ["http://localhost:7071"];
|
||||||
|
|
||||||
|
export const ETH_NODE_URL = "ws://localhost:8545"; //TODO testnet
|
||||||
|
export const POLYGON_NODE_URL = "ws:localhost:0000"; //TODO
|
||||||
|
export const BSC_NODE_URL = "ws://localhost:8545"; //TODO testnet
|
||||||
|
export const ETH_PRIVATE_KEY =
|
||||||
|
"0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d";
|
||||||
|
|
||||||
|
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 function getSignerForChain(chainId: ChainId): Signer {
|
||||||
|
const provider = new ethers.providers.WebSocketProvider(
|
||||||
|
chainId === CHAIN_ID_POLYGON
|
||||||
|
? POLYGON_NODE_URL
|
||||||
|
: chainId === CHAIN_ID_BSC
|
||||||
|
? BSC_NODE_URL
|
||||||
|
: ETH_NODE_URL
|
||||||
|
);
|
||||||
|
const signer = new ethers.Wallet(ETH_PRIVATE_KEY, provider);
|
||||||
|
return signer;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ETH_TEST_WALLET_PUBLIC_KEY =
|
||||||
|
"0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
|
||||||
|
|
||||||
|
export const SOLANA_TEST_TOKEN = "2WDq7wSs9zYrpx2kbHDA4RUTRch2CCTP6ZWaH4GNfnQQ"; //SOLT on devnet
|
||||||
|
export const SOLANA_TEST_WALLET_PUBLIC_KEY =
|
||||||
|
"6sbzC1eH4FTujJXWj51eQe25cYvr4xfXbJ1vAj7j2k5J";
|
|
@ -0,0 +1,80 @@
|
||||||
|
import {
|
||||||
|
attestFromEth,
|
||||||
|
attestFromSolana,
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
CHAIN_ID_TERRA,
|
||||||
|
parseSequenceFromLogEth,
|
||||||
|
parseSequenceFromLogSolana,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
|
import { Connection, Keypair } from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
getBridgeAddressForChain,
|
||||||
|
getSignerForChain,
|
||||||
|
getTokenBridgeAddressForChain,
|
||||||
|
SOLANA_HOST,
|
||||||
|
SOLANA_PRIVATE_KEY,
|
||||||
|
SOL_BRIDGE_ADDRESS,
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
} from "../consts";
|
||||||
|
|
||||||
|
/*
|
||||||
|
This function attests the given token and returns the resultant sequence number, which is then used to retrieve the
|
||||||
|
VAA from the guardians.
|
||||||
|
*/
|
||||||
|
export async function attest(
|
||||||
|
originChain: ChainId,
|
||||||
|
originAsset: string
|
||||||
|
): Promise<string> {
|
||||||
|
if (originChain === CHAIN_ID_SOLANA) {
|
||||||
|
return attestSolana(originAsset);
|
||||||
|
} else if (originChain === CHAIN_ID_TERRA) {
|
||||||
|
return attestTerra(originAsset);
|
||||||
|
} else {
|
||||||
|
return attestEvm(originChain, originAsset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function attestEvm(
|
||||||
|
originChain: ChainId,
|
||||||
|
originAsset: string
|
||||||
|
): Promise<string> {
|
||||||
|
const signer = getSignerForChain(originChain);
|
||||||
|
const receipt = await attestFromEth(
|
||||||
|
getTokenBridgeAddressForChain(originChain),
|
||||||
|
signer,
|
||||||
|
originAsset
|
||||||
|
);
|
||||||
|
const sequence = parseSequenceFromLogEth(
|
||||||
|
receipt,
|
||||||
|
getBridgeAddressForChain(originChain)
|
||||||
|
);
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function attestTerra(originAsset: string): Promise<string> {
|
||||||
|
//TODO modify bridge_ui to use in-memory signer
|
||||||
|
throw new Error("Unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function attestSolana(originAsset: string): Promise<string> {
|
||||||
|
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||||
|
const payerAddress = keypair.publicKey.toString();
|
||||||
|
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||||
|
const transaction = await attestFromSolana(
|
||||||
|
connection,
|
||||||
|
SOL_BRIDGE_ADDRESS,
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
payerAddress,
|
||||||
|
originAsset
|
||||||
|
);
|
||||||
|
transaction.partialSign(keypair);
|
||||||
|
const txid = await connection.sendRawTransaction(transaction.serialize());
|
||||||
|
await connection.confirmTransaction(txid);
|
||||||
|
const info = await connection.getTransaction(txid);
|
||||||
|
if (!info) {
|
||||||
|
throw new Error("An error occurred while fetching the transaction info");
|
||||||
|
}
|
||||||
|
const sequence = parseSequenceFromLogSolana(info);
|
||||||
|
return sequence;
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
CHAIN_ID_TERRA,
|
||||||
|
getEmitterAddressEth,
|
||||||
|
getEmitterAddressSolana,
|
||||||
|
getEmitterAddressTerra,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
|
import getSignedVAAWithRetry from "@certusone/wormhole-sdk/lib/rpc/getSignedVAAWithRetry";
|
||||||
|
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
|
||||||
|
import {
|
||||||
|
getNFTBridgeAddressForChain,
|
||||||
|
getTokenBridgeAddressForChain,
|
||||||
|
WORMHOLE_RPC_HOSTS,
|
||||||
|
} from "../consts";
|
||||||
|
|
||||||
|
export async function getSignedVAABySequence(
|
||||||
|
chainId: ChainId,
|
||||||
|
sequence: string,
|
||||||
|
isNftTransfer: boolean
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
//Note, if handed a sequence which doesn't exist or was skipped for consensus this will retry until the timeout.
|
||||||
|
const contractAddress = isNftTransfer
|
||||||
|
? getNFTBridgeAddressForChain(chainId)
|
||||||
|
: getTokenBridgeAddressForChain(chainId);
|
||||||
|
const emitterAddress = await nativeAddressToEmitterAddress(
|
||||||
|
chainId,
|
||||||
|
contractAddress
|
||||||
|
);
|
||||||
|
const { vaaBytes } = await getSignedVAAWithRetry(
|
||||||
|
WORMHOLE_RPC_HOSTS,
|
||||||
|
chainId,
|
||||||
|
emitterAddress,
|
||||||
|
sequence,
|
||||||
|
{
|
||||||
|
transport: NodeHttpTransport(), //This should only be needed when running in node.
|
||||||
|
},
|
||||||
|
1000, //retryTimeout
|
||||||
|
1000 //Maximum retry attempts
|
||||||
|
);
|
||||||
|
|
||||||
|
return vaaBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function nativeAddressToEmitterAddress(
|
||||||
|
chainId: ChainId,
|
||||||
|
address: string
|
||||||
|
): Promise<string> {
|
||||||
|
if (chainId === CHAIN_ID_SOLANA) {
|
||||||
|
return await getEmitterAddressSolana(address);
|
||||||
|
} else if (chainId === CHAIN_ID_TERRA) {
|
||||||
|
return await getEmitterAddressTerra(address);
|
||||||
|
} else {
|
||||||
|
return getEmitterAddressEth(address); //Not a mistake, this one is synchronous.
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_BSC,
|
||||||
|
CHAIN_ID_ETH,
|
||||||
|
CHAIN_ID_POLYGON,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
CHAIN_ID_TERRA,
|
||||||
|
postVaaSolana,
|
||||||
|
redeemOnEth,
|
||||||
|
redeemOnSolana,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
|
import { Signer } from "@ethersproject/abstract-signer";
|
||||||
|
import { Connection, Keypair } from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
getNFTBridgeAddressForChain,
|
||||||
|
getSignerForChain,
|
||||||
|
getTokenBridgeAddressForChain,
|
||||||
|
SOLANA_HOST,
|
||||||
|
SOLANA_PRIVATE_KEY,
|
||||||
|
SOL_BRIDGE_ADDRESS,
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
} from "../consts";
|
||||||
|
|
||||||
|
//This function attempts to redeem the VAA on the target chain.
|
||||||
|
export async function redeem(
|
||||||
|
targetChain: ChainId,
|
||||||
|
signedVaa: Uint8Array,
|
||||||
|
isNftTransfer: boolean
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
targetChain === CHAIN_ID_ETH ||
|
||||||
|
targetChain === CHAIN_ID_POLYGON ||
|
||||||
|
targetChain === CHAIN_ID_BSC
|
||||||
|
) {
|
||||||
|
redeemEvm(signedVaa, targetChain, isNftTransfer);
|
||||||
|
} else if (targetChain === CHAIN_ID_SOLANA) {
|
||||||
|
redeemSolana(signedVaa, isNftTransfer);
|
||||||
|
} else if (targetChain === CHAIN_ID_TERRA) {
|
||||||
|
redeemTerra(signedVaa);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function redeemEvm(
|
||||||
|
signedVAA: Uint8Array,
|
||||||
|
targetChain: ChainId,
|
||||||
|
isNftTransfer: boolean
|
||||||
|
) {
|
||||||
|
const signer: Signer = getSignerForChain(targetChain);
|
||||||
|
try {
|
||||||
|
await redeemOnEth(
|
||||||
|
isNftTransfer
|
||||||
|
? getNFTBridgeAddressForChain(targetChain)
|
||||||
|
: getTokenBridgeAddressForChain(targetChain),
|
||||||
|
signer,
|
||||||
|
signedVAA
|
||||||
|
);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function redeemSolana(
|
||||||
|
signedVAA: Uint8Array,
|
||||||
|
isNftTransfer: boolean
|
||||||
|
) {
|
||||||
|
if (isNftTransfer) {
|
||||||
|
//TODO
|
||||||
|
//Solana redemptions require sending metadata to the chain inside of transactions,
|
||||||
|
//and this in not yet available in the sdk.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||||
|
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||||
|
const payerAddress = keypair.publicKey.toString();
|
||||||
|
await postVaaSolana(
|
||||||
|
connection,
|
||||||
|
async (transaction) => {
|
||||||
|
transaction.partialSign(keypair);
|
||||||
|
return transaction;
|
||||||
|
},
|
||||||
|
SOL_BRIDGE_ADDRESS,
|
||||||
|
payerAddress,
|
||||||
|
Buffer.from(signedVAA)
|
||||||
|
);
|
||||||
|
await redeemOnSolana(
|
||||||
|
connection,
|
||||||
|
SOL_BRIDGE_ADDRESS,
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
payerAddress,
|
||||||
|
signedVAA
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function redeemTerra(signedVAA: Uint8Array) {
|
||||||
|
//TODO adapt bridge_ui implementation to use in-memory terra key
|
||||||
|
}
|
|
@ -0,0 +1,186 @@
|
||||||
|
import {
|
||||||
|
ChainId,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
CHAIN_ID_TERRA,
|
||||||
|
hexToUint8Array,
|
||||||
|
nativeToHexString,
|
||||||
|
parseSequenceFromLogEth,
|
||||||
|
parseSequenceFromLogSolana,
|
||||||
|
transferFromEth,
|
||||||
|
transferFromEthNative,
|
||||||
|
transferFromSolana,
|
||||||
|
transferNativeSol,
|
||||||
|
} from "@certusone/wormhole-sdk";
|
||||||
|
import { parseUnits } from "@ethersproject/units";
|
||||||
|
import { Connection, Keypair } from "@solana/web3.js";
|
||||||
|
import {
|
||||||
|
getBridgeAddressForChain,
|
||||||
|
getSignerForChain,
|
||||||
|
getTokenBridgeAddressForChain,
|
||||||
|
SOLANA_HOST,
|
||||||
|
SOLANA_PRIVATE_KEY,
|
||||||
|
SOL_BRIDGE_ADDRESS,
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
} from "../consts";
|
||||||
|
|
||||||
|
/*
|
||||||
|
This function transfers the given token and returns the resultant sequence number, which is then used to retrieve the
|
||||||
|
VAA from the guardians.
|
||||||
|
*/
|
||||||
|
export async function transferTokens(
|
||||||
|
sourceChain: ChainId,
|
||||||
|
amount: string,
|
||||||
|
targetChain: ChainId,
|
||||||
|
sourceAddress: string,
|
||||||
|
recipientAddress: string,
|
||||||
|
isNativeAsset: boolean,
|
||||||
|
assetAddress?: string,
|
||||||
|
decimals?: number
|
||||||
|
): Promise<string> {
|
||||||
|
//TODO support native assets,
|
||||||
|
//TODO set relayer fee,
|
||||||
|
if (sourceChain === CHAIN_ID_SOLANA) {
|
||||||
|
return transferSolana(
|
||||||
|
amount,
|
||||||
|
targetChain,
|
||||||
|
sourceAddress,
|
||||||
|
recipientAddress,
|
||||||
|
isNativeAsset,
|
||||||
|
assetAddress,
|
||||||
|
decimals
|
||||||
|
);
|
||||||
|
} else if (sourceChain === CHAIN_ID_TERRA) {
|
||||||
|
return transferTerra(
|
||||||
|
amount,
|
||||||
|
targetChain,
|
||||||
|
recipientAddress,
|
||||||
|
isNativeAsset,
|
||||||
|
assetAddress,
|
||||||
|
decimals
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return transferEvm(
|
||||||
|
sourceChain,
|
||||||
|
amount,
|
||||||
|
targetChain,
|
||||||
|
recipientAddress,
|
||||||
|
isNativeAsset,
|
||||||
|
assetAddress,
|
||||||
|
decimals
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function transferSolana(
|
||||||
|
amount: string,
|
||||||
|
targetChain: ChainId,
|
||||||
|
sourceAddress: string,
|
||||||
|
recipientAddress: string,
|
||||||
|
isNativeAsset: boolean,
|
||||||
|
assetAddress?: string,
|
||||||
|
decimals?: number
|
||||||
|
): Promise<string> {
|
||||||
|
if (isNativeAsset) {
|
||||||
|
decimals = 9;
|
||||||
|
} else if (!assetAddress || !decimals) {
|
||||||
|
throw new Error("No token specified for transfer.");
|
||||||
|
}
|
||||||
|
const keypair = Keypair.fromSecretKey(SOLANA_PRIVATE_KEY);
|
||||||
|
const payerAddress = keypair.publicKey.toString();
|
||||||
|
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||||
|
const amountParsed = parseUnits(amount, decimals).toBigInt();
|
||||||
|
const hexString = nativeToHexString(recipientAddress, targetChain);
|
||||||
|
if (!hexString) {
|
||||||
|
throw new Error("Invalid recipient");
|
||||||
|
}
|
||||||
|
const vaaCompatibleAddress = hexToUint8Array(hexString);
|
||||||
|
const promise = isNativeAsset
|
||||||
|
? transferNativeSol(
|
||||||
|
connection,
|
||||||
|
SOL_BRIDGE_ADDRESS,
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
payerAddress,
|
||||||
|
amountParsed,
|
||||||
|
vaaCompatibleAddress,
|
||||||
|
targetChain
|
||||||
|
)
|
||||||
|
: transferFromSolana(
|
||||||
|
connection,
|
||||||
|
SOL_BRIDGE_ADDRESS,
|
||||||
|
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||||
|
payerAddress, //Actual SOL fee paying address
|
||||||
|
sourceAddress, //SPL token account
|
||||||
|
assetAddress as string,
|
||||||
|
amountParsed,
|
||||||
|
vaaCompatibleAddress,
|
||||||
|
targetChain
|
||||||
|
//TODO support non-wormhole assets here.
|
||||||
|
// originAddress,
|
||||||
|
// originChain
|
||||||
|
);
|
||||||
|
const transaction = await promise;
|
||||||
|
transaction.partialSign(keypair);
|
||||||
|
const txid = await connection.sendRawTransaction(transaction.serialize());
|
||||||
|
await connection.confirmTransaction(txid);
|
||||||
|
const info = await connection.getTransaction(txid);
|
||||||
|
if (!info) {
|
||||||
|
throw new Error("An error occurred while fetching the transaction info");
|
||||||
|
}
|
||||||
|
const sequence = parseSequenceFromLogSolana(info);
|
||||||
|
return sequence;
|
||||||
|
}
|
||||||
|
export async function transferTerra(
|
||||||
|
amount: string,
|
||||||
|
targetChain: ChainId,
|
||||||
|
recipientAddress: string,
|
||||||
|
isNativeAsset: boolean,
|
||||||
|
assetAddress?: string,
|
||||||
|
decimals?: number
|
||||||
|
): Promise<string> {
|
||||||
|
//TODO modify bridge_ui to use in-memory signer
|
||||||
|
throw new Error("Unimplemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function transferEvm(
|
||||||
|
sourceChain: ChainId,
|
||||||
|
amount: string,
|
||||||
|
targetChain: ChainId,
|
||||||
|
recipientAddress: string,
|
||||||
|
isNativeAsset: boolean,
|
||||||
|
assetAddress?: string,
|
||||||
|
decimals?: number
|
||||||
|
): Promise<string> {
|
||||||
|
if (isNativeAsset) {
|
||||||
|
decimals = 18;
|
||||||
|
} else if (!assetAddress || !decimals) {
|
||||||
|
throw new Error("No token specified for transfer.");
|
||||||
|
}
|
||||||
|
const amountParsed = parseUnits(amount, decimals);
|
||||||
|
const signer = getSignerForChain(sourceChain);
|
||||||
|
const hexString = nativeToHexString(recipientAddress, targetChain);
|
||||||
|
if (!hexString) {
|
||||||
|
throw new Error("Invalid recipient");
|
||||||
|
}
|
||||||
|
const vaaCompatibleAddress = hexToUint8Array(hexString);
|
||||||
|
const receipt = isNativeAsset
|
||||||
|
? await transferFromEthNative(
|
||||||
|
getTokenBridgeAddressForChain(sourceChain),
|
||||||
|
signer,
|
||||||
|
amountParsed,
|
||||||
|
targetChain,
|
||||||
|
vaaCompatibleAddress
|
||||||
|
)
|
||||||
|
: await transferFromEth(
|
||||||
|
getTokenBridgeAddressForChain(sourceChain),
|
||||||
|
signer,
|
||||||
|
assetAddress as string,
|
||||||
|
amountParsed,
|
||||||
|
targetChain,
|
||||||
|
vaaCompatibleAddress
|
||||||
|
);
|
||||||
|
|
||||||
|
return await parseSequenceFromLogEth(
|
||||||
|
receipt,
|
||||||
|
getBridgeAddressForChain(sourceChain)
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
|
||||||
|
import { setDefaultWasm } from "@certusone/wormhole-sdk/lib/solana/wasm";
|
||||||
|
import {
|
||||||
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||||
|
Token,
|
||||||
|
TOKEN_PROGRAM_ID,
|
||||||
|
} from "@solana/spl-token";
|
||||||
|
import { PublicKey } from "@solana/web3.js";
|
||||||
|
import { relay } from "./basicRelayer";
|
||||||
|
import {
|
||||||
|
ETH_TEST_WALLET_PUBLIC_KEY,
|
||||||
|
SOLANA_TEST_TOKEN,
|
||||||
|
SOLANA_TEST_WALLET_PUBLIC_KEY,
|
||||||
|
} from "./consts";
|
||||||
|
/*
|
||||||
|
The goal of this example program is to demonstrate a common Wormhole token bridge
|
||||||
|
use-case.
|
||||||
|
|
||||||
|
*/
|
||||||
|
import { attest } from "./core/attestation";
|
||||||
|
import { getSignedVAABySequence } from "./core/guardianQuery";
|
||||||
|
import { redeem } from "./core/redeem";
|
||||||
|
import { transferTokens } from "./core/transfer";
|
||||||
|
setDefaultWasm("node");
|
||||||
|
|
||||||
|
/*
|
||||||
|
This example attests a test token on Solana, retrieves the resulting VAA, and then submits it
|
||||||
|
to Ethereum, thereby registering the token on Ethereum.
|
||||||
|
*/
|
||||||
|
export async function attestationExample() {
|
||||||
|
const sequenceNumber = await attest(CHAIN_ID_SOLANA, SOLANA_TEST_TOKEN);
|
||||||
|
|
||||||
|
const signedVaa = await getSignedVAABySequence(
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
sequenceNumber,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
await redeem(CHAIN_ID_ETH, signedVaa, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function transferWithRelayHandoff() {
|
||||||
|
const sourceAddress = (
|
||||||
|
await Token.getAssociatedTokenAddress(
|
||||||
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||||
|
TOKEN_PROGRAM_ID,
|
||||||
|
new PublicKey(SOLANA_TEST_TOKEN),
|
||||||
|
new PublicKey(SOLANA_TEST_WALLET_PUBLIC_KEY)
|
||||||
|
)
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
const sequenceNumber = await transferTokens(
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
"1.0",
|
||||||
|
CHAIN_ID_ETH,
|
||||||
|
sourceAddress,
|
||||||
|
ETH_TEST_WALLET_PUBLIC_KEY,
|
||||||
|
false,
|
||||||
|
SOLANA_TEST_TOKEN,
|
||||||
|
9
|
||||||
|
);
|
||||||
|
|
||||||
|
await relay(CHAIN_ID_SOLANA, sequenceNumber, false);
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { exit } from "process";
|
||||||
|
import * as examples from "../lib/examples";
|
||||||
|
|
||||||
|
function logWrapper(promise) {
|
||||||
|
return promise.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runAll() {
|
||||||
|
console.log("Attestation example");
|
||||||
|
await logWrapper(examples.attestationExample());
|
||||||
|
console.log("Attestation complete.");
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
console.log("Transfer example");
|
||||||
|
await logWrapper(examples.transferWithRelayHandoff());
|
||||||
|
console.log("Transfer example complete.");
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
console.log("Complete");
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
let done = false;
|
||||||
|
runAll().then(
|
||||||
|
() => (done = true),
|
||||||
|
() => (done = true)
|
||||||
|
);
|
||||||
|
function wait() {
|
||||||
|
if (!done) {
|
||||||
|
setTimeout(wait, 1000);
|
||||||
|
} else {
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wait();
|
|
@ -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