@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
268 lines • 12.6 kB
JavaScript
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