Skip to content
This repository was archived by the owner on Jun 16, 2025. It is now read-only.

Commit 77e0745

Browse files
committed
start implementing evm token router
1 parent 308ce54 commit 77e0745

File tree

14 files changed

+279
-190
lines changed

14 files changed

+279
-190
lines changed

evm/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
}
3131
},
3232
"dependencies": {
33-
"@wormhole-foundation/sdk-base": "^0.7.0-beta.6",
34-
"@wormhole-foundation/sdk-definitions": "^0.7.0-beta.6",
35-
"@wormhole-foundation/sdk-evm": "^0.7.0-beta.6",
33+
"@wormhole-foundation/sdk-base": "^0.7.1-beta.2",
34+
"@wormhole-foundation/sdk-definitions": "^0.7.1-beta.2",
35+
"@wormhole-foundation/sdk-evm": "^0.7.1-beta.2",
3636
"@wormhole-foundation/example-liquidity-layer-definitions": "0.0.1",
3737
"ethers": "^6.5.1"
3838
},

evm/ts/src/TokenRouter/evm.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ChainId, asChainId } from "@wormhole-foundation/sdk-base";
22
import { ethers } from "ethers";
3-
import { Endpoint, OrderResponse, TokenRouter, FastTransferParameters } from ".";
3+
import { Endpoint, OrderResponse, AbstractTokenRouter, FastTransferParameters } from ".";
44
import { LiquidityLayerTransactionResult } from "..";
55
import {
66
ITokenRouter,
@@ -11,7 +11,7 @@ import {
1111
ITokenMessenger,
1212
} from "../types";
1313

14-
export class EvmTokenRouter implements TokenRouter<ethers.ContractTransaction> {
14+
export class TokenRouter implements AbstractTokenRouter<ethers.ContractTransaction> {
1515
contract: ITokenRouter;
1616
circle: ITokenMessenger;
1717

evm/ts/src/TokenRouter/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export type Endpoint = {
1919
mintRecipient: string | Buffer | Uint8Array;
2020
};
2121

22-
export abstract class TokenRouter<PreparedTransactionType extends PreparedInstruction> {
22+
export abstract class AbstractTokenRouter<PreparedTransactionType extends PreparedInstruction> {
2323
abstract get address(): string;
2424

2525
abstract placeMarketOrderTx(

evm/ts/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from "./TokenRouter";
55
export * from "./error";
66
export * from "./messages";
77
export * from "./utils";
8+
export * from "./protocol";
89

910
export * as ethers_types from "./types";
1011

evm/ts/src/protocol/tokenRouter.ts

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { TokenRouter } from "@wormhole-foundation/example-liquidity-layer-definitions";
2-
import { Network, nativeChainIds, toChainId } from "@wormhole-foundation/sdk-base";
2+
import { Network, encoding, toChainId } from "@wormhole-foundation/sdk-base";
33
import {
44
CircleBridge,
55
Contracts,
66
UnsignedTransaction,
77
VAA,
8+
serialize,
89
} from "@wormhole-foundation/sdk-definitions";
910
import {
1011
AnyEvmAddress,
@@ -13,34 +14,30 @@ import {
1314
EvmUnsignedTransaction,
1415
} from "@wormhole-foundation/sdk-evm";
1516
import { ethers } from "ethers";
16-
import { EvmTokenRouter as _EvmTokenRouter } from "../TokenRouter";
17+
import { TokenRouter as _TokenRouter } from "../TokenRouter";
1718

1819
export class EvmTokenRouter<N extends Network, C extends EvmChains>
19-
extends _EvmTokenRouter
20+
extends _TokenRouter
2021
implements TokenRouter<N, C>
2122
{
22-
private _chainId: number;
2323
constructor(
2424
readonly network: N,
2525
readonly chain: C,
2626
readonly provider: ethers.Provider,
2727
readonly contracts: Contracts & TokenRouter.Addresses,
2828
) {
2929
super(provider, contracts.tokenRouter, contracts.cctp.tokenMessenger);
30-
this._chainId = 0; //nativeChainIds.networkChainToNativeChainId(network, chain);
3130
}
3231

33-
async *placeMarketOrder(
34-
sender: AnyEvmAddress,
35-
order: TokenRouter.OrderRequest,
36-
): AsyncGenerator<UnsignedTransaction<N, C>, any, unknown> {
32+
async *placeMarketOrder(sender: AnyEvmAddress, order: TokenRouter.OrderRequest) {
33+
const from = new EvmAddress(sender).unwrap();
3734
const msg = order.redeemerMessage ? order.redeemerMessage : new Uint8Array();
3835

3936
const refundAddress = order.refundAddress
4037
? new EvmAddress(order.refundAddress).unwrap()
4138
: undefined;
4239

43-
const tx = await this.placeMarketOrderTx(
40+
const txReq = await this.placeMarketOrderTx(
4441
order.amountIn,
4542
toChainId(order.targetChain),
4643
order.redeemer.toUint8Array(),
@@ -49,29 +46,30 @@ export class EvmTokenRouter<N extends Network, C extends EvmChains>
4946
refundAddress,
5047
);
5148

52-
yield this.createUnsignedTx(tx, "TokenRouter.placeMarketOrder");
49+
yield this.createUnsignedTx({ ...txReq, from }, "TokenRouter.placeMarketOrder");
5350
}
51+
5452
async *redeemFill(
5553
sender: AnyEvmAddress,
56-
vaa:
57-
| VAA<"FastTransfer:CctpDeposit">
58-
| VAA<"FastTransfer:FastMarketOrder">
59-
| VAA<"FastTransfer:FastFill">,
54+
vaa: VAA<"FastTransfer:CctpDeposit">,
6055
cctp: CircleBridge.Attestation,
61-
): AsyncGenerator<UnsignedTransaction<N, C>, any, unknown> {
62-
throw new Error("Method not implemented.");
56+
) {
57+
const from = new EvmAddress(sender).unwrap();
58+
const txReq = await this.redeemFillTx({
59+
encodedWormholeMessage: serialize(vaa),
60+
circleBridgeMessage: CircleBridge.serialize(cctp.message),
61+
circleAttestation: encoding.hex.decode(cctp.attestation!),
62+
});
63+
yield this.createUnsignedTx({ ...txReq, from }, "TokenRouter.redeemFill");
6364
}
6465

6566
private createUnsignedTx(
66-
txReq: ethers.ContractTransaction,
67+
txReq: ethers.TransactionRequest,
6768
description: string,
6869
parallelizable: boolean = false,
69-
): EvmUnsignedTransaction<N, C> {
70-
//txReq.chainId = this._chainId;
71-
70+
): UnsignedTransaction<N, C> {
7271
return new EvmUnsignedTransaction(
73-
// txReq,
74-
{},
72+
txReq,
7573
this.network,
7674
this.chain,
7775
description,

evm/ts/src/testing/env.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1+
import { TokenRouter } from "@wormhole-foundation/example-liquidity-layer-definitions";
2+
import { Platform } from "@wormhole-foundation/sdk-base";
13
//@ts-ignore
24
import { parse as envParse } from "envfile";
35
import * as fs from "fs";
46

5-
export enum ChainType {
6-
Evm,
7-
Solana,
8-
}
9-
107
export type LiquidityLayerEnv = {
11-
chainType: ChainType;
8+
chainType: Platform;
129
chainId: number;
1310
domain: number;
1411
tokenAddress: string;
@@ -77,16 +74,29 @@ export function parseLiquidityLayerEnvFile(envPath: string): LiquidityLayerEnv {
7774
};
7875
}
7976

77+
export function toContractAddresses(env: LiquidityLayerEnv): TokenRouter.Addresses {
78+
return {
79+
tokenRouter: env.tokenRouterAddress,
80+
matchingEngine: env.tokenMessengerAddress,
81+
coreBridge: env.wormholeAddress,
82+
cctp: {
83+
tokenMessenger: env.tokenMessengerAddress,
84+
// TODO
85+
messageTransmitter: "",
86+
usdcMint: "",
87+
wormhole: "",
88+
wormholeRelayer: "",
89+
},
90+
};
91+
}
92+
8093
function parseChainType(chainType: string) {
8194
switch (chainType) {
82-
case "evm": {
83-
return ChainType.Evm;
84-
}
85-
case "solana": {
86-
return ChainType.Solana;
87-
}
88-
default: {
95+
case "evm":
96+
return "Evm";
97+
case "solana":
98+
return "Solana";
99+
default:
89100
throw new Error(`invalid chain type: ${chainType}`);
90-
}
91101
}
92102
}

evm/ts/src/testing/utils.ts

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,78 @@
1-
import { ethers } from "ethers";
1+
import { ethers, isError } from "ethers";
22
import { IERC20 } from "../types";
33
import { IUSDC__factory } from "../types/factories/IUSDC__factory";
44
import { WALLET_PRIVATE_KEYS } from "./consts";
55
import { EvmMatchingEngine } from "..";
6-
import { Chain } from "@wormhole-foundation/sdk-base";
7-
import { toUniversal } from "@wormhole-foundation/sdk-definitions";
6+
import { signAndSendWait } from "@wormhole-foundation/sdk-connect";
7+
import { Chain, Network } from "@wormhole-foundation/sdk-base";
8+
import {
9+
SignAndSendSigner,
10+
UnsignedTransaction,
11+
toUniversal,
12+
} from "@wormhole-foundation/sdk-definitions";
13+
import { EvmChains, EvmNativeSigner } from "@wormhole-foundation/sdk-evm";
814

915
export interface ScoreKeeper {
1016
player: ethers.NonceManager;
1117
bid: bigint;
1218
balance: bigint;
1319
}
1420

21+
export function getSdkSigner<C extends EvmChains>(
22+
fromChain: C,
23+
wallet: ethers.Wallet,
24+
): SdkSigner<Network, C> {
25+
// @ts-ignore -- TODO, add peer dep to sdk-evm package for ethers
26+
return new SdkSigner(fromChain, wallet.address, wallet);
27+
}
28+
29+
export class SdkSigner<N extends Network, C extends EvmChains>
30+
extends EvmNativeSigner<N, C>
31+
implements SignAndSendSigner<N, C>
32+
{
33+
get provider() {
34+
return this._signer.provider as unknown as ethers.JsonRpcProvider;
35+
}
36+
get wallet() {
37+
return this._signer as unknown as ethers.Wallet;
38+
}
39+
40+
async signAndSend(txs: UnsignedTransaction<N, C>[]): Promise<string[]> {
41+
const txids: string[] = [];
42+
for (let tx of txs) {
43+
for (let x = 0; x < 3; x++) {
44+
try {
45+
const res = await this._signer.sendTransaction(tx.transaction);
46+
txids.push(res.hash);
47+
break;
48+
} catch (e) {
49+
if (
50+
isError(e, "CALL_EXCEPTION") &&
51+
"info" in e &&
52+
e.info!.error.message === "nonce too low"
53+
) {
54+
const nonce = await this.wallet.getNonce();
55+
console.log("Setting nonce to ", nonce);
56+
tx.transaction.nonce = nonce;
57+
continue;
58+
}
59+
throw e;
60+
}
61+
}
62+
}
63+
64+
await mine(this.provider);
65+
await this.provider.waitForTransaction(txids[0], 1, 5000);
66+
return txids;
67+
}
68+
}
69+
70+
export const sleep = async (seconds: number) =>
71+
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
72+
73+
export const nonceManagedWallet = (key: string, provider: ethers.Provider) =>
74+
new ethers.NonceManager(new ethers.Wallet(key, provider));
75+
1576
export async function mine(provider: ethers.JsonRpcProvider) {
1677
await provider.send("evm_mine", []);
1778
}
@@ -54,6 +115,14 @@ export async function mineToPenaltyPeriod(
54115
await mineMany(provider, Number(blocksToMine));
55116
}
56117

118+
export async function signSendMineWait<N extends Network, C extends EvmChains>(
119+
txs: AsyncGenerator<UnsignedTransaction<N, C>, void, unknown>,
120+
signer: SdkSigner<N, C>,
121+
) {
122+
const txids = await signAndSendWait(txs, signer);
123+
return await signer.provider.waitForTransaction(txids[0].txid);
124+
}
125+
57126
export async function mineWait(provider: ethers.JsonRpcProvider, tx: ethers.TransactionResponse) {
58127
await mine(provider);
59128
// 1 is default confirms, 5000ms timeout to prevent hanging forever.

evm/ts/tests/01__registration.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
ValidNetwork,
88
MATCHING_ENGINE_NAME,
99
parseLiquidityLayerEnvFile,
10-
ChainType,
1110
LiquidityLayerEnv,
1211
} from "../src/testing";
1312
import { expect } from "chai";
@@ -103,7 +102,7 @@ function fetchTokenRouterEndpoint(
103102
): [Uint8Array, Uint8Array] {
104103
const formattedAddress = toUniversal(chainName, targetEnv.tokenRouterAddress).toUint8Array();
105104
let formattedMintRecipient;
106-
if (targetEnv.chainType === ChainType.Evm) {
105+
if (targetEnv.chainType === "Evm") {
107106
formattedMintRecipient = formattedAddress;
108107
} else {
109108
if (targetEnv.tokenRouterMintRecipient === undefined) {

0 commit comments

Comments
 (0)