@@ -11,6 +11,7 @@ import {
1111 ChainsConfig ,
1212 Contracts ,
1313 isNative ,
14+ serialize ,
1415 TokenAddress ,
1516 TokenId ,
1617 toNative ,
@@ -30,34 +31,31 @@ import "@wormhole-foundation/sdk-evm-core";
3031import {
3132 MultiTokenNtt ,
3233 Ntt ,
33- NttTransceiver ,
3434 TrimmedAmount ,
35- WormholeNttTransceiver ,
3635} from "@wormhole-foundation/sdk-definitions-ntt" ;
37- import { ethers , type Provider } from "ethers" ;
38- import { EvmNttWormholeTranceiver } from "./ntt.js" ;
39- import { Wormhole } from "@wormhole-foundation/sdk-connect" ;
36+ import { Contract , ethers , Interface , type Provider } from "ethers" ;
4037import {
4138 GmpManagerBindings ,
4239 loadAbiVersion ,
40+ MultiTokenNttBindings ,
4341 MultiTokenNttManagerBindings ,
4442} from "./multiTokenNttBindings.js" ;
4543import {
4644 decodeTrimmedAmount ,
4745 EncodedTrimmedAmount ,
4846 untrim ,
4947} from "./trimmedAmount.js" ;
48+ import { getAxelarGasFee } from "./axelar.js" ;
49+ import { encoding } from "@wormhole-foundation/sdk-base" ;
5050
5151export class EvmMultiTokenNtt < N extends Network , C extends EvmChains >
5252 implements MultiTokenNtt < N , C >
5353{
5454 readonly chainId : bigint ;
55-
56- manager : MultiTokenNttManagerBindings . NttManager ;
57- gmpManager : GmpManagerBindings . GmpManager ;
58-
59- xcvrs : EvmNttWormholeTranceiver < N , C > [ ] ;
60- managerAddress : string ;
55+ readonly abiBindings : MultiTokenNttBindings ;
56+ readonly managerAddress : string ;
57+ readonly manager : MultiTokenNttManagerBindings . NttManager ;
58+ readonly gmpManager : GmpManagerBindings . GmpManager ;
6159
6260 constructor (
6361 readonly network : N ,
@@ -73,39 +71,19 @@ export class EvmMultiTokenNtt<N extends Network, C extends EvmChains>
7371 chain
7472 ) as bigint ;
7573
76- this . managerAddress = contracts . multiTokenNtt . manager ;
74+ this . abiBindings = loadAbiVersion ( this . version ) ;
7775
78- const abiBindings = loadAbiVersion ( this . version ) ;
76+ this . managerAddress = contracts . multiTokenNtt . manager ;
7977
80- this . manager = abiBindings . NttManager . connect (
78+ this . manager = this . abiBindings . NttManager . connect (
8179 contracts . multiTokenNtt . manager ,
8280 this . provider
8381 ) ;
8482
85- this . gmpManager = abiBindings . GmpManager . connect (
83+ this . gmpManager = this . abiBindings . GmpManager . connect (
8684 contracts . multiTokenNtt . gmpManager ,
8785 this . provider
8886 ) ;
89-
90- if ( contracts . multiTokenNtt . transceiver . wormhole ) {
91- this . xcvrs = [
92- // Enable more Transceivers here
93- new EvmNttWormholeTranceiver (
94- // TODO: make this compatible
95- // @ts -ignore
96- this ,
97- contracts . multiTokenNtt . transceiver . wormhole ,
98- abiBindings !
99- ) ,
100- ] ;
101- } else {
102- this . xcvrs = [ ] ;
103- }
104- }
105-
106- async getTransceiver ( ix : number ) : Promise < NttTransceiver < N , C , any > | null > {
107- // TODO: should we make an RPC call here, or just trust that the xcvrs are set up correctly?
108- return this . xcvrs [ ix ] || null ;
10987 }
11088
11189 async isPaused ( ) : Promise < boolean > {
@@ -170,20 +148,6 @@ export class EvmMultiTokenNtt<N extends Network, C extends EvmChains>
170148 ) ;
171149 }
172150
173- encodeOptions (
174- options : MultiTokenNtt . TransferOptions
175- ) : Ntt . TransceiverInstruction [ ] {
176- const ixs : Ntt . TransceiverInstruction [ ] = [ ] ;
177-
178- // TODO: add comment about how if you want to use relaying, then use the executor route
179- ixs . push ( {
180- index : 0 ,
181- payload : this . xcvrs [ 0 ] ! . encodeFlags ( { skipRelay : true } ) ,
182- } ) ;
183-
184- return ixs ;
185- }
186-
187151 static async getVersion (
188152 provider : ethers . Provider ,
189153 contracts : Contracts & { multiTokenNtt ?: MultiTokenNtt . Contracts }
@@ -209,13 +173,110 @@ export class EvmMultiTokenNtt<N extends Network, C extends EvmChains>
209173 }
210174 }
211175
176+ async getSendTransceivers ( destinationChain : Chain ) {
177+ const sendTransceivers =
178+ await this . gmpManager . getSendTransceiversWithIndicesForChain (
179+ toChainId ( destinationChain )
180+ ) ;
181+
182+ return await Promise . all (
183+ sendTransceivers . map ( async ( transceiver ) => {
184+ const type = await this . getTransceiverType ( transceiver . transceiver ) ;
185+ return {
186+ address : transceiver . transceiver ,
187+ index : Number ( transceiver . index ) ,
188+ type,
189+ } ;
190+ } )
191+ ) ;
192+ }
193+
194+ async getReceiveTransceivers ( sourceChain : Chain ) {
195+ const receiveTransceivers =
196+ await this . gmpManager . getReceiveTransceiversWithIndicesForChain (
197+ toChainId ( sourceChain )
198+ ) ;
199+
200+ return await Promise . all (
201+ receiveTransceivers . map ( async ( transceiver ) => {
202+ const type = await this . getTransceiverType ( transceiver . transceiver ) ;
203+ return {
204+ address : transceiver . transceiver ,
205+ index : Number ( transceiver . index ) ,
206+ type,
207+ } ;
208+ } )
209+ ) ;
210+ }
211+
212+ private async getTransceiverType (
213+ transceiverAddress : string
214+ ) : Promise < string > {
215+ const transceiverInterface = new Interface ( [
216+ "function getTransceiverType() external view returns (string memory)" ,
217+ ] ) ;
218+
219+ const transceiverContract = new Contract (
220+ transceiverAddress ,
221+ transceiverInterface ,
222+ this . provider
223+ ) ;
224+
225+ return await transceiverContract
226+ . getFunction ( "getTransceiverType" )
227+ . staticCall ( ) ;
228+ }
229+
230+ async createTransceiverInstructions (
231+ dstChain : Chain ,
232+ gasLimit : bigint
233+ ) : Promise < Ntt . TransceiverInstruction [ ] > {
234+ const sendTransceivers = await this . getSendTransceivers ( dstChain ) ;
235+
236+ const instructions : Ntt . TransceiverInstruction [ ] = await Promise . all (
237+ sendTransceivers . map ( async ( transceiver ) => {
238+ if ( transceiver . type . toLowerCase ( ) === "wormhole" ) {
239+ return {
240+ index : transceiver . index ,
241+ payload : new Uint8Array ( [ 1 ] ) , // skipRelay = true
242+ } ;
243+ } else if ( transceiver . type . toLowerCase ( ) === "axelar" ) {
244+ // If we fail to fetch the axelar gas fee, then use 0 as a fallback
245+ // The user will need to manually top up the axelar gas fee later
246+ let gasFee = 0n ;
247+ try {
248+ gasFee = await getAxelarGasFee (
249+ this . network ,
250+ this . chain ,
251+ dstChain ,
252+ gasLimit
253+ ) ;
254+ } catch { }
255+ return {
256+ index : transceiver . index ,
257+ payload : encoding . bignum . toBytes ( gasFee , 32 ) ,
258+ } ;
259+ } else {
260+ throw new Error (
261+ `Unsupported transceiver type: ${ transceiver . type } at index ${ transceiver . index } `
262+ ) ;
263+ }
264+ } )
265+ ) ;
266+
267+ // the contract expects the instructions to be sorted by transceiver index
268+ instructions . sort ( ( a , b ) => a . index - b . index ) ;
269+
270+ return instructions ;
271+ }
272+
212273 async quoteDeliveryPrice (
213274 dstChain : Chain ,
214- options : MultiTokenNtt . TransferOptions
275+ instructions : Ntt . TransceiverInstruction [ ]
215276 ) : Promise < bigint > {
216277 const [ , totalPrice ] = await this . gmpManager . quoteDeliveryPrice (
217278 toChainId ( dstChain ) ,
218- Ntt . encodeTransceiverInstructions ( this . encodeOptions ( options ) )
279+ Ntt . encodeTransceiverInstructions ( instructions )
219280 ) ;
220281 return totalPrice ;
221282 }
@@ -228,18 +289,17 @@ export class EvmMultiTokenNtt<N extends Network, C extends EvmChains>
228289 ) : AsyncGenerator < EvmUnsignedTransaction < N , C > > {
229290 const senderAddress = new EvmAddress ( sender ) . toString ( ) ;
230291
231- // TODO: get rid of this
232- const options = { } ;
292+ const transceiverInstructions = await this . createTransceiverInstructions (
293+ destination . chain ,
294+ 800_000n // TODO: make this configurable
295+ ) ;
233296
234297 const totalPrice = await this . quoteDeliveryPrice (
235298 destination . chain ,
236- options
299+ transceiverInstructions
237300 ) ;
238301
239302 const receiver = universalAddress ( destination ) ;
240- const transceiverInstructions = Ntt . encodeTransceiverInstructions (
241- this . encodeOptions ( options )
242- ) ;
243303
244304 let transferTx ;
245305 if ( isNative ( token ) ) {
@@ -249,7 +309,9 @@ export class EvmMultiTokenNtt<N extends Network, C extends EvmChains>
249309 recipient : receiver ,
250310 refundAddress : receiver ,
251311 shouldQueue : false ,
252- transceiverInstructions,
312+ transceiverInstructions : Ntt . encodeTransceiverInstructions (
313+ transceiverInstructions
314+ ) ,
253315 additionalPayload : "0x" ,
254316 } ;
255317
@@ -288,7 +350,9 @@ export class EvmMultiTokenNtt<N extends Network, C extends EvmChains>
288350 recipient : receiver ,
289351 refundAddress : receiver ,
290352 shouldQueue : false ,
291- transceiverInstructions,
353+ transceiverInstructions : Ntt . encodeTransceiverInstructions (
354+ transceiverInstructions
355+ ) ,
292356 permit : {
293357 permitted : {
294358 token : token . toString ( ) ,
@@ -314,25 +378,27 @@ export class EvmMultiTokenNtt<N extends Network, C extends EvmChains>
314378 ) ;
315379 }
316380
317- async * redeem ( attestations : MultiTokenNtt . Attestation [ ] ) {
318- if ( attestations . length !== this . xcvrs . length )
319- throw new Error (
320- "Not enough attestations for the registered Transceivers"
321- ) ;
381+ // TODO: this only supports redeeming with a Wormhole transceiver for now
382+ async * redeem ( attestation : MultiTokenNtt . Attestation ) {
383+ const transceivers = await this . getReceiveTransceivers (
384+ attestation . emitterChain
385+ ) ;
322386
323- for ( const idx in this . xcvrs ) {
324- const xcvr = this . xcvrs [ idx ] ! ;
325- const attestation = attestations [ idx ] ;
326- if ( attestation ?. payloadName !== "WormholeTransfer" ) {
327- // TODO: support standard relayer attestations
328- // which must be submitted to the delivery provider
329- throw new Error ( "Invalid attestation type for redeem" ) ;
330- }
331- // TODO: deserialize is throwing. casting is fine for now
332- // const serialized = serialize(attestation);
333- // const vaa = deserialize("Ntt:WormholeTransfer", serialized);
334- yield * xcvr . receive ( attestation as unknown as WormholeNttTransceiver . VAA ) ;
387+ const wormholeTransceiver = transceivers . find ( ( t ) => t . type === "wormhole" ) ;
388+ if ( ! wormholeTransceiver ) {
389+ throw new Error ( "No Wormhole transceiver registered for this chain" ) ;
335390 }
391+
392+ const transceiver = this . abiBindings . NttTransceiver . connect (
393+ wormholeTransceiver . address ,
394+ this . provider
395+ ) ;
396+
397+ const tx = await transceiver . receiveMessage . populateTransaction (
398+ serialize ( attestation )
399+ ) ;
400+
401+ yield this . createUnsignedTx ( tx , "NttTransceiver.receiveMessage" ) ;
336402 }
337403
338404 async getTokenMeta ( token : TokenId ) : Promise < MultiTokenNtt . TokenMeta > {
@@ -485,12 +551,12 @@ export class EvmMultiTokenNtt<N extends Network, C extends EvmChains>
485551
486552 if ( localToken === ethers . ZeroAddress ) return null ;
487553
488- return Wormhole . tokenId ( this . chain , localToken ) ;
554+ return { chain : this . chain , address : toNative ( this . chain , localToken ) } ;
489555 }
490556
491557 async getWrappedNativeToken ( ) : Promise < TokenId > {
492558 const wethAddress = await this . manager . WETH ( ) ;
493- return Wormhole . tokenId ( this . chain , wethAddress ) ;
559+ return { chain : this . chain , address : toNative ( this . chain , wethAddress ) } ;
494560 }
495561
496562 async calculateLocalTokenAddress (
0 commit comments