@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 +4 -0
- package/dist/index.d.mts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +302 -0
- package/dist/index.mjs +300 -0
- package/package.json +40 -0
package/README.md
ADDED
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|