UNPKG

@wormhole-foundation/sdk-aptos-tokenbridge

Version:

SDK for Solana, used in conjunction with @wormhole-foundation/sdk

253 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.AptosTokenBridge = void 0; const sdk_connect_1 = require("@wormhole-foundation/sdk-connect"); const sdk_aptos_1 = require("@wormhole-foundation/sdk-aptos"); const foreignAddress_js_1 = require("./foreignAddress.js"); const ts_sdk_1 = require("@aptos-labs/ts-sdk"); class AptosTokenBridge { network; chain; connection; contracts; chainId; tokenBridgeAddress; constructor(network, chain, connection, contracts) { this.network = network; this.chain = chain; this.connection = connection; this.contracts = contracts; this.chainId = (0, sdk_connect_1.toChainId)(chain); const tokenBridgeAddress = contracts.tokenBridge; if (!tokenBridgeAddress) throw new Error(`TokenBridge contract Address for chain ${chain} not found`); this.tokenBridgeAddress = tokenBridgeAddress; } static async fromRpc(connection, config) { const [network, chain] = await sdk_aptos_1.AptosPlatform.chainFromRpc(connection); const conf = config[chain]; if (conf.network !== network) throw new Error("Network mismatch " + conf.network + " !== " + network); return new AptosTokenBridge(network, chain, connection, conf.contracts); } async isWrappedAsset(token) { try { await this.getOriginalAsset(token); return true; } catch (_) { return false; } } async getOriginalAsset(token) { const fqt = token.toString().split(sdk_aptos_1.APTOS_SEPARATOR); let originInfo = null; try { originInfo = await this.connection.getAccountResource({ accountAddress: fqt[0], resourceType: `${this.tokenBridgeAddress}::state::OriginInfo`, }); } catch (e) { if (e instanceof ts_sdk_1.AptosApiError && e.data?.error_code === "resource_not_found") { // if we can't find the origin info, it means this is not a wrapped asset throw (0, sdk_connect_1.ErrNotWrapped)(token.toString()); } throw e; } if (!originInfo) throw (0, sdk_connect_1.ErrNotWrapped)(token.toString()); // wrapped asset const chain = (0, sdk_connect_1.toChain)(parseInt(originInfo.token_chain.number)); const address = new sdk_connect_1.UniversalAddress(originInfo.token_address.external_address); return { chain, address }; } async getTokenUniversalAddress(token) { return new sdk_connect_1.UniversalAddress(sdk_connect_1.encoding.hex.encode((0, sdk_connect_1.sha3_256)(token.toString()), true)); } async getTokenNativeAddress(originChain, token) { const assetType = originChain === this.chain ? await this.getTypeFromExternalAddress(token.toString()) : await this.getAssetFullyQualifiedType({ chain: originChain, address: token }); if (!assetType) throw new Error("Invalid asset address."); return new sdk_aptos_1.AptosAddress(assetType); } async hasWrappedAsset(token) { try { await this.getWrappedAsset(token); return true; } catch (_) { } return false; } async getWrappedAsset(token) { if ((0, sdk_connect_1.isNative)(token.address)) throw new Error("native asset cannot be a wrapped asset"); const assetFullyQualifiedType = await this.getAssetFullyQualifiedType(token); if (!assetFullyQualifiedType) throw new Error("Invalid asset address."); // check to see if we can get origin info from asset address await this.connection.getAccountResource({ accountAddress: (0, sdk_aptos_1.coalesceModuleAddress)(assetFullyQualifiedType), resourceType: `${this.tokenBridgeAddress}::state::OriginInfo`, }); // if successful, we can just return the computed address return (0, sdk_connect_1.toNative)(this.chain, assetFullyQualifiedType); } async isTransferCompleted(vaa) { const state = await this.connection.getAccountResource({ accountAddress: this.tokenBridgeAddress, resourceType: `${this.tokenBridgeAddress}::state::State`, }); const handle = state.consumed_vaas.elems.handle; // check if vaa hash is in consumed_vaas try { // when accessing Set<T>, key is type T and value is 0 await this.connection.getTableItem({ handle, data: { key_type: "vector<u8>", value_type: "u8", key: `0x${Buffer.from((0, sdk_connect_1.keccak256)(vaa.hash)).toString("hex")}`, }, }); return true; } catch { return false; } } async getWrappedNative() { return (0, sdk_connect_1.toNative)(this.chain, sdk_aptos_1.APTOS_COIN); } async *createAttestation(token, payer) { const tokenId = { chain: this.chain, address: new sdk_aptos_1.AptosAddress(token) }; const assetType = await this.getAssetFullyQualifiedType(tokenId); if (!assetType) throw new Error("Invalid asset address."); yield this.createUnsignedTx({ function: `${this.tokenBridgeAddress}::attest_token::attest_token_entry`, typeArguments: [assetType], functionArguments: [], }, "Aptos.AttestToken"); } async *submitAttestation(vaa, payer) { yield this.createUnsignedTx({ function: `${this.tokenBridgeAddress}::wrapped::create_wrapped_coin_type`, typeArguments: [], functionArguments: [(0, sdk_connect_1.serialize)(vaa)], }, "Aptos.CreateWrappedCoinType"); const assetType = await this.getAssetFullyQualifiedType(vaa.payload.token); if (!assetType) throw new Error("Invalid asset address."); yield this.createUnsignedTx({ function: `${this.tokenBridgeAddress}::wrapped::create_wrapped_coin`, typeArguments: [assetType], functionArguments: [(0, sdk_connect_1.serialize)(vaa)], }, "Aptos.CreateWrappedCoin"); } async *transfer(sender, recipient, token, amount, payload) { // TODO const fee = 0n; const nonce = 0n; const fullyQualifiedType = (0, sdk_connect_1.isNative)(token) ? sdk_aptos_1.APTOS_COIN : token.toString(); const dstAddress = recipient.address.toUniversalAddress().toUint8Array(); const dstChain = (0, sdk_connect_1.toChainId)(recipient.chain); if (payload) { yield this.createUnsignedTx({ function: `${this.tokenBridgeAddress}::transfer_tokens::transfer_tokens_with_payload_entry`, typeArguments: [fullyQualifiedType], functionArguments: [amount, dstChain, dstAddress, nonce, payload], }, "Aptos.TransferTokensWithPayload"); } else { yield this.createUnsignedTx({ function: `${this.tokenBridgeAddress}::transfer_tokens::transfer_tokens_entry`, typeArguments: [fullyQualifiedType], functionArguments: [amount, dstChain, dstAddress, fee, nonce], }, "Aptos.TransferTokens"); } } async *redeem(sender, vaa, unwrapNative = true) { const assetType = vaa.payload.token.chain === this.chain ? await this.getTypeFromExternalAddress(vaa.payload.token.address.toString()) : await this.getAssetFullyQualifiedType(vaa.payload.token); if (!assetType) throw new Error("Invalid asset address."); yield this.createUnsignedTx({ function: `${this.tokenBridgeAddress}::complete_transfer::submit_vaa_and_register_entry`, typeArguments: [assetType], functionArguments: [(0, sdk_connect_1.serialize)(vaa)], }, "Aptos.CompleteTransfer"); } async getAssetFullyQualifiedType(tokenId) { // native asset if (tokenId.chain === this.chain) { // originAddress should be of form address::module::type if (!(0, sdk_aptos_1.isValidAptosType)(tokenId.address.toString())) { return null; } return tokenId.address.toString(); } // non-native asset, derive unique address const wrappedAssetAddress = AptosTokenBridge.getForeignAssetAddress(this.chain, this.tokenBridgeAddress, tokenId); return `${wrappedAssetAddress}::coin::T`; } /** * Given a hash, returns the fully qualified type by querying the corresponding TypeInfo. * @param address Hash of fully qualified type * @returns The fully qualified type associated with the given hash */ async getTypeFromExternalAddress(address) { try { // get handle const state = await this.connection.getAccountResource({ accountAddress: this.tokenBridgeAddress, resourceType: `${this.tokenBridgeAddress}::state::State`, }); const { handle } = state.native_infos; // get type info const typeInfo = await this.connection.getTableItem({ handle, data: { key_type: `${this.tokenBridgeAddress}::token_hash::TokenHash`, value_type: "0x1::type_info::TypeInfo", key: { hash: address }, }, }); return typeInfo ? [ typeInfo.account_address, String.fromCharCode(...sdk_connect_1.encoding.hex.decode(typeInfo.module_name)), String.fromCharCode(...sdk_connect_1.encoding.hex.decode(typeInfo.struct_name)), ].join(sdk_aptos_1.APTOS_SEPARATOR) : null; } catch { return null; } } /** * Derive the module address for an asset defined by the given origin chain and address. * @param tokenBridgeAddress Address of token bridge (32 bytes) * @param originChain Chain ID of chain that original asset is from * @param originAddress Native address of asset * @returns The module address for the given asset */ static getForeignAssetAddress(chain, tokenBridgeAddress, tokenId) { if ((0, sdk_connect_1.isNative)(tokenId.address)) throw new Error("Invalid token address"); const data = (0, foreignAddress_js_1.serializeForeignAddressSeeds)({ chain: tokenId.chain, tokenBridgeAddress: new sdk_aptos_1.AptosAddress(tokenBridgeAddress).toUniversalAddress(), tokenId: tokenId.address.toUniversalAddress(), }); return sdk_connect_1.encoding.hex.encode((0, sdk_connect_1.sha3_256)(data), true); } createUnsignedTx(txReq, description, parallelizable = false) { return new sdk_aptos_1.AptosUnsignedTransaction(txReq, this.network, this.chain, description, parallelizable); } } exports.AptosTokenBridge = AptosTokenBridge; //# sourceMappingURL=tokenBridge.js.map