reworking typescript sdk portion

This commit is contained in:
chase-45 2022-09-22 16:20:32 -04:00
parent d979ed397b
commit 5c7f9af146
13 changed files with 580 additions and 119 deletions

View File

@ -65,13 +65,11 @@
- [Generic Relayers](./technical/relayer/genericRelayer.md)
- [Specialized Relayers](./technical/relayer/pluginRelayers.md)
- [Wormhole Typescript SDK](./development/portal/overview.md)
- [EVM]()
- [Attesting](./development/portal/evm/attestingToken.md)
- [Transfer Tokens](./development/portal/evm/tokenTransfer.md)
- [Portal JS SDK](./development/portal/sdkjs/overview.md)
- [EVM to Solana Transfer](./development/portal/sdkjs/evm-solana-transfer.md)
- [Polygon to Oasis with Relayers](./development/portal/sdkjs/polygon-oasis-relayer.md)
- [Wormhole Typescript SDK](./technical/typescript/overview.md)
- [Token Registration](./technical/typescript/attestingToken.md)
- [Token Transfer Basics](./technical/typescript/tokenTransfer.md)
- [EVM to Solana Transfer](./technical/typescript/evm-solana-transfer.md)
- [Polygon to Oasis with Relayers](./technical/typescript/polygon-oasis-relayer.md)
---

View File

@ -1,6 +1,6 @@
# Portal JS SDK Overview
# Wormhole Typescript SDK Overview
For applications that only need to interact with the Core and Token Bridge contracts off-chain, there is a Wormhole JS SDK provided.
For applications that only need to interact with the Core and Token Bridge contracts off-chain, there is a Wormhole Typescript SDK provided.
It can be installed using npm:

View File

@ -1,10 +1,10 @@
# Polygon to Oasis with Relayers
In this example, well fetch the fee schedule and attach a relayer fee onto our transaction. This is a non-trivial example as well also use Polygon as a source chain, which has some quirks when it comes to gas estimation.
In this example, well fetch the fee schedule and attach a relayer fee onto our transaction. This is a non-trivial example as well also use Polygon as a source chain, which has some quirks when it comes to gas estimation.
NOTE: We're working on streamlining this process, so check back in the future for a much simpler version of this example.
To start, well need a couple of packages:
To start, well need a couple of packages:
```bash
npm i --save @certusone/wormhole-sdk ethers node-fetch
@ -16,15 +16,15 @@ Then, get started writing some code:
import { BigNumber, ethers } from "ethers";
import fetch from "node-fetch";
import {
getEmitterAddressEth,
hexToUint8Array,
nativeToHexString,
parseSequenceFromLogEth,
CHAIN_ID_POLYGON,
CHAIN_ID_OASIS,
transferFromEthNative,
getIsTransferCompletedEth,
setDefaultWasm
getEmitterAddressEth,
hexToUint8Array,
nativeToHexString,
parseSequenceFromLogEth,
CHAIN_ID_POLYGON,
CHAIN_ID_OASIS,
transferFromEthNative,
getIsTransferCompletedEth,
setDefaultWasm,
} from "@certusone/wormhole-sdk";
```
@ -34,56 +34,59 @@ Now, set up the two wallets well be sending and receiving from. While we are
```ts
const EmeraldWallet = new ethers.Wallet(
privatekey_emerald,
new ethers.providers.JsonRpcProvider("https://emerald.oasis.dev")
privatekey_emerald,
new ethers.providers.JsonRpcProvider("https://emerald.oasis.dev")
);
const PolygonWallet = new ethers.Wallet(
privatekey_polygon,
new ethers.providers.JsonRpcProvider("https://polygon-rpc.com/")
privatekey_polygon,
new ethers.providers.JsonRpcProvider("https://polygon-rpc.com/")
);
```
### Fetch the fee schedule
Fetch the fee schedule for the Portal Token Bridge relayer. This fee schedule outlines the minimum fee for each recipient chain that the relayer will accept. As long as we attach at least that fee in the relayer fee, we can be fairly confident that the relayer will pick up the transaction and relay it to the recipient chain. The fee will cover the gas cost for the relayer along with a little extra to make it worth their time to run the relayer service.
We will also define the transfer amount in this step. The fee schedule will either return a flat fee in USD for the recipient chain, or a percentage fee (usually only for Ethereum). Either way, well need to calculate the fee in in BigNumber format (no decimals).
Fetch the fee schedule for the token bridge relayers. This fee schedule outlines the minimum fee for each recipient chain that the relayer will accept. As long as we attach at least that fee in the relayer fee, we can be fairly confident that the relayer will pick up the transaction and relay it to the recipient chain. The fee will cover the gas cost for the relayer along with a little extra to make it worth their time to run the relayer service.
For example, 1 MATIC on Polygon is 1e18 wei, or 1000000000000000000 wei. Because EVM has a hard time with floating point math, we have to do all our transactions in this small unit, to avoid decimal numbers.
We will also define the transfer amount in this step. The fee schedule will either return a flat fee in USD for the recipient chain, or a percentage fee (usually only for Ethereum). Either way, well need to calculate the fee in in BigNumber format (no decimals).
For example, 1 MATIC on Polygon is 1e18 wei, or 1000000000000000000 wei. Because EVM has a hard time with floating point math, we have to do all our transactions in this small unit, to avoid decimal numbers.
```ts
const transferAmount = BigNumber.from("1000000000000000000"); // We are sending 1 MATIC over the wall to Oasis
const relayerFeeSchedule = await (await fetch(
"https://raw.githubusercontent.com/certusone/wormhole-relayer-list/main/relayer.json"
)).json();
const relayerFeeSchedule = await(
await fetch(
"https://raw.githubusercontent.com/certusone/wormhole-relayer-list/main/relayer.json"
)
).json();
```
The fee schedule has the following interface:
```ts
export interface RelayerFeeSchedule {
supportedTokens: ChainAddress[];
relayers: Relayer[];
feeSchedule: FeeSchedule;
supportedTokens: ChainAddress[];
relayers: Relayer[];
feeSchedule: FeeSchedule;
}
interface ChainAddress {
chainId: number;
address: string;
coingeckoId: string;
chainId: number;
address: string;
coingeckoId: string;
}
interface Relayer {
name: string;
url: string;
name: string;
url: string;
}
interface FeeSchedule {
[chainId: string]: {
type: "flat" | "percent";
feeUsd?: number;
feePercent?: number;
gasEstimate?: number;
};
[chainId: string]: {
type: "flat" | "percent";
feeUsd?: number;
feePercent?: number;
gasEstimate?: number;
};
}
```
@ -92,70 +95,65 @@ After fetching the fee schedule, find the fee in wei that needs to be paid to th
```ts
let feeWei: number;
if (relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].type == "flat") {
const feeUsd = relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].feeUsd
const MATIC_PRICE = (
await (
await fetch(
"https://api.coingecko.com/api/v3/simple/token_price/polygon-pos?contract_addresses=0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270&vs_currencies=usd"
)
).json()
)["0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270"]["usd"];
const feeUsd = relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].feeUsd;
const MATIC_PRICE = await(
await fetch(
"https://api.coingecko.com/api/v3/simple/token_price/polygon-pos?contract_addresses=0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270&vs_currencies=usd"
)
).json()["0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270"]["usd"];
feeWei = (feeUsd / MATIC_PRICE) * 1e18;
feeWei = (feeUsd / MATIC_PRICE) * 1e18;
} else if (relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].type == "percent") {
let feeWei = (relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].feePercent /100) * transferAmount.toNumber();
let feeWei =
(relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].feePercent / 100) *
transferAmount.toNumber();
}
```
### Add override for gas estimation for Polygon
When the source chain is Polygon, there's an additional step to overestimate the gas. This is because Ethers library has some problems with fee estimation after EIP-1559.
```ts
let overrides;
let feeData = await PolygonWallet.provider.getFeeData();
overrides = {
maxFeePerGas: feeData.maxFeePerGas?.mul(50) || undefined,
maxPriorityFeePerGas:
feeData.maxPriorityFeePerGas?.mul(50) || undefined,
maxFeePerGas: feeData.maxFeePerGas?.mul(50) || undefined,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas?.mul(50) || undefined,
};
```
### Emit Portal Message
Now we have all the pieces we need to emit a Portal Bridge message with a relay fee attached. We do this using the transferFromEthNative() method. EthNative is used because were transferring the native token of the Polygon network rather than an ERC20 token.
### Emit Token Bridge Message
Now we have all the pieces we need to emit a token bridge message with a relay fee attached. We do this using the transferFromEthNative() method. EthNative is used because were transferring the native token of the Polygon network rather than an ERC20 token.
```ts
const POLYGON_TOKEN_BRIDGE = "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE";
const receipt = await transferFromEthNative(
POLYGON_TOKEN_BRIDGE,
PolygonWallet,
transferAmount,
CHAIN_ID_OASIS,
hexToUint8Array(
nativeToHexString(
await EmeraldWallet.getAddress(),
CHAIN_ID_OASIS
) || ""
),
BigNumber.from(feeWei.toString()),
overrides
POLYGON_TOKEN_BRIDGE,
PolygonWallet,
transferAmount,
CHAIN_ID_OASIS,
hexToUint8Array(
nativeToHexString(await EmeraldWallet.getAddress(), CHAIN_ID_OASIS) || ""
),
BigNumber.from(feeWei.toString()),
overrides
);
console.log("Receipt: ", receipt);
const POLYGON_CORE_BRIDGE_ADDRESS =
"0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7";
const sequence = parseSequenceFromLogEth(
receipt,
POLYGON_CORE_BRIDGE_ADDRESS
);
"0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7";
const sequence = parseSequenceFromLogEth(receipt, POLYGON_CORE_BRIDGE_ADDRESS);
const emitterAddress = getEmitterAddressEth(POLYGON_TOKEN_BRIDGE);
console.log("Sequence: ", sequence);
console.log("EmitterAddress: ", emitterAddress);
```
Lets walk through each of the arguments of this function and what they mean.
Lets walk through each of the arguments of this function and what they mean.
`POLYGON_TOKEN_BRIDGE` is the address of the Portal Token Bridge on the Polygon network. You can find it and other addresses on the Deployment Info page.
`POLYGON_TOKEN_BRIDGE` is the address of the token bridge module on the Polygon network. You can find it and other addresses on the Deployment Info page.
`PolygonWallet` is a signer you get from the Ethers library that holds a private key that can sign transactions.
@ -165,7 +163,7 @@ Lets walk through each of the arguments of this function and what they mean.
`hexToUint8Array()` translates the target publickey into a wormhole public key.
`BigNumber.from(feeWei.toString())` identifies the fee in smallest unit of the network for the relayer.
`BigNumber.from(feeWei.toString())` identifies the fee in smallest unit of the network for the relayer.
`overrides` are used if we need to override the gas cost, which we need to do for Polygon.
@ -178,17 +176,15 @@ await new Promise((r) => setTimeout(r, 900000)); //15m in seconds
const WORMHOLE_RPC = "https://wormhole-v2-mainnet-api.certus.one";
let vaaBytes = undefined;
while (!vaaBytes) {
try {
vaaBytes = (
await (
await fetch(
`${WORMHOLE_RPC}/v1/signed_vaa/${CHAIN_ID_POLYGON}/${emitterAddress}/${sequence}`
)
).json()
).vaaBytes;
} catch (e) {
await new Promise((r) => setTimeout(r, 5000));
}
try {
vaaBytes = await(
await fetch(
`${WORMHOLE_RPC}/v1/signed_vaa/${CHAIN_ID_POLYGON}/${emitterAddress}/${sequence}`
)
).json().vaaBytes;
} catch (e) {
await new Promise((r) => setTimeout(r, 5000));
}
}
console.log("VAA Bytes: ", vaaBytes);
```
@ -201,17 +197,17 @@ In the final step, use the getIsTransferCompletedEth() method to check if the tr
setDefaultWasm("node"); //only needed if running in node.js
const EMERALD_TOKEN_BRIDGE = "0x5848C791e09901b40A9Ef749f2a6735b418d7564";
let transferCompleted = await getIsTransferCompletedEth(
EMERALD_TOKEN_BRIDGE,
EmeraldWallet.provider,
vaaBytes
);
while (!transferCompleted) {
await new Promise((r) => setTimeout(r, 5000));
transferCompleted = await getIsTransferCompletedEth(
EMERALD_TOKEN_BRIDGE,
EmeraldWallet.provider,
vaaBytes
);
while (!transferCompleted) {
await new Promise((r) => setTimeout(r, 5000));
transferCompleted = await getIsTransferCompletedEth(
EMERALD_TOKEN_BRIDGE,
EmeraldWallet.provider,
vaaBytes
);
);
}
console.log("VAA Relayed!");

View File

@ -1,17 +1,30 @@
# Contracts and Accounts
The devnet environment deploys the Wormhole and Portal contracts to each of the chains at the same addresses every time. It also provides specific wallets with funds.
The devnet environment deploys the core layer and token bridge to each of the chains at the same addresses every time. It also provides specific wallets with funds.
## Tilt
## Guardian
- REST Port: 7071
- gRPC Port: 7070
## ETH0
- RPC Port: 8545
## ETH1
- RPC Port: 8546
## Solana
- RPC Port: 8899
## Algorand
- RPC Port:
- RPC Port:
## Terra
- RPC Port:
- RPC Port:

View File

@ -1,8 +1,8 @@
# Tilt Development Environment
For a faster development cycle, especially when developing blockchain programs that interact with Wormhole or Portal contracts, consider setting up the Tilt Devnet Environment.
For a faster development cycle, especially when developing blockchain programs that interact with Wormhole contracts, consider setting up the Tilt Devnet Environment.
Tilt is a Kubernetes and Docker orchestration tool that will spin up all the Wormhole supported chains in containers, alongside a Guardian node that will observe and store VAAs.
Tilt is a Kubernetes and Docker orchestration tool that will spin up all the Wormhole supported chains in containers, alongside a Guardian node that will observe and store VAAs.
This devnet environment can be set up on your computer or in a Linux VM that has at least 4 CPU cores and 16GB of RAM.
@ -15,22 +15,25 @@ tilt up --host=0.0.0.0 -- --webHost=0.0.0.0
While the exact commands for each environment might differ, the basic setup process for Tilt is the following:
1. Install Go
2. Install Docker Desktop (Or Docker CE)
a. Install Minikube if Docker CE
2. Install Docker Desktop (Or Docker CE)
a. Install Minikube if Docker CE
3. Install Tilt
4. Clone Wormhole Repo and Tilt Up
## FAQ
## FAQ
### Where are Fantom/Celo/Polygon/other EVM chains?
For all chains that support EVM, the smart contract development environment is effectively the same. For changes in gas costs and transaction times, consider testing contract logic on devnet and then using testnet environments to get chain-specific answers.
For all chains that support EVM, the smart contract development environment is effectively the same. For changes in gas costs and transaction times, consider testing contract logic on devnet and then using testnet environments to get chain-specific answers.
### Solana is taking forever
Due to Solana's architecture, it often takes 25-40min to build the Solana pod. Consider increasing CPU cores assigned to devnet for a faster build.
### Solana program deploy doesn't work
Kubernetes doesn't currently allow port forwarding for UDP ports, which is what Solana uses for `solana program deploy`. Instead, we recommend using [Solana Deployer](https://github.com/acheroncrypto/solana-deployer). Not only does this deploy programs over regular RPC (thus bypassing UDP port requirements), it's also much faster than `solana program deploy`.
### Reset state for a pod
If you want to quickly iterate and don't want to bring Tilt down and back up, you can reset state for a pod by clicking the 🔄 button next to the pod name in Tilt UI.
If you want to quickly iterate and don't want to bring Tilt down and back up, you can reset state for a pod by clicking the 🔄 button next to the pod name in Tilt UI.

View File

@ -20,9 +20,9 @@ ITokenBridge token_bridge = ITokenBridge(wormhole_token_bridge_address);
Attesting a token from EVM needs to happen once per token. If a token is not attested, it will not be claimable until so. However, there are no restrictions to reattesting a token; doing so will update the metadata.
It is not advised to attest tokens on-chain for most usecases. To attest a token by an off-chain process, you can either do it by hand through one of the Token Bridge UIs (for example [Portal](https://www.portalbridge.com/#/register)) or using the [JS SDK](https://www.npmjs.com/package/@certusone/wormhole-sdk).
It is not advised to attest tokens on-chain for most usecases. To attest a token by an off-chain process, you can either do it by hand through one of the Token Bridge UIs (for example [Portal](https://www.portalbridge.com/#/register)) or using the [Typescript SDK](https://www.npmjs.com/package/@certusone/wormhole-sdk).
_[Here](../../development/portal/evm/attestingToken.md) is an example of how to attest a token using the JS SDK._
_[Here](../../development/portal/evm/attestingToken.md) is an example of how to attest a token using the Typescript SDK._
## Basic Transfer

View File

@ -0,0 +1,62 @@
# Registering Tokens
Registering tokens with the token bridge can be done from any supported blockchain, and only needs to be done once - globally - per token. This is is typically done via a UI (such as the [Portal UI](portalbridge.com)) rather than done on-chain.
If you need to do it programmatically, you can also use the Typescript SDK to attest a token:
The first step is to create an AttestMeta VAA. We do this by calling `attest()` function from the SDK and passing in the Token Bridge address, and the address of the Token we want to attest.
For example, here is the code to produce an attestation VAA using ethers:
```js
const networkTokenAttestation = await attestFromEth(
network.tokenBridgeAddress, // Token Bridge Address
signer, //Private Key to sign and pay for TX + RPC Endpoint
network.testToken //Token Address
);
```
The attestation transaction will produce a signed VAA. This signed VAA is necessary in order to register the tokens on other chains.
In order to retrieve the VAA, you will need the `emitterAddress` of the Token Bridge and the `sequence` from the logs of the transaction receipt.
With those, you can fetch the VAA from any Guardian REST endpoint. It could take a moment (up to 30 seconds) for the Guardian to see and sign the VAA, so it's a good idea to poll the Guardian every few seconds until the VAA is found.
Here is a relatively compact piece of code which is able to fetch **any** VAA, given an emitter address and sequence number.
```js
const emitterAddr = getEmitterAddressEth(network.tokenBridgeAddress);
const seq = parseSequenceFromLogEth(
networkTokenAttestation,
network.bridgeAddress
);
const vaaURL = `${config.wormhole.restAddress}/v1/signed_vaa/${network.wormholeChainId}/${emitterAddr}/${seq}`;
console.log("Searching for: ", vaaURL);
let vaaBytes = await (await fetch(vaaURL)).json();
while (!vaaBytes.vaaBytes) {
console.log("VAA not found, retrying in 5s!");
await new Promise((r) => setTimeout(r, 5000)); //Timeout to let Guardiand pick up log and have VAA ready
vaaBytes = await (await fetch(vaaURL)).json();
}
```
Lastly, we submit the VAA onto the target chain to create a wrapped version of the token. This is accomplished by calling `createWrapped()`.
You can get the new wrapped token address by calling the `wrappedAsset()` function of the TokenBridge.
Here is how this can be accomplished using Ethers:
```js
await targetTokenBridge.createWrapped(
Buffer.from(vaaBytes.vaaBytes, "base64"),
{
gasLimit: 2000000,
}
);
await new Promise((r) => setTimeout(r, 5000)); //Time out to let block propogate
const wrappedTokenAddress = await targetTokenBridge.wrappedAsset(
network.wormholeChainId,
Buffer.from(tryNativeToHexString(network.testToken, "ethereum"), "hex")
);
console.log("Wrapped token created at: ", wrappedTokenAddress);
```

View File

@ -0,0 +1,109 @@
# EVM to Solana Token Transfer
A defining feature of cross chain apps (xDapps) is the ability to move tokens from one chain to another, even if those blockchains have radically different virtual machine models.
To demonstrate, lets do a simple programmatic transfer from Eth to Solana.
First, determine the address on Solana where we're sending the tokens. Unlike EVM chains where the wallet address is used, we need to send the tokens to the recipient's associated token account for that token. We'll use a couple helper functions from the Wormhole Typescript SDK to make this easier.
```ts
import {
Token,
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
import {
getForeignAssetSolana,
hexToUint8Array,
nativeToHexString,
CHAIN_ID_ETH,
} from "@certusone/wormhole-sdk";
const SOLANA_TOKEN_BRIDGE_ADDRESS =
"wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb";
// determine destination address - an associated token account
const solanaMintKey = new PublicKey(
(await getForeignAssetSolana(
connection,
SOLANA_TOKEN_BRIDGE_ADDRESS,
CHAIN_ID_ETH,
hexToUint8Array(nativeToHexString(tokenAddress, CHAIN_ID_ETH) || "")
)) || ""
);
const recipientAddress = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
solanaMintKey,
recipientWalletAddress
);
```
After we have the receipt token account on Solana, it's time to submit the transfer message on Ethereum. This will output a log that contains a sequence number (a unique number for the message) and an emitter address (the ETH Token Bridge Address in Wormhole format). The sequence number and emitter address will be used to fetch the VAA after its been signed by Guardians.
```ts
import {
trasnferFromEth,
parseSequenceFromLogEth,
getEmitterAddressEth,
CHAIN_ID_SOLANA,
} from "@certusone/wormhole-sdk";
const ETH_TOKEN_BRIDGE_ADDRESS = "0x3ee18B2214AFF97000D974cf647E7C347E8fa585";
// Submit transaction - results in a Wormhole message being published
const receipt = await transferFromEth(
ETH_TOKEN_BRIDGE_ADDRESS,
signer,
tokenAddress,
amount,
CHAIN_ID_SOLANA,
recipientAddress
);
// Get the sequence number and emitter address required to fetch the signedVAA of our message
const sequence = parseSequenceFromLogEth(receipt, ETH_BRIDGE_ADDRESS);
const emitterAddress = getEmitterAddressEth(ETH_TOKEN_BRIDGE_ADDRESS);
```
Once the Guardians have signed the token bridge VAA, it needs to be retrieved from the Guardian Network. This time we'll use the Guardian GRPC endpoint, though the REST endpoint used in previous sections works as well.
```ts
import { getSignedVAA } from "@certusone/wormhole-sdk";
// Fetch the signedVAA from the Wormhole Network (this may require retries while you wait for confirmation)
const { signedVAA } = await getSignedVAA(
WORMHOLE_RPC_HOST,
CHAIN_ID_ETH,
emitterAddress,
sequence
);
```
Now, we post the VAA to Solana in order to mint the wrapped tokens. Because of the compute limit on Solana, we split the signature verification and token claim into steps. To do that, verify all the signatures and create a claim account for the token.
```ts
const SOL_BRIDGE_ADDRESS = "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth";
await postVaaSolana(
connection, // Solana Mainnet Connection
wallet, //Solana Wallet Signer
SOL_BRIDGE_ADDRESS,
payerAddress,
signedVAA
);
```
Finally, claim the tokens:
```ts
const transaction = await redeemOnSolana(
connection,
SOL_BRIDGE_ADDRESS,
SOL_TOKEN_BRIDGE_ADDRESS,
payerAddress,
signedVAA,
isSolanaNative,
mintAddress
);
const signed = await wallet.signTransaction(transaction);
const txid = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction(txid);
```

View File

@ -0,0 +1,13 @@
# Wormhole Typescript SDK
For applications that only need to interact with the Core and Token Bridge contracts off-chain, there is a Wormhole Typescript SDK provided.
It can be installed using npm:
```sh
npm i @certusone/wormholesdk
```
An explanation of the key concepts of using the Typescript SDK will be outlined in the following section, as well as some examples. For more examples with a more exhaustive coverage of all the supported blockchains in Wormhole, be sure to check the [official codebase](https://github.com/wormhole-foundation/wormhole/tree/dev.v2/sdk/js) for the Typescript SDK.
Virtually all functions of the SDK are demonstrated in the [reference bridge UI](https://github.com/wormhole-foundation/example-token-bridge-ui), which makes it an excellent source of example code as well.

View File

@ -0,0 +1,210 @@
# Polygon to Oasis with Relayers
In this example, well utilize the token bridge relayer network to complete our token transfer.
This code is written for a browser environment. If you're working in node, consider using node-fetch:
```bash
npm i --save @certusone/wormhole-sdk ethers node-fetch
```
```ts
import { BigNumber, ethers } from "ethers";
import fetch from "node-fetch";
import {
getEmitterAddressEth,
hexToUint8Array,
nativeToHexString,
parseSequenceFromLogEth,
CHAIN_ID_POLYGON,
CHAIN_ID_OASIS,
transferFromEthNative,
getIsTransferCompletedEth,
setDefaultWasm,
} from "@certusone/wormhole-sdk";
```
### Setup the Polygon and Oasis Wallets
Now, set up the two wallets well be sending and receiving from. While we are instantiating both wallets with their private keys, we only need the Public key of the receiving wallet for this example.
```ts
const EmeraldWallet = new ethers.Wallet(
privatekey_emerald,
new ethers.providers.JsonRpcProvider("https://emerald.oasis.dev")
);
const PolygonWallet = new ethers.Wallet(
privatekey_polygon,
new ethers.providers.JsonRpcProvider("https://polygon-rpc.com/")
);
```
### Fetch the fee schedule
Fetch the fee schedule for the token bridge relayers. This fee schedule outlines the minimum fee for each recipient chain that relayers will accept. As long as we attach at least that fee in the relayer fee, we can expect a relayer pick up the transaction and relay it to the recipient chain. The fee will cover the gas cost for the relayer along with a little extra to make it worth their time to run the relayer service.
We will also define the transfer amount in this step. The fee schedule will either return a flat fee in USD for the recipient chain, or a percentage fee (usually only for Ethereum). Either way, well need to calculate the fee in in BigNumber format (no decimals).
```ts
const transferAmount = BigNumber.from("1000000000000000000"); // We are sending 1 MATIC over the wall to Oasis
const relayerFeeSchedule = await(
await fetch(
"https://raw.githubusercontent.com/certusone/wormhole-relayer-list/main/relayer.json"
)
).json();
```
The fee schedule has the following interface:
```ts
export interface RelayerFeeSchedule {
supportedTokens: ChainAddress[];
relayers: Relayer[];
feeSchedule: FeeSchedule;
}
interface ChainAddress {
chainId: number;
address: string;
coingeckoId: string;
}
interface Relayer {
name: string;
url: string;
}
interface FeeSchedule {
[chainId: string]: {
type: "flat" | "percent";
feeUsd?: number;
feePercent?: number;
gasEstimate?: number;
};
}
```
After fetching the fee schedule, find the fee in wei that needs to be paid to the Relayer. At the time of writing, Oasis has a flat fee of $0.50, so to calculate how much MATIC we need to pay for the $0.50 fee, we need to fetch the MATIC price. To do that, use the free CoinGecko api:
```ts
let feeWei: number;
if (relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].type == "flat") {
const feeUsd = relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].feeUsd;
const MATIC_PRICE = await(
await fetch(
"https://api.coingecko.com/api/v3/simple/token_price/polygon-pos?contract_addresses=0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270&vs_currencies=usd"
)
).json()["0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270"]["usd"];
feeWei = (feeUsd / MATIC_PRICE) * 1e18;
} else if (relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].type == "percent") {
let feeWei =
(relayerFeeSchedule.feeSchedule[CHAIN_ID_OASIS].feePercent / 100) *
transferAmount.toNumber();
}
```
### Overrides & Quirks
Dependent on the specific blockchains you are working with, you may need to perform special actions when submitting this transaction. Because we're dealing with Polygon in this example, there's an additional step to overestimate the gas. This is because Ethers library has some problems with fee estimation after EIP-1559.
```ts
let overrides;
let feeData = await PolygonWallet.provider.getFeeData();
overrides = {
maxFeePerGas: feeData.maxFeePerGas?.mul(50) || undefined,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas?.mul(50) || undefined,
};
```
### Emit Token Bridge Message
Now we have all the pieces we need to emit a token bridge message with a relay fee attached. We do this using the transferFromEthNative() method. EthNative is used because were transferring the native token of the Polygon network rather than an ERC20 token.
```ts
const POLYGON_TOKEN_BRIDGE = "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE";
const receipt = await transferFromEthNative(
POLYGON_TOKEN_BRIDGE,
PolygonWallet,
transferAmount,
CHAIN_ID_OASIS,
hexToUint8Array(
nativeToHexString(await EmeraldWallet.getAddress(), CHAIN_ID_OASIS) || ""
),
BigNumber.from(feeWei.toString()),
overrides
);
console.log("Receipt: ", receipt);
const POLYGON_CORE_BRIDGE_ADDRESS =
"0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7";
const sequence = parseSequenceFromLogEth(receipt, POLYGON_CORE_BRIDGE_ADDRESS);
const emitterAddress = getEmitterAddressEth(POLYGON_TOKEN_BRIDGE);
console.log("Sequence: ", sequence);
console.log("EmitterAddress: ", emitterAddress);
```
Lets walk through each of the arguments of this function and what they mean.
`POLYGON_TOKEN_BRIDGE` is the address of the token bridge module on the Polygon network. You can find it and other addresses on the Deployment Info page.
`PolygonWallet` is a signer you get from the Ethers library that holds a private key that can sign transactions.
`transferAmount` is a BigNumber that contains the amount to transfer in the smallest unit of the network.
`CHAIN_ID_OASIS` is a constant that identifies the target chain.
`hexToUint8Array()` translates the target publickey into a wormhole public key.
`BigNumber.from(feeWei.toString())` identifies the fee in smallest unit of the network for the relayer.
`overrides` are used if we need to override the gas cost, which we need to do for Polygon.
### Check VAA was signed
Wait 15 min for finality on Polygon and then check to see if it was submitted. If successful, youll be able to fetch a base64 encoded vaaBytes. We need this in the next step where we check if the transaction was successfully relayed.
```ts
await new Promise((r) => setTimeout(r, 900000)); //15m in seconds
const WORMHOLE_RPC = "https://wormhole-v2-mainnet-api.certus.one";
let vaaBytes = undefined;
while (!vaaBytes) {
try {
vaaBytes = await(
await fetch(
`${WORMHOLE_RPC}/v1/signed_vaa/${CHAIN_ID_POLYGON}/${emitterAddress}/${sequence}`
)
).json().vaaBytes;
} catch (e) {
await new Promise((r) => setTimeout(r, 5000));
}
}
console.log("VAA Bytes: ", vaaBytes);
```
### Check if the transfer was completed
In the final step, use the getIsTransferCompletedEth() method to check if the transfer was completed on the Oasis Emerald chain. If its not, wait 5 seconds and check again.
```ts
setDefaultWasm("node"); //only needed if running in node.js
const EMERALD_TOKEN_BRIDGE = "0x5848C791e09901b40A9Ef749f2a6735b418d7564";
let transferCompleted = await getIsTransferCompletedEth(
EMERALD_TOKEN_BRIDGE,
EmeraldWallet.provider,
vaaBytes
);
while (!transferCompleted) {
await new Promise((r) => setTimeout(r, 5000));
transferCompleted = await getIsTransferCompletedEth(
EMERALD_TOKEN_BRIDGE,
EmeraldWallet.provider,
vaaBytes
);
}
console.log("VAA Relayed!");
```
Success! You've programmatically relayed a transaction!

View File

@ -0,0 +1,57 @@
# Token Transfers
<!-- //TODO this information should be captured elsewhere One challenge that arises for new EVM developers is that, because EVM uses unsigned integers, there's no concept of decimals. Therefore, tokens usually have up to 18 zeros behind them to denote up to 18 decimal places. Wormhole normalizes this to *eight* zeros, with transfer amounts rounded down to the nearest 8th decimal. -->
Before transferring tokens, you should ensure that the token is [Registered](./attestingToken.md) on the chain you are transferring to, and that any necessary prerequisite steps (such as sending token approvals or creating associated token accounts) have already been done.
For example, you'll likely need to do a standard ERC-20 token approval prior to performing a bridge action if you're in the EVM ecosystem.
```js
// Here we are approving and transfering 50 tokens. The ERC20 token we are transfering has 18 decimal places.
const bridgeAmt = ethers.utils.parseUnits("50", "18");
await treasury.approveTokenBridge(bridgeAmt, {
gasLimit: 2000000,
});
```
Once any prerequisite steps have been handled, simply call `transfer` on the token bridge module to initiate a transfer and create a transfer VAA. Note that the target receipient is a Wormhole-format address (referred to as 'hex' format in the Typescript SDK).
```js
const targetRecepient = Buffer.from(
tryNativeToHexString(targetDeployment.deployedAddress, "ethereum"),
"hex"
);
const tx = await (
await treasury.bridgeToken(
bridgeAmt,
targetNetwork.wormholeChainId,
targetRecepient
)
).wait();
```
If you're not using a relayer, you'll have to submit the target chain transaction yourself. [This section](./polygon-oasis-relayer.md) outlines how to use relayers.
This code shows how to retrieve the VAA. (It's the same code as shown in the previous section.)
```js
const emitterAddr = getEmitterAddressEth(network.tokenBridgeAddress);
const seq = parseSequenceFromLogEth(tx, network.bridgeAddress);
const vaaURL = `${config.wormhole.restAddress}/v1/signed_vaa/${network.wormholeChainId}/${emitterAddr}/${seq}`;
let vaaBytes = await (await fetch(vaaURL)).json();
while (!vaaBytes.vaaBytes) {
console.log("VAA not found, retrying in 5s!");
await new Promise((r) => setTimeout(r, 5000)); //Timeout to let Guardiand pick up log and have VAA ready
vaaBytes = await (await fetch(vaaURL)).json();
}
```
After we've fetched the VAA, we can call the `completeTransfer()` function on the target chain.
```js
const completeTransferTx = await targetTokenBridge.completeTransfer(
Buffer.from(vaaBytes.vaaBytes, "base64")
);
```

View File

@ -48,7 +48,7 @@ Wormhole can expand to new ecosystems as quickly as a Core Contract can be devel
## Scalability
Wormhole scales well, as demonstrated by Portal's ability to handle huge TVL and transaction volume--even during tumultuous events.
Wormhole scales well, as demonstrated by its ability to handle huge TVL and transaction volume--even during tumultuous events.
The requirements for running a Guardian are relatively heavy, as they need to run a full node for every single blockchain in the ecosystem. This is another reason why a limited number of robust validator companies are beneficial for this design.

View File

@ -2,9 +2,9 @@
There is a set of ecosystem contracts that provision Wormhole's xAsset layer which allow tokens to be bridged around the Wormhole Ecosystem in a **path-independent** fashion, and are easily composable with other functions in the Wormhole ecosystem.
This section provides a high-level overview of how to interact with two smart contract modules that implement xAssets: (1) Token Bridge module and (2) NFT Bridge Module.
This section provides a high-level overview of how to interact with two smart contract modules that implement xAssets: (1) Token Bridge module and (2) NFT Bridge Module.
If you're looking to interact with the Token Bridge directly from a typescript client or backend, you should start with the [Wormhole Typescript SDK](https://www.npmjs.com/package/@certusone/wormhole-sdk).
If you're looking to interact with the Token Bridge directly from a typescript client or backend, you should start with the [Wormhole Typescript SDK](https://www.npmjs.com/package/@certusone/wormhole-sdk).
## Creating xAssets
@ -14,7 +14,7 @@ xAssets are all **fungible** with each other. This means the Wormhole-wrapped as
**Tokens**
To convert tokens into an xAsset, an **attestation** must first be created. To create an attestation, simply call the **attest** function on the Portal contract of the origin chain.
To convert tokens into an xAsset, an **attestation** must first be created. To create an attestation, simply call the **attest** function on the token bridge contract of the origin chain.
function attestToken(
address tokenAddress,
@ -33,7 +33,7 @@ Calling this function will deploy a new contract for the token on the foreign ch
**NFTs**
NFTs do not need need to be attested before they can be created into a xAsset.
NFTs do not need need to be attested before they can be created into a xAsset.
## Transferring xAssets
@ -54,17 +54,17 @@ All tokens managed by the Token Bridge are backed by the origin asset, allowing
```
**NFTs**
```
function transferNFT(
address token,
uint256 tokenID,
uint16 recipientChain,
bytes32 recipient,
uint256 tokenID,
uint16 recipientChain,
bytes32 recipient,
uint32 nonce) returns (uint64 sequence)
)
```
## Contract-Controlled Transfers
Basic transfers are intended to transfer xAssets from one wallet to another, whereas Contract Controlled Transfers (CCTs) are meant to transfer xAssets from one smart contract to another. If you're writing an xDapp, CCTs will likely be a large component.