EVM: Attesting a Token

Attesting a token from EVM is fairly simple, and usually done via the Portal UI since it's a step that only needs to happen once per Token.

If for whatever reason you need to do it programmatically, you can also do it using the JS SDK:

The first step is to create an AttestMeta VAA. We do this by calling attestFromEth() function from the JS SDK and passing in the Token Bridge address, a Ethers signer object, and the address of the Token we want to attest:

const networkTokenAttestation = await attestFromEth(
    network.tokenBridgeAddress, // Token Bridge Address
    signer, //Private Key to sign and pay for TX + RPC Endpoint
    network.testToken //Token Address

Anyone can attest any token on the network.

To complete the Attestation, we grab the VAA that the attestFromEth() function generates by getting the Emitter address of the Token Bridge and the Sequence from the logs of the transaction receipt. We then fetch against a guardian REST endpoint. It could take a couple seconds (up to 30s!) for the guardian to see and sign the VAA, so it's a good idea to poll the guardian every couple seconds until the VAA is found.

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();
    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();

Next, we submit the VAA onto the target chain to create a wrapped version of the Token by calling createWrapped(). On an EVM chain, this will deploy a Portal Wrapped Token contract who's mint authority is the Portal Token Bridge on that chain. Sometimes this transaction throws an unpredicatable gas price error, so it's a good idea to set a high gas limit.

After the wrapped token is created, you can get the new wrapped token address by calling the wrappedAsset() function of the TokenBridge.

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(
        tryNativeToHexString(network.testToken, "ethereum"),
console.log("Wrapped token created at: ", wrappedTokenAddress);