@m0-foundation/ntt-sdk-route 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,4 @@
1
+ # M0 Custom NTT Route
2
+
3
+ The package extends `NttAutomaticRoute` to support `transferMLikeToken` function from M0 `Portal` contract
4
+
@@ -0,0 +1,45 @@
1
+ import { Network, routes, Chain, ChainContext, TokenId, Signer, ChainAddress, AccountAddress } from '@wormhole-foundation/sdk-connect';
2
+ import { Ntt } from '@wormhole-foundation/sdk-definitions-ntt';
3
+ import { EvmNtt } from '@wormhole-foundation/sdk-evm-ntt';
4
+ import { EvmChains, EvmUnsignedTransaction } from '@wormhole-foundation/sdk-evm';
5
+ import { NttRoute } from '@wormhole-foundation/sdk-route-ntt';
6
+ import { TransactionRequest } from 'ethers';
7
+
8
+ type Op = NttRoute.Options;
9
+ type Tp = routes.TransferParams<Op>;
10
+ type Vr = routes.ValidationResult<Op>;
11
+ type Vp = NttRoute.ValidatedParams;
12
+ type QR = routes.QuoteResult<Op, Vp>;
13
+ type Q = routes.Quote<Op, Vp>;
14
+ type R = NttRoute.AutomaticTransferReceipt;
15
+ type Contracts = Ntt.Contracts & {
16
+ wrappedMToken: string;
17
+ };
18
+ declare class M0AutomaticRoute<N extends Network> extends routes.AutomaticRoute<N, Op, Vp, R> implements routes.StaticRouteMethods<typeof M0AutomaticRoute> {
19
+ static NATIVE_GAS_DROPOFF_SUPPORTED: boolean;
20
+ static EVM_CONTRACTS: Contracts;
21
+ static meta: {
22
+ name: string;
23
+ provider: string;
24
+ };
25
+ static supportedNetworks(): Network[];
26
+ static supportedChains(network: Network): Chain[];
27
+ static getContracts(chain: Chain): Contracts;
28
+ static supportedSourceTokens(fromChain: ChainContext<Network>): Promise<TokenId[]>;
29
+ static supportedDestinationTokens<N extends Network>(token: TokenId, fromChain: ChainContext<N>, toChain: ChainContext<N>): Promise<TokenId[]>;
30
+ static isProtocolSupported<N extends Network>(chain: ChainContext<N>): boolean;
31
+ getDefaultOptions(): Op;
32
+ isAvailable(request: routes.RouteTransferRequest<N>): Promise<boolean>;
33
+ validate(request: routes.RouteTransferRequest<N>, params: Tp): Promise<Vr>;
34
+ quote(request: routes.RouteTransferRequest<N>, params: Vp): Promise<QR>;
35
+ initiate(request: routes.RouteTransferRequest<N>, signer: Signer, quote: Q, to: ChainAddress): Promise<R>;
36
+ /**
37
+ * Modified from EvmNtt `transfer` function to call `transferMLikeToken` instead
38
+ * https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/ts/src/ntt.ts#L461
39
+ */
40
+ transferMLike<N extends Network, C extends EvmChains>(ntt: EvmNtt<N, C>, sender: AccountAddress<C>, amount: bigint, destination: ChainAddress, sourceToken: string, destinationToken: string, options: Ntt.TransferOptions): AsyncGenerator<EvmUnsignedTransaction<N, C>>;
41
+ createUnsignedTx<N extends Network, C extends EvmChains>(ntt: EvmNtt<N, C>, txReq: TransactionRequest, description: string, parallelizable?: boolean): EvmUnsignedTransaction<N, C>;
42
+ track(receipt: R, timeout?: number): AsyncGenerator<R, void, unknown>;
43
+ }
44
+
45
+ export { M0AutomaticRoute };
@@ -0,0 +1,45 @@
1
+ import { Network, routes, Chain, ChainContext, TokenId, Signer, ChainAddress, AccountAddress } from '@wormhole-foundation/sdk-connect';
2
+ import { Ntt } from '@wormhole-foundation/sdk-definitions-ntt';
3
+ import { EvmNtt } from '@wormhole-foundation/sdk-evm-ntt';
4
+ import { EvmChains, EvmUnsignedTransaction } from '@wormhole-foundation/sdk-evm';
5
+ import { NttRoute } from '@wormhole-foundation/sdk-route-ntt';
6
+ import { TransactionRequest } from 'ethers';
7
+
8
+ type Op = NttRoute.Options;
9
+ type Tp = routes.TransferParams<Op>;
10
+ type Vr = routes.ValidationResult<Op>;
11
+ type Vp = NttRoute.ValidatedParams;
12
+ type QR = routes.QuoteResult<Op, Vp>;
13
+ type Q = routes.Quote<Op, Vp>;
14
+ type R = NttRoute.AutomaticTransferReceipt;
15
+ type Contracts = Ntt.Contracts & {
16
+ wrappedMToken: string;
17
+ };
18
+ declare class M0AutomaticRoute<N extends Network> extends routes.AutomaticRoute<N, Op, Vp, R> implements routes.StaticRouteMethods<typeof M0AutomaticRoute> {
19
+ static NATIVE_GAS_DROPOFF_SUPPORTED: boolean;
20
+ static EVM_CONTRACTS: Contracts;
21
+ static meta: {
22
+ name: string;
23
+ provider: string;
24
+ };
25
+ static supportedNetworks(): Network[];
26
+ static supportedChains(network: Network): Chain[];
27
+ static getContracts(chain: Chain): Contracts;
28
+ static supportedSourceTokens(fromChain: ChainContext<Network>): Promise<TokenId[]>;
29
+ static supportedDestinationTokens<N extends Network>(token: TokenId, fromChain: ChainContext<N>, toChain: ChainContext<N>): Promise<TokenId[]>;
30
+ static isProtocolSupported<N extends Network>(chain: ChainContext<N>): boolean;
31
+ getDefaultOptions(): Op;
32
+ isAvailable(request: routes.RouteTransferRequest<N>): Promise<boolean>;
33
+ validate(request: routes.RouteTransferRequest<N>, params: Tp): Promise<Vr>;
34
+ quote(request: routes.RouteTransferRequest<N>, params: Vp): Promise<QR>;
35
+ initiate(request: routes.RouteTransferRequest<N>, signer: Signer, quote: Q, to: ChainAddress): Promise<R>;
36
+ /**
37
+ * Modified from EvmNtt `transfer` function to call `transferMLikeToken` instead
38
+ * https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/ts/src/ntt.ts#L461
39
+ */
40
+ transferMLike<N extends Network, C extends EvmChains>(ntt: EvmNtt<N, C>, sender: AccountAddress<C>, amount: bigint, destination: ChainAddress, sourceToken: string, destinationToken: string, options: Ntt.TransferOptions): AsyncGenerator<EvmUnsignedTransaction<N, C>>;
41
+ createUnsignedTx<N extends Network, C extends EvmChains>(ntt: EvmNtt<N, C>, txReq: TransactionRequest, description: string, parallelizable?: boolean): EvmUnsignedTransaction<N, C>;
42
+ track(receipt: R, timeout?: number): AsyncGenerator<R, void, unknown>;
43
+ }
44
+
45
+ export { M0AutomaticRoute };
package/dist/index.js ADDED
@@ -0,0 +1,302 @@
1
+ 'use strict';
2
+
3
+ var sdkConnect = require('@wormhole-foundation/sdk-connect');
4
+ var sdkDefinitionsNtt = require('@wormhole-foundation/sdk-definitions-ntt');
5
+ var sdkEvm = require('@wormhole-foundation/sdk-evm');
6
+ var sdkRouteNtt = require('@wormhole-foundation/sdk-route-ntt');
7
+ var ethers = require('ethers');
8
+
9
+ // src/m0AutomaticRoute.ts
10
+ var _M0AutomaticRoute = class _M0AutomaticRoute extends sdkConnect.routes.AutomaticRoute {
11
+ static supportedNetworks() {
12
+ return ["Mainnet", "Testnet"];
13
+ }
14
+ static supportedChains(network) {
15
+ switch (network) {
16
+ case "Mainnet":
17
+ return ["Ethereum", "Arbitrum", "Optimism"];
18
+ case "Testnet":
19
+ return ["Sepolia", "ArbitrumSepolia", "OptimismSepolia"];
20
+ default:
21
+ throw new Error(`Unsupported network: ${network}`);
22
+ }
23
+ }
24
+ static getContracts(chain) {
25
+ switch (chain) {
26
+ case "Ethereum":
27
+ return this.EVM_CONTRACTS;
28
+ case "Optimism":
29
+ return this.EVM_CONTRACTS;
30
+ case "Arbitrum":
31
+ return this.EVM_CONTRACTS;
32
+ case "Sepolia":
33
+ return this.EVM_CONTRACTS;
34
+ case "OptimismSepolia":
35
+ return this.EVM_CONTRACTS;
36
+ case "ArbitrumSepolia":
37
+ return this.EVM_CONTRACTS;
38
+ default:
39
+ throw new Error(`Unsupported chain: ${chain}`);
40
+ }
41
+ }
42
+ static async supportedSourceTokens(fromChain) {
43
+ const { token, wrappedMToken } = this.getContracts(fromChain.chain);
44
+ return [
45
+ sdkConnect.Wormhole.tokenId(fromChain.chain, token),
46
+ sdkConnect.Wormhole.tokenId(fromChain.chain, wrappedMToken)
47
+ ];
48
+ }
49
+ static async supportedDestinationTokens(token, fromChain, toChain) {
50
+ const { token: mToken, wrappedMToken } = this.getContracts(toChain.chain);
51
+ return [
52
+ sdkConnect.Wormhole.tokenId(toChain.chain, mToken),
53
+ sdkConnect.Wormhole.tokenId(toChain.chain, wrappedMToken)
54
+ ];
55
+ }
56
+ static isProtocolSupported(chain) {
57
+ return chain.supportsProtocol("Ntt");
58
+ }
59
+ getDefaultOptions() {
60
+ return sdkRouteNtt.NttRoute.AutomaticOptions;
61
+ }
62
+ async isAvailable(request) {
63
+ const ntt = await request.fromChain.getProtocol("Ntt", {
64
+ ntt: _M0AutomaticRoute.getContracts(request.fromChain.chain)
65
+ });
66
+ return ntt.isRelayingAvailable(request.toChain.chain);
67
+ }
68
+ async validate(request, params) {
69
+ const options = params.options ?? this.getDefaultOptions();
70
+ const gasDropoff = sdkConnect.amount.parse(
71
+ options.gasDropoff ?? "0.0",
72
+ request.toChain.config.nativeTokenDecimals
73
+ );
74
+ const parsedAmount = sdkConnect.amount.parse(params.amount, request.source.decimals);
75
+ const trimmedAmount = sdkRouteNtt.NttRoute.trimAmount(
76
+ parsedAmount,
77
+ request.destination.decimals
78
+ );
79
+ const fromContracts = _M0AutomaticRoute.getContracts(
80
+ request.fromChain.chain
81
+ );
82
+ const toContracts = _M0AutomaticRoute.getContracts(request.toChain.chain);
83
+ const validatedParams = {
84
+ amount: params.amount,
85
+ normalizedParams: {
86
+ amount: trimmedAmount,
87
+ sourceContracts: fromContracts,
88
+ destinationContracts: toContracts,
89
+ options: {
90
+ queue: false,
91
+ automatic: true,
92
+ gasDropoff: sdkConnect.amount.units(gasDropoff)
93
+ }
94
+ },
95
+ options
96
+ };
97
+ return { valid: true, params: validatedParams };
98
+ }
99
+ async quote(request, params) {
100
+ const { fromChain, toChain } = request;
101
+ const ntt = await fromChain.getProtocol("Ntt", {
102
+ ntt: _M0AutomaticRoute.getContracts(fromChain.chain)
103
+ });
104
+ if (!await ntt.isRelayingAvailable(toChain.chain)) {
105
+ return {
106
+ success: false,
107
+ error: new Error(`Relaying to chain ${toChain.chain} is not available`)
108
+ };
109
+ }
110
+ const deliveryPrice = await ntt.quoteDeliveryPrice(
111
+ toChain.chain,
112
+ params.normalizedParams.options
113
+ );
114
+ const dstAmount = sdkConnect.amount.scale(
115
+ params.normalizedParams.amount,
116
+ request.destination.decimals
117
+ );
118
+ const result = {
119
+ success: true,
120
+ params,
121
+ sourceToken: {
122
+ token: request.source.id,
123
+ amount: params.normalizedParams.amount
124
+ },
125
+ destinationToken: {
126
+ token: request.destination.id,
127
+ amount: dstAmount
128
+ },
129
+ relayFee: {
130
+ token: sdkConnect.Wormhole.tokenId(fromChain.chain, "native"),
131
+ amount: sdkConnect.amount.fromBaseUnits(
132
+ deliveryPrice,
133
+ fromChain.config.nativeTokenDecimals
134
+ )
135
+ },
136
+ destinationNativeGas: sdkConnect.amount.fromBaseUnits(
137
+ params.normalizedParams.options.gasDropoff ?? 0n,
138
+ toChain.config.nativeTokenDecimals
139
+ ),
140
+ eta: sdkConnect.finality.estimateFinalityTime(request.fromChain.chain)
141
+ };
142
+ return result;
143
+ }
144
+ async initiate(request, signer, quote, to) {
145
+ const { params } = quote;
146
+ const { fromChain } = request;
147
+ const sender = sdkConnect.Wormhole.parseAddress(signer.chain(), signer.address());
148
+ if (sdkConnect.chainToPlatform(fromChain.chain) !== "Evm")
149
+ throw new Error("The route supports only EVM");
150
+ const ntt = await fromChain.getProtocol("Ntt", {
151
+ ntt: _M0AutomaticRoute.getContracts(fromChain.chain)
152
+ });
153
+ const sourceTokenAddress = sdkConnect.canonicalAddress(request.source.id);
154
+ const destinationTokenAddress = sdkConnect.canonicalAddress(request.destination.id);
155
+ const initXfer = this.transferMLike(
156
+ ntt,
157
+ sender,
158
+ sdkConnect.amount.units(params.normalizedParams.amount),
159
+ to,
160
+ sourceTokenAddress,
161
+ destinationTokenAddress,
162
+ params.normalizedParams.options
163
+ );
164
+ const txids = await sdkConnect.signSendWait(fromChain, initXfer, signer);
165
+ return {
166
+ from: fromChain.chain,
167
+ to: to.chain,
168
+ state: sdkConnect.TransferState.SourceInitiated,
169
+ originTxs: txids,
170
+ params
171
+ };
172
+ }
173
+ /**
174
+ * Modified from EvmNtt `transfer` function to call `transferMLikeToken` instead
175
+ * https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/ts/src/ntt.ts#L461
176
+ */
177
+ async *transferMLike(ntt, sender, amount2, destination, sourceToken, destinationToken, options) {
178
+ const senderAddress = new sdkEvm.EvmAddress(sender).toString();
179
+ const totalPrice = await ntt.quoteDeliveryPrice(destination.chain, options);
180
+ const tokenContract = sdkEvm.EvmPlatform.getTokenImplementation(
181
+ ntt.provider,
182
+ sourceToken
183
+ );
184
+ const allowance = await tokenContract.allowance(
185
+ senderAddress,
186
+ ntt.managerAddress
187
+ );
188
+ if (allowance < amount2) {
189
+ const txReq2 = await tokenContract.approve.populateTransaction(
190
+ ntt.managerAddress,
191
+ amount2
192
+ );
193
+ yield this.createUnsignedTx(
194
+ ntt,
195
+ sdkEvm.addFrom(txReq2, senderAddress),
196
+ "Ntt.Approve"
197
+ );
198
+ }
199
+ const receiver = sdkConnect.universalAddress(destination);
200
+ const contract = new ethers.Contract(ntt.managerAddress, [
201
+ "function transferMLikeToken(uint256 amount, address sourceToken, uint16 destinationChainId, bytes32 destinationToken, bytes32 recipient, bytes32 refundAddress) external payable returns (uint64 sequence)"
202
+ ]);
203
+ const txReq = await contract.getFunction("transferMLikeToken").populateTransaction(
204
+ amount2,
205
+ sourceToken,
206
+ sdkConnect.toChainId(destination.chain),
207
+ sdkConnect.toUniversal(destination.chain, destinationToken).toString(),
208
+ receiver,
209
+ receiver,
210
+ { value: totalPrice }
211
+ );
212
+ yield ntt.createUnsignedTx(sdkEvm.addFrom(txReq, senderAddress), "Ntt.transfer");
213
+ }
214
+ createUnsignedTx(ntt, txReq, description, parallelizable = false) {
215
+ return new sdkEvm.EvmUnsignedTransaction(
216
+ sdkEvm.addChainId(txReq, ntt.chainId),
217
+ ntt.network,
218
+ ntt.chain,
219
+ description,
220
+ parallelizable
221
+ );
222
+ }
223
+ async *track(receipt, timeout) {
224
+ if (sdkConnect.isSourceInitiated(receipt) || sdkConnect.isSourceFinalized(receipt)) {
225
+ const { txid } = receipt.originTxs[receipt.originTxs.length - 1];
226
+ const isEvmPlatform = (chain) => sdkConnect.chainToPlatform(chain) === "Evm";
227
+ const vaaType = isEvmPlatform(receipt.from) && isEvmPlatform(receipt.to) ? (
228
+ // Automatic NTT transfers between EVM chains use standard relayers
229
+ "Ntt:WormholeTransferStandardRelayer"
230
+ ) : "Ntt:WormholeTransfer";
231
+ const vaa = await this.wh.getVaa(txid, vaaType, timeout);
232
+ if (!vaa) {
233
+ throw new Error(`No VAA found for transaction: ${txid}`);
234
+ }
235
+ const msgId = {
236
+ chain: vaa.emitterChain,
237
+ emitter: vaa.emitterAddress,
238
+ sequence: vaa.sequence
239
+ };
240
+ receipt = {
241
+ ...receipt,
242
+ state: sdkConnect.TransferState.Attested,
243
+ attestation: {
244
+ id: msgId,
245
+ attestation: vaa
246
+ }
247
+ };
248
+ yield receipt;
249
+ }
250
+ const toChain = this.wh.getChain(receipt.to);
251
+ const ntt = await toChain.getProtocol("Ntt", {
252
+ ntt: _M0AutomaticRoute.getContracts(toChain.chain)
253
+ });
254
+ if (sdkConnect.isAttested(receipt)) {
255
+ const {
256
+ attestation: { attestation: vaa }
257
+ } = receipt;
258
+ if (await ntt.getIsApproved(vaa)) {
259
+ receipt = {
260
+ ...receipt,
261
+ state: sdkConnect.TransferState.DestinationInitiated
262
+ // TODO: check for destination event transactions to get dest Txids
263
+ };
264
+ yield receipt;
265
+ }
266
+ }
267
+ if (sdkConnect.isRedeemed(receipt)) {
268
+ const {
269
+ attestation: { attestation: vaa }
270
+ } = receipt;
271
+ const payload = vaa.payloadName === "WormholeTransfer" ? vaa.payload : vaa.payload["payload"];
272
+ const isExecuted = await ntt.manager.isMessageExecuted(
273
+ sdkDefinitionsNtt.Ntt.messageDigest(vaa.emitterChain, payload["nttManagerPayload"])
274
+ );
275
+ if (isExecuted) {
276
+ receipt = {
277
+ ...receipt,
278
+ state: sdkConnect.TransferState.DestinationFinalized
279
+ };
280
+ yield receipt;
281
+ }
282
+ }
283
+ yield receipt;
284
+ }
285
+ };
286
+ // ntt does not support gas drop-off currently
287
+ _M0AutomaticRoute.NATIVE_GAS_DROPOFF_SUPPORTED = false;
288
+ // Contract addresses are the same on all EVM chains
289
+ _M0AutomaticRoute.EVM_CONTRACTS = {
290
+ // M token address is the same on EVM chains
291
+ token: "0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b",
292
+ // Wrapped M token address is the same on EVM chains
293
+ wrappedMToken: "0x437cc33344a0B27A429f795ff6B469C72698B291",
294
+ // M0 Portal address is the same on EVM chains
295
+ manager: "0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd",
296
+ // Wormhole transceiver address is the same on EVM chains
297
+ transceiver: { wormhole: "0x0763196A091575adF99e2306E5e90E0Be5154841" }
298
+ };
299
+ _M0AutomaticRoute.meta = { name: "M0AutomaticRoute", provider: "M^0" };
300
+ var M0AutomaticRoute = _M0AutomaticRoute;
301
+
302
+ exports.M0AutomaticRoute = M0AutomaticRoute;
package/dist/index.mjs ADDED
@@ -0,0 +1,300 @@
1
+ import { routes, Wormhole, amount, finality, chainToPlatform, canonicalAddress, signSendWait, TransferState, universalAddress, toChainId, toUniversal, isSourceInitiated, isSourceFinalized, isAttested, isRedeemed } from '@wormhole-foundation/sdk-connect';
2
+ import { Ntt } from '@wormhole-foundation/sdk-definitions-ntt';
3
+ import { EvmAddress, EvmPlatform, addFrom, EvmUnsignedTransaction, addChainId } from '@wormhole-foundation/sdk-evm';
4
+ import { NttRoute } from '@wormhole-foundation/sdk-route-ntt';
5
+ import { Contract } from 'ethers';
6
+
7
+ // src/m0AutomaticRoute.ts
8
+ var _M0AutomaticRoute = class _M0AutomaticRoute extends routes.AutomaticRoute {
9
+ static supportedNetworks() {
10
+ return ["Mainnet", "Testnet"];
11
+ }
12
+ static supportedChains(network) {
13
+ switch (network) {
14
+ case "Mainnet":
15
+ return ["Ethereum", "Arbitrum", "Optimism"];
16
+ case "Testnet":
17
+ return ["Sepolia", "ArbitrumSepolia", "OptimismSepolia"];
18
+ default:
19
+ throw new Error(`Unsupported network: ${network}`);
20
+ }
21
+ }
22
+ static getContracts(chain) {
23
+ switch (chain) {
24
+ case "Ethereum":
25
+ return this.EVM_CONTRACTS;
26
+ case "Optimism":
27
+ return this.EVM_CONTRACTS;
28
+ case "Arbitrum":
29
+ return this.EVM_CONTRACTS;
30
+ case "Sepolia":
31
+ return this.EVM_CONTRACTS;
32
+ case "OptimismSepolia":
33
+ return this.EVM_CONTRACTS;
34
+ case "ArbitrumSepolia":
35
+ return this.EVM_CONTRACTS;
36
+ default:
37
+ throw new Error(`Unsupported chain: ${chain}`);
38
+ }
39
+ }
40
+ static async supportedSourceTokens(fromChain) {
41
+ const { token, wrappedMToken } = this.getContracts(fromChain.chain);
42
+ return [
43
+ Wormhole.tokenId(fromChain.chain, token),
44
+ Wormhole.tokenId(fromChain.chain, wrappedMToken)
45
+ ];
46
+ }
47
+ static async supportedDestinationTokens(token, fromChain, toChain) {
48
+ const { token: mToken, wrappedMToken } = this.getContracts(toChain.chain);
49
+ return [
50
+ Wormhole.tokenId(toChain.chain, mToken),
51
+ Wormhole.tokenId(toChain.chain, wrappedMToken)
52
+ ];
53
+ }
54
+ static isProtocolSupported(chain) {
55
+ return chain.supportsProtocol("Ntt");
56
+ }
57
+ getDefaultOptions() {
58
+ return NttRoute.AutomaticOptions;
59
+ }
60
+ async isAvailable(request) {
61
+ const ntt = await request.fromChain.getProtocol("Ntt", {
62
+ ntt: _M0AutomaticRoute.getContracts(request.fromChain.chain)
63
+ });
64
+ return ntt.isRelayingAvailable(request.toChain.chain);
65
+ }
66
+ async validate(request, params) {
67
+ const options = params.options ?? this.getDefaultOptions();
68
+ const gasDropoff = amount.parse(
69
+ options.gasDropoff ?? "0.0",
70
+ request.toChain.config.nativeTokenDecimals
71
+ );
72
+ const parsedAmount = amount.parse(params.amount, request.source.decimals);
73
+ const trimmedAmount = NttRoute.trimAmount(
74
+ parsedAmount,
75
+ request.destination.decimals
76
+ );
77
+ const fromContracts = _M0AutomaticRoute.getContracts(
78
+ request.fromChain.chain
79
+ );
80
+ const toContracts = _M0AutomaticRoute.getContracts(request.toChain.chain);
81
+ const validatedParams = {
82
+ amount: params.amount,
83
+ normalizedParams: {
84
+ amount: trimmedAmount,
85
+ sourceContracts: fromContracts,
86
+ destinationContracts: toContracts,
87
+ options: {
88
+ queue: false,
89
+ automatic: true,
90
+ gasDropoff: amount.units(gasDropoff)
91
+ }
92
+ },
93
+ options
94
+ };
95
+ return { valid: true, params: validatedParams };
96
+ }
97
+ async quote(request, params) {
98
+ const { fromChain, toChain } = request;
99
+ const ntt = await fromChain.getProtocol("Ntt", {
100
+ ntt: _M0AutomaticRoute.getContracts(fromChain.chain)
101
+ });
102
+ if (!await ntt.isRelayingAvailable(toChain.chain)) {
103
+ return {
104
+ success: false,
105
+ error: new Error(`Relaying to chain ${toChain.chain} is not available`)
106
+ };
107
+ }
108
+ const deliveryPrice = await ntt.quoteDeliveryPrice(
109
+ toChain.chain,
110
+ params.normalizedParams.options
111
+ );
112
+ const dstAmount = amount.scale(
113
+ params.normalizedParams.amount,
114
+ request.destination.decimals
115
+ );
116
+ const result = {
117
+ success: true,
118
+ params,
119
+ sourceToken: {
120
+ token: request.source.id,
121
+ amount: params.normalizedParams.amount
122
+ },
123
+ destinationToken: {
124
+ token: request.destination.id,
125
+ amount: dstAmount
126
+ },
127
+ relayFee: {
128
+ token: Wormhole.tokenId(fromChain.chain, "native"),
129
+ amount: amount.fromBaseUnits(
130
+ deliveryPrice,
131
+ fromChain.config.nativeTokenDecimals
132
+ )
133
+ },
134
+ destinationNativeGas: amount.fromBaseUnits(
135
+ params.normalizedParams.options.gasDropoff ?? 0n,
136
+ toChain.config.nativeTokenDecimals
137
+ ),
138
+ eta: finality.estimateFinalityTime(request.fromChain.chain)
139
+ };
140
+ return result;
141
+ }
142
+ async initiate(request, signer, quote, to) {
143
+ const { params } = quote;
144
+ const { fromChain } = request;
145
+ const sender = Wormhole.parseAddress(signer.chain(), signer.address());
146
+ if (chainToPlatform(fromChain.chain) !== "Evm")
147
+ throw new Error("The route supports only EVM");
148
+ const ntt = await fromChain.getProtocol("Ntt", {
149
+ ntt: _M0AutomaticRoute.getContracts(fromChain.chain)
150
+ });
151
+ const sourceTokenAddress = canonicalAddress(request.source.id);
152
+ const destinationTokenAddress = canonicalAddress(request.destination.id);
153
+ const initXfer = this.transferMLike(
154
+ ntt,
155
+ sender,
156
+ amount.units(params.normalizedParams.amount),
157
+ to,
158
+ sourceTokenAddress,
159
+ destinationTokenAddress,
160
+ params.normalizedParams.options
161
+ );
162
+ const txids = await signSendWait(fromChain, initXfer, signer);
163
+ return {
164
+ from: fromChain.chain,
165
+ to: to.chain,
166
+ state: TransferState.SourceInitiated,
167
+ originTxs: txids,
168
+ params
169
+ };
170
+ }
171
+ /**
172
+ * Modified from EvmNtt `transfer` function to call `transferMLikeToken` instead
173
+ * https://github.com/wormhole-foundation/native-token-transfers/blob/main/evm/ts/src/ntt.ts#L461
174
+ */
175
+ async *transferMLike(ntt, sender, amount2, destination, sourceToken, destinationToken, options) {
176
+ const senderAddress = new EvmAddress(sender).toString();
177
+ const totalPrice = await ntt.quoteDeliveryPrice(destination.chain, options);
178
+ const tokenContract = EvmPlatform.getTokenImplementation(
179
+ ntt.provider,
180
+ sourceToken
181
+ );
182
+ const allowance = await tokenContract.allowance(
183
+ senderAddress,
184
+ ntt.managerAddress
185
+ );
186
+ if (allowance < amount2) {
187
+ const txReq2 = await tokenContract.approve.populateTransaction(
188
+ ntt.managerAddress,
189
+ amount2
190
+ );
191
+ yield this.createUnsignedTx(
192
+ ntt,
193
+ addFrom(txReq2, senderAddress),
194
+ "Ntt.Approve"
195
+ );
196
+ }
197
+ const receiver = universalAddress(destination);
198
+ const contract = new Contract(ntt.managerAddress, [
199
+ "function transferMLikeToken(uint256 amount, address sourceToken, uint16 destinationChainId, bytes32 destinationToken, bytes32 recipient, bytes32 refundAddress) external payable returns (uint64 sequence)"
200
+ ]);
201
+ const txReq = await contract.getFunction("transferMLikeToken").populateTransaction(
202
+ amount2,
203
+ sourceToken,
204
+ toChainId(destination.chain),
205
+ toUniversal(destination.chain, destinationToken).toString(),
206
+ receiver,
207
+ receiver,
208
+ { value: totalPrice }
209
+ );
210
+ yield ntt.createUnsignedTx(addFrom(txReq, senderAddress), "Ntt.transfer");
211
+ }
212
+ createUnsignedTx(ntt, txReq, description, parallelizable = false) {
213
+ return new EvmUnsignedTransaction(
214
+ addChainId(txReq, ntt.chainId),
215
+ ntt.network,
216
+ ntt.chain,
217
+ description,
218
+ parallelizable
219
+ );
220
+ }
221
+ async *track(receipt, timeout) {
222
+ if (isSourceInitiated(receipt) || isSourceFinalized(receipt)) {
223
+ const { txid } = receipt.originTxs[receipt.originTxs.length - 1];
224
+ const isEvmPlatform = (chain) => chainToPlatform(chain) === "Evm";
225
+ const vaaType = isEvmPlatform(receipt.from) && isEvmPlatform(receipt.to) ? (
226
+ // Automatic NTT transfers between EVM chains use standard relayers
227
+ "Ntt:WormholeTransferStandardRelayer"
228
+ ) : "Ntt:WormholeTransfer";
229
+ const vaa = await this.wh.getVaa(txid, vaaType, timeout);
230
+ if (!vaa) {
231
+ throw new Error(`No VAA found for transaction: ${txid}`);
232
+ }
233
+ const msgId = {
234
+ chain: vaa.emitterChain,
235
+ emitter: vaa.emitterAddress,
236
+ sequence: vaa.sequence
237
+ };
238
+ receipt = {
239
+ ...receipt,
240
+ state: TransferState.Attested,
241
+ attestation: {
242
+ id: msgId,
243
+ attestation: vaa
244
+ }
245
+ };
246
+ yield receipt;
247
+ }
248
+ const toChain = this.wh.getChain(receipt.to);
249
+ const ntt = await toChain.getProtocol("Ntt", {
250
+ ntt: _M0AutomaticRoute.getContracts(toChain.chain)
251
+ });
252
+ if (isAttested(receipt)) {
253
+ const {
254
+ attestation: { attestation: vaa }
255
+ } = receipt;
256
+ if (await ntt.getIsApproved(vaa)) {
257
+ receipt = {
258
+ ...receipt,
259
+ state: TransferState.DestinationInitiated
260
+ // TODO: check for destination event transactions to get dest Txids
261
+ };
262
+ yield receipt;
263
+ }
264
+ }
265
+ if (isRedeemed(receipt)) {
266
+ const {
267
+ attestation: { attestation: vaa }
268
+ } = receipt;
269
+ const payload = vaa.payloadName === "WormholeTransfer" ? vaa.payload : vaa.payload["payload"];
270
+ const isExecuted = await ntt.manager.isMessageExecuted(
271
+ Ntt.messageDigest(vaa.emitterChain, payload["nttManagerPayload"])
272
+ );
273
+ if (isExecuted) {
274
+ receipt = {
275
+ ...receipt,
276
+ state: TransferState.DestinationFinalized
277
+ };
278
+ yield receipt;
279
+ }
280
+ }
281
+ yield receipt;
282
+ }
283
+ };
284
+ // ntt does not support gas drop-off currently
285
+ _M0AutomaticRoute.NATIVE_GAS_DROPOFF_SUPPORTED = false;
286
+ // Contract addresses are the same on all EVM chains
287
+ _M0AutomaticRoute.EVM_CONTRACTS = {
288
+ // M token address is the same on EVM chains
289
+ token: "0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b",
290
+ // Wrapped M token address is the same on EVM chains
291
+ wrappedMToken: "0x437cc33344a0B27A429f795ff6B469C72698B291",
292
+ // M0 Portal address is the same on EVM chains
293
+ manager: "0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd",
294
+ // Wormhole transceiver address is the same on EVM chains
295
+ transceiver: { wormhole: "0x0763196A091575adF99e2306E5e90E0Be5154841" }
296
+ };
297
+ _M0AutomaticRoute.meta = { name: "M0AutomaticRoute", provider: "M^0" };
298
+ var M0AutomaticRoute = _M0AutomaticRoute;
299
+
300
+ export { M0AutomaticRoute };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@m0-foundation/ntt-sdk-route",
3
+ "version": "0.0.2",
4
+ "exports": {
5
+ ".": {
6
+ "types": "./dist/index.d.ts",
7
+ "import": "./dist/index.mjs",
8
+ "require": "./dist/index.js"
9
+ },
10
+ "./package.json": "./package.json"
11
+ },
12
+ "main": "./dist/index.js",
13
+ "module": "./dist/index.mjs",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "devDependencies": {
19
+ "@changesets/cli": "^2.28.1",
20
+ "@types/node": "~20.17.17",
21
+ "tsup": "~8.3.6",
22
+ "typescript": "~5.6.3"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "dependencies": {
28
+ "@wormhole-foundation/sdk": "^1.7.0",
29
+ "@wormhole-foundation/sdk-connect": "^1.7.0",
30
+ "@wormhole-foundation/sdk-definitions-ntt": "^0.6.1",
31
+ "@wormhole-foundation/sdk-evm": "^1.7.0",
32
+ "@wormhole-foundation/sdk-evm-ntt": "^0.6.1",
33
+ "@wormhole-foundation/sdk-route-ntt": "^0.6.1",
34
+ "ethers": "^6.5.1"
35
+ },
36
+ "scripts": {
37
+ "build": "$npm_execpath tsup --clean",
38
+ "clean": "rm -rf dist"
39
+ }
40
+ }