Skip to content

Commit

Permalink
Fixed Algorand tokenBridge.redeem to check storage lsigs properly (#187)
Browse files Browse the repository at this point in the history
Co-authored-by: Ben Guidarelli <ben.guidarelli@gmail.com>
  • Loading branch information
SilentRhetoric and barnjamin authored Dec 30, 2023
1 parent abb89fd commit 4e6312a
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 42 deletions.
4 changes: 2 additions & 2 deletions connect/src/protocols/tokenTransfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ import {
WormholeTransfer,
} from "../wormholeTransfer";

type TokenTransferProtocol = "TokenBridge" | "AutomaticTokenBridge";
type TokenTransferVAA = TokenBridge.TransferVAA | AutomaticTokenBridge.VAA;
export type TokenTransferProtocol = "TokenBridge" | "AutomaticTokenBridge";
export type TokenTransferVAA = TokenBridge.TransferVAA | AutomaticTokenBridge.VAA;

export class TokenTransfer<N extends Network = Network>
implements WormholeTransfer<TokenTransferProtocol>
Expand Down
9 changes: 5 additions & 4 deletions examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"scripts": {
"wrapped": "tsx src/createWrapped.ts",
"tb": "tsx src/tokenBridge.ts",
"mtb": "cd ../platforms/algorand/protocols/tokenBridge && npm run rebuild && cd - && tsx src/multiTokenBridge.ts",
"cctp": "tsx src/cctp.ts",
"demo": "tsx src/index.ts",
"cosmos": "tsx src/cosmos.ts",
Expand All @@ -48,7 +49,8 @@
},
"devDependencies": {
"dotenv": "^16.3.1",
"tsx": "^4.7.0"
"tsx": "^4.7.0",
"algosdk": "^2.7.0"
},
"dependencies": {
"@wormhole-foundation/connect-sdk": "^0.3.0-beta.7",
Expand All @@ -63,7 +65,6 @@
"@wormhole-foundation/connect-sdk-cosmwasm-tokenbridge": "^0.3.0-beta.7",
"@wormhole-foundation/connect-sdk-evm-cctp": "^0.3.0-beta.7",
"@wormhole-foundation/connect-sdk-solana-cctp": "^0.3.0-beta.7",
"@wormhole-foundation/connect-sdk-cosmwasm-ibc": "^0.3.0-beta.7",
"algosdk": "^2.7.0"
"@wormhole-foundation/connect-sdk-cosmwasm-ibc": "^0.3.0-beta.7"
}
}
}
37 changes: 25 additions & 12 deletions examples/src/createWrapped.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import { TokenId, Wormhole, signSendWait } from "@wormhole-foundation/connect-sdk";
import { AlgorandPlatform } from "@wormhole-foundation/connect-sdk-algorand";
import { SolanaPlatform } from "@wormhole-foundation/connect-sdk-solana";
import { getStuff } from "./helpers";
import { inspect } from "util";

import "@wormhole-foundation/connect-sdk-algorand-tokenbridge";
// Import the platform specific packages
import { EvmPlatform } from "@wormhole-foundation/connect-sdk-evm";
import { SolanaPlatform } from "@wormhole-foundation/connect-sdk-solana";
import { AlgorandPlatform } from "@wormhole-foundation/connect-sdk-algorand";

// Register the protocols
import "@wormhole-foundation/connect-sdk-evm-tokenbridge";
import "@wormhole-foundation/connect-sdk-solana-tokenbridge";
import "@wormhole-foundation/connect-sdk-algorand-tokenbridge";

(async function () {
const wh = new Wormhole("Testnet", [AlgorandPlatform, SolanaPlatform]);
const wh = new Wormhole("Testnet", [EvmPlatform, SolanaPlatform, AlgorandPlatform]);

// Original Token to Attest
const token: TokenId = Wormhole.chainAddress("Algorand", "10458941");
// const token: TokenId = Wormhole.chainAddress(
// "Solana",
// "9rU2jFrzA5zDDmt9yR7vEABvXCUNJ1YgGigdTb9oCaTv",
// );
const token: TokenId = Wormhole.chainAddress(
"Avalanche",
"0x3bE4bce46442F5E85c47257145578E724E40cF97",
);

// grab context and signer
const origChain = wh.getChain(token.chain);
Expand All @@ -20,9 +33,8 @@ import "@wormhole-foundation/connect-sdk-solana-tokenbridge";
// you should set this value to the txid logged in the previous run
let txid = undefined;
// txid = "0x55127b9c8af46aaeea9ef28d8bf91e1aff920422fc1c9831285eb0f39ddca2fe";

txid = "FPNHIFFUZDVPT5SATZQZZ7DFGZMPCCHEFBCB5EZQJV4RRK3ZYTVA";
txid = "GWZU432ERFU3NES4MA7IAAP6DX73F5VRSSIWGJVC5JRHOH6UMWEQ";
// txid = "FPNHIFFUZDVPT5SATZQZZ7DFGZMPCCHEFBCB5EZQJV4RRK3ZYTVA";
// txid = "GWZU432ERFU3NES4MA7IAAP6DX73F5VRSSIWGJVC5JRHOH6UMWEQ";

if (!txid) {
// create attestation from origin chain, the same VAA
Expand All @@ -33,6 +45,7 @@ import "@wormhole-foundation/connect-sdk-solana-tokenbridge";
Wormhole.parseAddress(origSigner.chain(), origSigner.address()),
);
const txids = await signSendWait(origChain, attestTxns, origSigner);
console.log("txids: ", inspect(txids, { depth: null }));
txid = txids[0].txid;
console.log("Created attestation (save this): ", txid);
}
Expand All @@ -51,7 +64,7 @@ import "@wormhole-foundation/connect-sdk-solana-tokenbridge";
// Check if its attested and if not
// submit the attestation to the token bridge on the
// destination chain
const chain = "Solana";
const chain = "Algorand";
const destChain = wh.getChain(chain);
const { signer } = await getStuff(destChain);

Expand All @@ -61,12 +74,12 @@ import "@wormhole-foundation/connect-sdk-solana-tokenbridge";
// try to get the wrapped version, an error here likely means
// its not been attested
const wrapped = await tb.getWrappedAsset(token);
console.log("already wrapped");
console.log("Already wrapped");
return { chain, address: wrapped };
} catch (e) {}

// no wrapped asset, needs to be attested
console.log("attesting asset");
console.log("Attesting asset");
await signSendWait(
destChain,
tb.submitAttestation(vaa, Wormhole.parseAddress(signer.chain(), signer.address())),
Expand All @@ -87,5 +100,5 @@ import "@wormhole-foundation/connect-sdk-solana-tokenbridge";
} while (true);
}

console.log("wrapped: ", await waitForIt());
console.log("Wrapped: ", await waitForIt());
})();
17 changes: 17 additions & 0 deletions examples/src/helpers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ import {
api,
tasks,
DEFAULT_TASK_TIMEOUT,
Chain,
TokenId,
TransferReceipt,
} from "@wormhole-foundation/connect-sdk";

// Importing from src so we dont have to rebuild to see debug stuff in signer
import { getEvmSigner } from "@wormhole-foundation/connect-sdk-evm/src/testing";
import { getSolanaSigner } from "@wormhole-foundation/connect-sdk-solana/src/testing";
import { getCosmwasmSigner } from "@wormhole-foundation/connect-sdk-cosmwasm/src/testing";
import { getAlgorandSigner } from "@wormhole-foundation/connect-sdk-algorand/src/testing";
import { TokenTransferProtocol } from "@wormhole-foundation/connect-sdk";

// read in from `.env`
require("dotenv").config();
Expand All @@ -34,6 +38,19 @@ function getEnv(key: string): string {
return val;
}

export interface BridgingInputs {
fromChain: Chain;
asset: TokenId | "native";
quantity: string;
toChain: Chain;
roundTrip?: boolean;
recoverTxId?: string | undefined;
}

export interface BridgingResult extends BridgingInputs {
result: TransferReceipt<TokenTransferProtocol>;
}

export interface TransferStuff<
N extends Network,
P extends Platform,
Expand Down
214 changes: 214 additions & 0 deletions examples/src/multiTokenBridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import {
Chain,
Network,
Platform,
TokenId,
TokenTransfer,
Wormhole,
isTokenId,
normalizeAmount,
} from "@wormhole-foundation/connect-sdk";
import { BridgingInputs, BridgingResult, TransferStuff, getStuff, waitLog } from "./helpers";
import { inspect } from "util";

// Import the platform specific packages
import { EvmPlatform } from "@wormhole-foundation/connect-sdk-evm";
import { SolanaPlatform } from "@wormhole-foundation/connect-sdk-solana";
import { AlgorandPlatform } from "@wormhole-foundation/connect-sdk-algorand";

// Register the protocols
import "@wormhole-foundation/connect-sdk-evm-tokenbridge";
import "@wormhole-foundation/connect-sdk-solana-tokenbridge";
import "@wormhole-foundation/connect-sdk-algorand-tokenbridge";

(async function multipleBridges() {
const scenarios: BridgingInputs[] = [
// Example input set showing all parameters with recoverTxId as optional
// {
// fromChain: "Avalanche",
// asset: Wormhole.chainAddress("Avalanche", "0x3bE4bce46442F5E85c47257145578E724E40cF97"),
// quantity: "0.0001",
// toChain: "Algorand",
// roundTrip: false,
// recoverTxId: "0xa4e0a2c1c994fe3298b5646dfd5ce92596dc1a589f42e241b7f07501a5a5a39f",
// },
{
fromChain: "Algorand",
asset: "native",
quantity: "0.0001",
toChain: "Solana",
roundTrip: true,
},
{
fromChain: "Algorand",
asset: Wormhole.chainAddress("Algorand", "10458941"),
quantity: "0.0001",
toChain: "Solana",
roundTrip: true,
},
{
fromChain: "Solana",
asset: "native",
quantity: "0.0001",
toChain: "Algorand",
roundTrip: true,
},
{
fromChain: "Solana",
asset: Wormhole.chainAddress("Solana", "3Ftc5hTz9sG4huk79onufGiebJNDMZNL8HYgdMJ9E7JR"), // Wrapped AVAX
quantity: "0.0001",
toChain: "Algorand",
roundTrip: true,
},
];

// Init Wormhole object, passing config for which network
// to use (e.g. Mainnet/Testnet) and what Platforms to support
const wh = new Wormhole("Testnet", [EvmPlatform, SolanaPlatform, AlgorandPlatform]);

const results: BridgingResult[] = await Promise.all(
scenarios.map(async (scenario: BridgingInputs) => {
// Grab chain Contexts -- these hold a reference to a cached rpc client
const sendChain = wh.getChain(scenario.fromChain);
const receiveChain = wh.getChain(scenario.toChain);

// Get signer from local key
// Anything that implements Signer interface (e.g. wrapper around web wallet) should also work
const source = await getStuff(sendChain);
const destination = await getStuff(receiveChain);

// A TokenId is just a `{chain, address}` pair and an alias for ChainAddress
// The `address` field must be a parsed address.
// You can get a TokenId (or ChainAddress) prepared for you
// by calling the static `chainAddress` method on the Wormhole class.
// e.g.
// const token = Wormhole.chainAddress("Avalanche", "0xd00ae08403B9bbb9124bB305C09058E32C39A48c"); // TokenId<"Avalanche">
// "Native" is a shortcut to allow transferring the native gas token
const token = scenario.asset;

// Normalized given token decimals later but can just pass bigints as base units
// Note: The Token bridge will dedust past 8 decimals
// this means any amount specified past that point will be returned
// to the caller
const amount = scenario.quantity;

// Used to normalize the amount to account for the tokens decimals
const decimals = isTokenId(scenario.asset)
? await wh.getDecimals(scenario.asset.chain, scenario.asset.address)
: BigInt(sendChain.config.nativeTokenDecimals);

// With automatic set to true, perform an automatic transfer. This will invoke a relayer
// contract intermediary that knows to pick up the transfers
// With automatic set to false, perform a manual transfer from source to destination
// of the token
// On the destination side, a wrapped version of the token will be minted
// to the address specified in the transfer VAA
const automatic = false;

// The automatic relayer has the ability to deliver some native gas funds to the destination account
// The amount specified for native gas will be swapped for the native gas token according
// to the swap rate provided by the contract, denominated in native gas tokens
const nativeGas = automatic ? "0.01" : undefined;

// Set this to the transfer txid of the initiating transaction to recover a token transfer
// and attempt to fetch details about its progress.
const recoverTxid = scenario.recoverTxId;

// Finally create and perform the transfer given the parameters set above
const xfer = !recoverTxid
? // Perform the token transfer
await tokenTransfer(
wh,
{
token,
amount: normalizeAmount(amount, decimals),
source,
destination,
delivery: {
automatic,
nativeGas: nativeGas ? normalizeAmount(nativeGas, decimals) : undefined,
},
},
scenario.roundTrip,
)
: // Recover the transfer from the originating txid
await TokenTransfer.from(wh, {
chain: source.chain.chain,
txid: recoverTxid,
});

const receipt = await waitLog(wh, xfer);
return { ...scenario, result: receipt };
}),
);

console.log("Results: ", inspect(results, { depth: null }));
})();

async function tokenTransfer<N extends Network>(
wh: Wormhole<N>,
route: {
token: TokenId | "native";
amount: bigint;
source: TransferStuff<N, Platform, Chain>;
destination: TransferStuff<N, Platform, Chain>;
delivery?: {
automatic: boolean;
nativeGas?: bigint;
};
payload?: Uint8Array;
},
roundTrip?: boolean,
): Promise<TokenTransfer<N>> {
// Create a TokenTransfer object to track the state of
// the transfer over time
const xfer = await wh.tokenTransfer(
route.token,
route.amount,
route.source.address,
route.destination.address,
route.delivery?.automatic ?? false,
route.payload,
route.delivery?.nativeGas,
);

const quote = await TokenTransfer.quoteTransfer(
route.source.chain,
route.destination.chain,
xfer.transfer,
);
console.log("quote: ", inspect(quote, { depth: null }));

if (xfer.transfer.automatic && quote.destinationToken.amount < 0)
throw "The amount requested is too low to cover the fee and any native gas requested.";

// 1) Submit the transactions to the source chain, passing a signer to sign any txns
console.log("Starting transfer");
const srcTxids = await xfer.initiateTransfer(route.source.signer);
console.log(`Started transfer: `, srcTxids);

// If automatic, we're done
if (route.delivery?.automatic) return xfer;

// 2) wait for the VAA to be signed and ready (not required for auto transfer)
console.log("Getting Attestation");
const attestIds = await xfer.fetchAttestation(60_000);
console.log(`Got Attestation: `, attestIds);

// 3) redeem the VAA on the dest chain
console.log("Completing Transfer");
const destTxids = await xfer.completeTransfer(route.destination.signer);
console.log(`Completed Transfer: `, destTxids);

// No need to send back, dip
if (!roundTrip) return xfer;

const { destinationToken: token } = quote;
return await tokenTransfer(wh, {
...route,
token: token.token,
amount: token.amount,
source: route.destination,
destination: route.source,
});
}
Loading

0 comments on commit 4e6312a

Please sign in to comment.