@aptos-labs/cross-chain-core 4.23.1

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.
Files changed (66) hide show
  1. package/LICENSE +201 -0
  2. package/dist/CrossChainCore.d.ts +36 -0
  3. package/dist/CrossChainCore.d.ts.map +1 -0
  4. package/dist/config/index.d.ts +4 -0
  5. package/dist/config/index.d.ts.map +1 -0
  6. package/dist/config/mainnet/chains.d.ts +17 -0
  7. package/dist/config/mainnet/chains.d.ts.map +1 -0
  8. package/dist/config/mainnet/index.d.ts +3 -0
  9. package/dist/config/mainnet/index.d.ts.map +1 -0
  10. package/dist/config/mainnet/tokens.d.ts +4 -0
  11. package/dist/config/mainnet/tokens.d.ts.map +1 -0
  12. package/dist/config/testnet/chains.d.ts +19 -0
  13. package/dist/config/testnet/chains.d.ts.map +1 -0
  14. package/dist/config/testnet/index.d.ts +3 -0
  15. package/dist/config/testnet/index.d.ts.map +1 -0
  16. package/dist/config/testnet/tokens.d.ts +4 -0
  17. package/dist/config/testnet/tokens.d.ts.map +1 -0
  18. package/dist/config/types.d.ts +41 -0
  19. package/dist/config/types.d.ts.map +1 -0
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +989 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/index.mjs +957 -0
  25. package/dist/index.mjs.map +1 -0
  26. package/dist/providers/wormhole/index.d.ts +5 -0
  27. package/dist/providers/wormhole/index.d.ts.map +1 -0
  28. package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts +18 -0
  29. package/dist/providers/wormhole/signers/AptosLocalSigner.d.ts.map +1 -0
  30. package/dist/providers/wormhole/signers/EthereumSigner.d.ts +5 -0
  31. package/dist/providers/wormhole/signers/EthereumSigner.d.ts.map +1 -0
  32. package/dist/providers/wormhole/signers/Signer.d.ts +17 -0
  33. package/dist/providers/wormhole/signers/Signer.d.ts.map +1 -0
  34. package/dist/providers/wormhole/signers/SolanaSigner.d.ts +28 -0
  35. package/dist/providers/wormhole/signers/SolanaSigner.d.ts.map +1 -0
  36. package/dist/providers/wormhole/types.d.ts +39 -0
  37. package/dist/providers/wormhole/types.d.ts.map +1 -0
  38. package/dist/providers/wormhole/wormhole.d.ts +35 -0
  39. package/dist/providers/wormhole/wormhole.d.ts.map +1 -0
  40. package/dist/utils/getUsdcBalance.d.ts +5 -0
  41. package/dist/utils/getUsdcBalance.d.ts.map +1 -0
  42. package/dist/utils/logger.d.ts +6 -0
  43. package/dist/utils/logger.d.ts.map +1 -0
  44. package/dist/version.d.ts +2 -0
  45. package/dist/version.d.ts.map +1 -0
  46. package/package.json +83 -0
  47. package/src/CrossChainCore.ts +130 -0
  48. package/src/config/index.ts +3 -0
  49. package/src/config/mainnet/chains.ts +64 -0
  50. package/src/config/mainnet/index.ts +2 -0
  51. package/src/config/mainnet/tokens.ts +43 -0
  52. package/src/config/testnet/chains.ts +69 -0
  53. package/src/config/testnet/index.ts +2 -0
  54. package/src/config/testnet/tokens.ts +43 -0
  55. package/src/config/types.ts +45 -0
  56. package/src/index.ts +5 -0
  57. package/src/providers/wormhole/index.ts +4 -0
  58. package/src/providers/wormhole/signers/AptosLocalSigner.ts +136 -0
  59. package/src/providers/wormhole/signers/EthereumSigner.ts +49 -0
  60. package/src/providers/wormhole/signers/Signer.ts +102 -0
  61. package/src/providers/wormhole/signers/SolanaSigner.ts +418 -0
  62. package/src/providers/wormhole/types.ts +59 -0
  63. package/src/providers/wormhole/wormhole.ts +320 -0
  64. package/src/utils/getUsdcBalance.ts +82 -0
  65. package/src/utils/logger.ts +17 -0
  66. package/src/version.ts +1 -0
@@ -0,0 +1,320 @@
1
+ import {
2
+ chainToPlatform,
3
+ routes,
4
+ TokenId,
5
+ Wormhole,
6
+ wormhole,
7
+ PlatformLoader,
8
+ TransferState,
9
+ } from "@wormhole-foundation/sdk";
10
+ import { Network, sleep } from "@aptos-labs/ts-sdk";
11
+ import aptos from "@wormhole-foundation/sdk/aptos";
12
+ import solana from "@wormhole-foundation/sdk/solana";
13
+ import evm from "@wormhole-foundation/sdk/evm";
14
+
15
+ import {
16
+ Chain,
17
+ CrossChainProvider,
18
+ CrossChainCore,
19
+ } from "../../CrossChainCore";
20
+ import { logger } from "../../utils/logger";
21
+ import { AptosLocalSigner } from "./signers/AptosLocalSigner";
22
+ import { Signer } from "./signers/Signer";
23
+ import { ChainConfig } from "../../config";
24
+ import {
25
+ WormholeQuoteRequest,
26
+ WormholeQuoteResponse,
27
+ WormholeInitiateTransferRequest,
28
+ WormholeInitiateTransferResponse,
29
+ WormholeRouteResponse,
30
+ WormholeRequest,
31
+ WormholeSubmitTransferRequest,
32
+ WormholeStartTransferResponse,
33
+ WormholeClaimTransferRequest,
34
+ } from "./types";
35
+ import { SolanaDerivedWallet } from "@aptos-labs/derived-wallet-solana";
36
+ import { EIP1193DerivedWallet } from "@aptos-labs/derived-wallet-ethereum";
37
+
38
+ export class WormholeProvider
39
+ implements
40
+ CrossChainProvider<
41
+ WormholeQuoteRequest,
42
+ WormholeQuoteResponse,
43
+ WormholeInitiateTransferRequest,
44
+ WormholeInitiateTransferResponse
45
+ >
46
+ {
47
+ private crossChainCore: CrossChainCore;
48
+
49
+ private _wormholeContext: Wormhole<"Mainnet" | "Testnet"> | undefined;
50
+
51
+ private wormholeRoute: WormholeRouteResponse | undefined;
52
+ private wormholeRequest: WormholeRequest | undefined;
53
+ private wormholeQuote: WormholeQuoteResponse | undefined;
54
+
55
+ constructor(core: CrossChainCore) {
56
+ this.crossChainCore = core;
57
+ }
58
+
59
+ get wormholeContext(): Wormhole<"Mainnet" | "Testnet"> | undefined {
60
+ return this._wormholeContext;
61
+ }
62
+
63
+ async setWormholeContext(sourceChain: Chain) {
64
+ const dappNetwork = this.crossChainCore._dappConfig?.aptosNetwork;
65
+ if (dappNetwork === Network.DEVNET) {
66
+ throw new Error("Devnet is not supported on Wormhole");
67
+ }
68
+ if (!sourceChain) {
69
+ throw new Error("Origin chain not selected");
70
+ }
71
+ const isMainnet = dappNetwork === Network.MAINNET;
72
+ const platforms: PlatformLoader<any>[] = [aptos, solana, evm];
73
+ const wh = await wormhole(isMainnet ? "Mainnet" : "Testnet", platforms);
74
+ this._wormholeContext = wh;
75
+ }
76
+
77
+ async getRoute(sourceChain: Chain): Promise<{
78
+ route: WormholeRouteResponse;
79
+ request: WormholeRequest;
80
+ }> {
81
+ if (!this._wormholeContext) {
82
+ throw new Error("Wormhole context not initialized");
83
+ }
84
+
85
+ const { sourceToken, destToken } = this.getTokenInfo(sourceChain);
86
+
87
+ const sourceContext = this._wormholeContext
88
+ .getPlatform(chainToPlatform(sourceChain))
89
+ .getChain(sourceChain);
90
+
91
+ logger.log("sourceContext", sourceContext);
92
+
93
+ const destContext = this._wormholeContext
94
+ .getPlatform(chainToPlatform("Aptos"))
95
+ .getChain("Aptos");
96
+
97
+ logger.log("destContext", destContext);
98
+
99
+ const request = await routes.RouteTransferRequest.create(
100
+ this._wormholeContext,
101
+ {
102
+ source: sourceToken,
103
+ destination: destToken,
104
+ },
105
+ sourceContext,
106
+ destContext
107
+ );
108
+
109
+ const resolver = this._wormholeContext.resolver([
110
+ routes.CCTPRoute, // manual CCTP
111
+ ]);
112
+
113
+ const route = await resolver.findRoutes(request);
114
+ const cctpRoute = route[0];
115
+
116
+ this.wormholeRoute = cctpRoute;
117
+ this.wormholeRequest = request;
118
+
119
+ return { route: cctpRoute, request };
120
+ }
121
+
122
+ async getQuote(input: WormholeQuoteRequest): Promise<WormholeQuoteResponse> {
123
+ const { amount, sourceChain } = input;
124
+
125
+ if (!this._wormholeContext) {
126
+ await this.setWormholeContext(sourceChain);
127
+ }
128
+
129
+ const { route, request } = await this.getRoute(sourceChain);
130
+
131
+ // TODO what is nativeGas for?
132
+ const transferParams = {
133
+ amount: amount,
134
+ options: { nativeGas: 0 },
135
+ };
136
+
137
+ const validated = await route.validate(request, transferParams);
138
+ if (!validated.valid) {
139
+ logger.log("invalid", validated.valid);
140
+ throw new Error(`Invalid quote: ${validated.error}`).message;
141
+ }
142
+ const quote = await route.quote(request, validated.params);
143
+ if (!quote.success) {
144
+ logger.log("quote failed", quote.success);
145
+ throw new Error(`Invalid quote: ${quote.error}`).message;
146
+ }
147
+ this.wormholeQuote = quote;
148
+ logger.log("quote", quote);
149
+ return quote;
150
+ }
151
+
152
+ async submitCCTPTransfer(
153
+ input: WormholeSubmitTransferRequest
154
+ ): Promise<WormholeStartTransferResponse> {
155
+ const { sourceChain, wallet, destinationAddress } = input;
156
+
157
+ if (!this._wormholeContext) {
158
+ await this.setWormholeContext(sourceChain);
159
+ }
160
+ if (!this.wormholeRoute || !this.wormholeRequest || !this.wormholeQuote) {
161
+ throw new Error("Wormhole route, request, or quote not initialized");
162
+ }
163
+
164
+ let signerAddress: string;
165
+
166
+ const chainContext = this.getChainConfig(sourceChain).context;
167
+
168
+ //const currentAccount = await wallet.getAccount();
169
+ if (chainContext === "Solana") {
170
+ signerAddress =
171
+ (wallet as SolanaDerivedWallet).solanaWallet.publicKey?.toBase58() ||
172
+ "";
173
+ } else {
174
+ // is Ethereum
175
+ [signerAddress] = await (
176
+ wallet as EIP1193DerivedWallet
177
+ ).eip1193Provider.request({
178
+ method: "eth_requestAccounts",
179
+ });
180
+ }
181
+ logger.log("signerAddress", signerAddress);
182
+
183
+ const signer = new Signer(
184
+ this.getChainConfig(sourceChain),
185
+ signerAddress,
186
+ {},
187
+ wallet
188
+ );
189
+
190
+ let receipt = await this.wormholeRoute.initiate(
191
+ this.wormholeRequest,
192
+ signer,
193
+ this.wormholeQuote,
194
+ Wormhole.chainAddress("Aptos", destinationAddress.toString())
195
+ );
196
+
197
+ const originChainTxnId =
198
+ "originTxs" in receipt
199
+ ? receipt.originTxs[receipt.originTxs.length - 1].txid
200
+ : undefined;
201
+
202
+ return { originChainTxnId: originChainTxnId || "", receipt };
203
+ }
204
+
205
+ async claimCCTPTransfer(
206
+ input: WormholeClaimTransferRequest
207
+ ): Promise<{ destinationChainTxnId: string }> {
208
+ let { receipt, mainSigner, sponsorAccount } = input;
209
+ if (!this.wormholeRoute) {
210
+ throw new Error("Wormhole route not initialized");
211
+ }
212
+
213
+ logger.log("mainSigner", mainSigner.accountAddress.toString());
214
+
215
+ let retries = 0;
216
+ const maxRetries = 5;
217
+ const baseDelay = 1000; // Initial delay of 1 second
218
+
219
+ while (retries < maxRetries) {
220
+ try {
221
+ for await (receipt of this.wormholeRoute.track(receipt, 120 * 1000)) {
222
+ if (receipt.state >= TransferState.SourceInitiated) {
223
+ logger.log("Receipt is on track ", receipt);
224
+
225
+ try {
226
+ const signer = new AptosLocalSigner(
227
+ "Aptos",
228
+ {},
229
+ mainSigner, // the account that signs the "claim" transaction
230
+ sponsorAccount ? sponsorAccount : undefined // the fee payer account
231
+ );
232
+
233
+ if (routes.isManual(this.wormholeRoute)) {
234
+ const circleAttestationReceipt =
235
+ await this.wormholeRoute.complete(signer, receipt);
236
+ logger.log("Claim receipt: ", circleAttestationReceipt);
237
+ const destinationChainTxnId = signer.claimedTransactionHashes();
238
+ return { destinationChainTxnId };
239
+ } else {
240
+ // Should be unreachable
241
+ return { destinationChainTxnId: "" };
242
+ }
243
+ } catch (e) {
244
+ console.error("Failed to claim", e);
245
+ return { destinationChainTxnId: "" };
246
+ }
247
+ }
248
+ }
249
+ } catch (e) {
250
+ console.error(
251
+ `Error tracking transfer (attempt ${retries + 1} / ${maxRetries}):`,
252
+ e
253
+ );
254
+ const delay = baseDelay * Math.pow(2, retries); // Exponential backoff
255
+ await sleep(delay);
256
+ retries++;
257
+ }
258
+ }
259
+ // Should be unreachable
260
+ return { destinationChainTxnId: "" };
261
+ }
262
+
263
+ /**
264
+ * Initiates a transfer of USDC funds from the source chain wallet to the destination chain wallet
265
+ * @param args
266
+ * @returns
267
+ */
268
+ async initiateCCTPTransfer(
269
+ input: WormholeInitiateTransferRequest
270
+ ): Promise<WormholeInitiateTransferResponse> {
271
+ if (this.crossChainCore._dappConfig?.aptosNetwork === Network.DEVNET) {
272
+ throw new Error("Devnet is not supported on Wormhole");
273
+ }
274
+ // if amount is provided, it is expected to get the quote internally
275
+ // and initiate a transfer automatically
276
+ if (input.amount) {
277
+ await this.getQuote({
278
+ amount: input.amount,
279
+ sourceChain: input.sourceChain,
280
+ });
281
+ }
282
+ // Submit transfer transaction from origin chain
283
+ let { originChainTxnId, receipt } = await this.submitCCTPTransfer(input);
284
+ // Claim transfer transaction on destination chain
285
+ const { destinationChainTxnId } = await this.claimCCTPTransfer({
286
+ receipt,
287
+ mainSigner: input.mainSigner,
288
+ sponsorAccount: input.sponsorAccount,
289
+ });
290
+ return { originChainTxnId, destinationChainTxnId };
291
+ }
292
+
293
+ getChainConfig(chain: Chain): ChainConfig {
294
+ const chainConfig =
295
+ this.crossChainCore.CHAINS[
296
+ chain as keyof typeof this.crossChainCore.CHAINS
297
+ ];
298
+ if (!chainConfig) {
299
+ throw new Error(`Chain config not found for chain: ${chain}`);
300
+ }
301
+ return chainConfig;
302
+ }
303
+
304
+ getTokenInfo(sourceChain: Chain): {
305
+ sourceToken: TokenId;
306
+ destToken: TokenId;
307
+ } {
308
+ const sourceToken: TokenId = Wormhole.tokenId(
309
+ this.crossChainCore.TOKENS[sourceChain].tokenId.chain as Chain,
310
+ this.crossChainCore.TOKENS[sourceChain].tokenId.address
311
+ );
312
+
313
+ const destToken: TokenId = Wormhole.tokenId(
314
+ this.crossChainCore.APTOS_TOKEN.tokenId.chain as Chain,
315
+ this.crossChainCore.APTOS_TOKEN.tokenId.address
316
+ );
317
+
318
+ return { sourceToken, destToken };
319
+ }
320
+ }
@@ -0,0 +1,82 @@
1
+ import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
2
+ import { Connection, PublicKey } from "@solana/web3.js";
3
+ import {
4
+ AptosMainnetUSDCToken,
5
+ AptosTestnetUSDCToken,
6
+ mainnetTokens,
7
+ testnetTokens,
8
+ } from "../config";
9
+ import { ethers, JsonRpcProvider } from "ethers";
10
+
11
+ export const getSolanaWalletUSDCBalance = async (
12
+ walletAddress: string,
13
+ aptosNetwork: Network,
14
+ rpc: string
15
+ ): Promise<string> => {
16
+ const address = new PublicKey(walletAddress);
17
+ const tokenAddress =
18
+ aptosNetwork === Network.MAINNET
19
+ ? mainnetTokens["Solana"].tokenId.address
20
+ : testnetTokens["Solana"].tokenId.address;
21
+
22
+ const connection = new Connection(rpc);
23
+ // Check to see if we were passed wallet address or token account
24
+ const splToken = await connection.getTokenAccountsByOwner(address, {
25
+ mint: new PublicKey(tokenAddress),
26
+ });
27
+
28
+ // Use the first token account if it exists, otherwise fall back to wallet address
29
+ const checkAddress =
30
+ splToken.value.length > 0 ? splToken.value[0]!.pubkey : address;
31
+
32
+ const balance = await connection.getTokenAccountBalance(checkAddress);
33
+ console.log("balance", balance);
34
+ return (
35
+ balance.value.uiAmountString ??
36
+ (Number(balance.value.amount) / 10 ** balance.value.decimals).toString()
37
+ );
38
+ };
39
+
40
+ export const getEthereumWalletUSDCBalance = async (
41
+ walletAddress: string,
42
+ aptosNetwork: Network,
43
+ rpc: string
44
+ ): Promise<string> => {
45
+ const token =
46
+ aptosNetwork === Network.MAINNET
47
+ ? mainnetTokens["Ethereum"]
48
+ : testnetTokens["Sepolia"];
49
+
50
+ const tokenAddress = token.tokenId.address;
51
+ const connection = new JsonRpcProvider(rpc);
52
+ const abi = ["function balanceOf(address owner) view returns (uint256)"];
53
+ const contract = new ethers.Contract(tokenAddress, abi, connection);
54
+ const balance = await contract.balanceOf(walletAddress);
55
+ return ethers.formatUnits(balance, token.decimals).toString();
56
+ };
57
+
58
+ export const getAptosWalletUSDCBalance = async (
59
+ walletAddress: string,
60
+ aptosNetwork: Network
61
+ ): Promise<string> => {
62
+ const token =
63
+ aptosNetwork === Network.MAINNET
64
+ ? AptosMainnetUSDCToken
65
+ : AptosTestnetUSDCToken;
66
+ const tokenAddress = token.tokenId.address;
67
+ const aptosConfig = new AptosConfig({ network: aptosNetwork });
68
+ const connection = new Aptos(aptosConfig);
69
+ const response = await connection.getCurrentFungibleAssetBalances({
70
+ options: {
71
+ where: {
72
+ owner_address: { _eq: walletAddress },
73
+ asset_type: { _eq: tokenAddress },
74
+ },
75
+ },
76
+ });
77
+ const balance = (
78
+ Number(response[0].amount) /
79
+ 10 ** token.decimals
80
+ ).toString();
81
+ return balance;
82
+ };
@@ -0,0 +1,17 @@
1
+ export const logger = {
2
+ log: (...args: any[]) => {
3
+ if (process.env.NODE_ENV === "development") {
4
+ console.log(...args);
5
+ }
6
+ },
7
+ warn: (...args: any[]) => {
8
+ if (process.env.NODE_ENV === "development") {
9
+ console.warn(...args);
10
+ }
11
+ },
12
+ error: (...args: any[]) => {
13
+ if (process.env.NODE_ENV === "development") {
14
+ console.error(...args);
15
+ }
16
+ },
17
+ };
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const CROSS_CHAIN_CORE_VERSION = "4.23.1";