@wormhole-foundation/sdk-aptos-tokenbridge
Version:
SDK for Solana, used in conjunction with @wormhole-foundation/sdk
253 lines • 11.3 kB
JavaScript
"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