UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

268 lines 12.6 kB
import { BigNumber } from 'ethers'; import { CairoOption, CairoOptionVariant, Contract, cairo, num, shortString, uint256, } from 'starknet'; import { ProtocolType, addressToBytes32, assert, ensure0x, isNullish, } from '@hyperlane-xyz/utils'; import { BaseStarknetAdapter } from '../../app/MultiProtocolApp.js'; import { getStarknetEtherContract, getStarknetHypERC20CollateralContract, getStarknetHypERC20Contract, } from '../../utils/starknet.js'; import { PROTOCOL_TO_DEFAULT_NATIVE_TOKEN } from '../nativeTokenMetadata.js'; const stringFromDecimalNumber = (num) => shortString.decodeShortString(ensure0x(num.toString(16))); export class StarknetTokenAdapter extends BaseStarknetAdapter { chainName; multiProvider; addresses; tokenContract; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; } // This function returns the Contract instance for the given token // If the contract is a Proxy: retrieves the implementation contract ABI and returns that Contract // If the contract is not a Proxy: returns the contract with the token address ABI async getContractInstance() { if (this.tokenContract) { return this.tokenContract; } const provider = this.getProvider(); const { abi } = await provider.getClassAt(this.addresses.tokenAddress); const contractInstance = new Contract(abi, this.addresses.tokenAddress, provider); if (contractInstance.get_implementation) { const { implementation } = await contractInstance.get_implementation(); const contractClass = await provider.getClassByHash(implementation); const implementationContract = new Contract(contractClass.abi, this.addresses.tokenAddress, provider); return (this.tokenContract = implementationContract); } this.tokenContract = contractInstance; return contractInstance; } async getBalance(address) { const contract = await this.getContractInstance(); if (contract.balance_of) return contract.balance_of(address); const response = await contract.balanceOf(address); if (!isNullish(response.balance?.low)) { return uint256.uint256ToBN(response.balance); } return response; } async getMetadata(_isNft) { const contract = await this.getContractInstance(); const [decimals, symbol, name] = await Promise.all([ contract.decimals(), contract.symbol(), contract.name(), ]); return { // Decimals get returned as bigint decimals: Number(decimals.toString()), // strings might be returned as bigint/numbers depending on the ABI // and cairo version of the contract symbol: typeof symbol === 'string' ? symbol : stringFromDecimalNumber(symbol), name: typeof name === 'string' ? name : stringFromDecimalNumber(name), }; } async getMinimumTransferAmount(_recipient) { return 0n; } async isApproveRequired(owner, spender, weiAmountOrId) { const contract = await this.getContractInstance(); const allowance = await contract.allowance(owner, spender); return BigNumber.from(allowance.toString()).lt(BigNumber.from(weiAmountOrId)); } async isRevokeApprovalRequired(_owner, _spender) { return false; } async populateApproveTx({ weiAmountOrId, recipient, }) { const contract = await this.getContractInstance(); return contract.populateTransaction.approve(recipient, weiAmountOrId); } async populateTransferTx({ weiAmountOrId, recipient, }) { const contract = await this.getContractInstance(); return contract.populateTransaction.transfer(recipient, weiAmountOrId); } async getTotalSupply() { const contract = await this.getContractInstance(); return contract.total_supply(); } } class BaseStarknetHypTokenAdapter extends BaseStarknetAdapter { chainName; multiProvider; addresses; contract; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; this.contract = getStarknetHypERC20Contract(addresses.warpRouter, multiProvider.getStarknetProvider(chainName)); } async getBalance(address) { if (this.contract.balance_of) return this.contract.balance_of(address); return this.contract.balanceOf(address); } async getMetadata(_isNft) { const [decimals, symbol, name] = await Promise.all([ this.contract.decimals(), this.contract.symbol(), this.contract.name(), ]); return { decimals, symbol, name }; } async getMinimumTransferAmount(_recipient) { return 0n; } async isApproveRequired(owner, spender, weiAmountOrId) { const allowance = await this.contract.allowance(owner, spender); return BigNumber.from(allowance.toString()).lt(BigNumber.from(weiAmountOrId)); } async isRevokeApprovalRequired(_owner, _spender) { return false; } async populateApproveTx({ weiAmountOrId, recipient, }) { return this.contract.populateTransaction.approve(recipient, weiAmountOrId); } async populateTransferTx({ weiAmountOrId, recipient, }) { return this.contract.populateTransaction.transfer(recipient, weiAmountOrId); } async getTotalSupply() { return this.contract.total_supply(); } } export class StarknetHypSyntheticAdapter extends BaseStarknetHypTokenAdapter { chainName; multiProvider; addresses; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; } async isApproveRequired(_owner, _spender, _weiAmountOrId) { return false; } async isRevokeApprovalRequired(_owner, _spender) { return false; } async getDomains() { return this.contract.domains(); } async getRouterAddress(domain) { const routerAddresses = await this.contract.routers(domain); return Buffer.from(routerAddresses); } async getAllRouters() { const domains = await this.getDomains(); const routers = await Promise.all(domains.map((d) => this.getRouterAddress(d))); return domains.map((d, i) => ({ domain: d, address: routers[i] })); } async getBridgedSupply() { return this.getTotalSupply(); } async quoteTransferRemoteGas({ destination, }) { const gasPayment = await this.contract.quote_gas_payment(destination); return { igpQuote: { amount: BigInt(gasPayment.toString()) } }; } async populateTransferRemoteTx({ weiAmountOrId, destination, recipient, interchainGas, }) { const nonOption = new CairoOption(CairoOptionVariant.None); const { igpQuote } = interchainGas || (await this.quoteTransferRemoteGas({ destination })); return this.contract.populateTransaction.transfer_remote(destination, cairo.uint256(addressToBytes32(recipient)), cairo.uint256(BigInt(weiAmountOrId.toString())), cairo.uint256(BigInt(igpQuote.amount)), nonOption, nonOption); } } export class StarknetHypCollateralAdapter extends StarknetHypSyntheticAdapter { collateralContract; wrappedTokenAddress; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.collateralContract = getStarknetHypERC20CollateralContract(addresses.warpRouter, multiProvider.getStarknetProvider(chainName)); } async getWrappedTokenAddress() { if (!this.wrappedTokenAddress) { this.wrappedTokenAddress = num.toHex64(await this.collateralContract.get_wrapped_token()); } return this.wrappedTokenAddress; } async getWrappedTokenAdapter() { return new StarknetTokenAdapter(this.chainName, this.multiProvider, { tokenAddress: await this.getWrappedTokenAddress(), }); } getBridgedSupply() { return this.getBalance(this.addresses.warpRouter); } async getMetadata(isNft) { const adapter = await this.getWrappedTokenAdapter(); return adapter.getMetadata(isNft); } async isApproveRequired(owner, spender, weiAmountOrId) { const adapter = await this.getWrappedTokenAdapter(); return adapter.isApproveRequired(owner, spender, weiAmountOrId); } async populateApproveTx(params) { const adapter = await this.getWrappedTokenAdapter(); return adapter.populateApproveTx(params); } async populateTransferTx(params) { const adapter = await this.getWrappedTokenAdapter(); return adapter.populateTransferTx(params); } } export class StarknetHypNativeAdapter extends StarknetHypSyntheticAdapter { collateralContract; nativeContract; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.collateralContract = getStarknetHypERC20CollateralContract(addresses.warpRouter, multiProvider.getStarknetProvider(chainName)); const nativeAddress = multiProvider.getChainMetadata(chainName)?.nativeToken?.denom; const tokenAddress = nativeAddress ?? PROTOCOL_TO_DEFAULT_NATIVE_TOKEN[ProtocolType.Starknet].denom; assert(tokenAddress, `Native address not found for chain ${chainName}`); this.nativeContract = getStarknetEtherContract(tokenAddress, multiProvider.getStarknetProvider(chainName)); } async getBalance(address) { return this.nativeContract.balanceOf(address); } async isApproveRequired(owner, spender, weiAmountOrId) { const allowance = await this.nativeContract.allowance(owner, spender); return BigNumber.from(allowance.toString()).lt(BigNumber.from(weiAmountOrId)); } async populateApproveTx({ weiAmountOrId, recipient, }) { return this.nativeContract.populateTransaction.approve(recipient, weiAmountOrId); } async populateTransferRemoteTx({ weiAmountOrId, destination, recipient, interchainGas, }) { const nonOption = new CairoOption(CairoOptionVariant.None); const amount = BigInt(weiAmountOrId.toString()); const gasAmount = BigInt(interchainGas?.igpQuote.amount.toString() ?? '0'); const totalAmount = amount + gasAmount; return this.collateralContract.populateTransaction.transfer_remote(destination, cairo.uint256(addressToBytes32(recipient)), cairo.uint256(amount), cairo.uint256(totalAmount), nonOption, nonOption); } } export class StarknetHypFeeAdapter extends StarknetHypSyntheticAdapter { collateralContract; feeTokenContract; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.collateralContract = getStarknetHypERC20CollateralContract(addresses.warpRouter, multiProvider.getStarknetProvider(chainName)); this.feeTokenContract = getStarknetEtherContract(addresses.warpRouter, multiProvider.getStarknetProvider(chainName)); } async getBalance(address) { return this.feeTokenContract.balanceOf(address); } async isApproveRequired(owner, spender, weiAmountOrId) { const allowance = await this.feeTokenContract.allowance(owner, spender); return BigNumber.from(allowance.toString()).lt(BigNumber.from(weiAmountOrId)); } async populateApproveTx({ weiAmountOrId, recipient, }) { return this.feeTokenContract.populateTransaction.approve(recipient, weiAmountOrId); } async populateTransferRemoteTx({ weiAmountOrId, destination, recipient, interchainGas, }) { const nonOption = new CairoOption(CairoOptionVariant.None); const amount = BigInt(weiAmountOrId.toString()); const gasAmount = BigInt(interchainGas?.igpQuote.amount.toString() ?? '0'); const totalAmount = amount + gasAmount; return this.collateralContract.populateTransaction.transfer_remote(destination, cairo.uint256(addressToBytes32(recipient)), cairo.uint256(amount), cairo.uint256(totalAmount), nonOption, nonOption); } } //# sourceMappingURL=StarknetTokenAdapter.js.map