-
Notifications
You must be signed in to change notification settings - Fork 36
feat(sdk-router): Gas.zip module [SYN-37] #3528
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
282af3e
feat: scaffold GasZip module
ChiTimesChi 2aa3ed5
refactor: isolate logging utils
ChiTimesChi 9d21a92
refactor: isolate API utils
ChiTimesChi 4706c76
feat: bridge, status first impl
ChiTimesChi 7456b42
feat: supported chain ID check
ChiTimesChi 19c9757
feat: `isSameAddress`
ChiTimesChi 16fdfc4
feat: gas.zip quotes
ChiTimesChi d9a661f
feat: no-op slippage
ChiTimesChi 232840c
feat: add gas.zip module
ChiTimesChi 4c15047
fix: sanitize expected amount from gas.zip
ChiTimesChi 7827627
fix: bridge module name
ChiTimesChi 3072d75
feat: prioritize gas.zip quotes for testing [REVERT LATER]
ChiTimesChi b4861b7
feat: regenerate bridge map with gas.zip [SYN-38]
ChiTimesChi c000cd9
feat: don't show slippage for gas.zip for now [SYN-39]
ChiTimesChi 24ff10d
feat: add BNB, BERA and other native tokens
ChiTimesChi 229f41f
feat: add HyperEVM to the list of chains
ChiTimesChi 67466d9
feat: add HYPE to bridge map
ChiTimesChi 1c20008
chore: add hyperEVM to spellcheck
ChiTimesChi 0a754e3
chore: clean up TODOs
ChiTimesChi 8f66b0e
fix: remove trailing slash
ChiTimesChi 219f604
Merge branch 'master' into feat/gas-zip-module
ChiTimesChi d896cc6
fix: remove unsupported gas.zip assets [SYN-53]
ChiTimesChi 8991ac0
Merge branch 'master' into feat/gas-zip-module
ChiTimesChi dbccbce
Revert "feat: prioritize gas.zip quotes for testing [REVERT LATER]"
ChiTimesChi 061ee1f
feat: track gas.zip refund status
ChiTimesChi 2b71f07
fix: gas.zip refund tracking
ChiTimesChi 29910be
refactor: logs cleanup, better typing
ChiTimesChi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,7 @@ | |
"gitbook", | ||
"gorm", | ||
"headlessui", | ||
"hyperevm", | ||
"hyperliquid", | ||
"incentivized", | ||
"interchain", | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import { BigNumber } from 'ethers' | ||
import { Zero } from '@ethersproject/constants' | ||
|
||
import { BigintIsh } from '../constants' | ||
import { getWithTimeout } from '../utils/api' | ||
|
||
const GAS_ZIP_API_URL = 'https://backend.gas.zip/v2' | ||
const GAS_ZIP_API_TIMEOUT = 2000 | ||
|
||
interface Transaction { | ||
status: string | ||
} | ||
|
||
interface TransactionStatusData { | ||
txs: Transaction[] | ||
} | ||
|
||
interface Chains { | ||
chains: [ | ||
{ | ||
name: string | ||
chain: number // native chain id | ||
short: number // unique Gas.zip id | ||
gas: string // gas usage of a simple transfer | ||
gwei: string // current gas price | ||
bal: string // balance of the Gas.zip reloader | ||
rpcs: string[] | ||
symbol: string | ||
price: number | ||
} | ||
] | ||
} | ||
|
||
interface CalldataQuoteResponse { | ||
calldata: string | ||
quotes: { | ||
chain: number | ||
expected: string | ||
gas: string | ||
speed: number | ||
usd: number | ||
}[] | ||
} | ||
|
||
export type GasZipQuote = { | ||
amountOut: BigNumber | ||
calldata: string | ||
} | ||
|
||
const EMPTY_GAS_ZIP_QUOTE: GasZipQuote = { | ||
amountOut: Zero, | ||
calldata: '0x', | ||
} | ||
DD41
|
||
export const getGasZipTxStatus = async (txHash: string): Promise<boolean> => { | ||
const response = await getWithTimeout( | ||
'Gas.Zip API', | ||
`${GAS_ZIP_API_URL}/search/${txHash}`, | ||
GAS_ZIP_API_TIMEOUT | ||
) | ||
if (!response) { | ||
return false | ||
} | ||
const data: TransactionStatusData = await response.json() | ||
return data.txs.length > 0 && data.txs[0].status === 'CONFIRMED' | ||
} | ||
|
||
export const getChainIds = async (): Promise<number[]> => { | ||
const response = await getWithTimeout( | ||
'Gas.Zip API', | ||
`${GAS_ZIP_API_URL}/chains`, | ||
GAS_ZIP_API_TIMEOUT | ||
) | ||
if (!response) { | ||
return [] | ||
} | ||
const data: Chains = await response.json() | ||
return data.chains.map((chain) => chain.chain) | ||
} | ||
|
||
export const getGasZipQuote = async ( | ||
originChainId: number, | ||
destChainId: number, | ||
amount: BigintIsh, | ||
to: string, | ||
from?: string | ||
): Promise<GasZipQuote> => { | ||
const response = await getWithTimeout( | ||
'Gas.Zip API', | ||
`${GAS_ZIP_API_URL}/quotes/${originChainId}/${amount}/${destChainId}`, | ||
GAS_ZIP_API_TIMEOUT, | ||
{ | ||
from, | ||
to, | ||
} | ||
) | ||
if (!response) { | ||
return EMPTY_GAS_ZIP_QUOTE | ||
} | ||
const data: CalldataQuoteResponse = await response.json() | ||
if (data.quotes.length === 0 || !data.quotes[0].expected) { | ||
return EMPTY_GAS_ZIP_QUOTE | ||
} | ||
return { | ||
amountOut: BigNumber.from(data.quotes[0].expected.toString()), | ||
calldata: data.calldata, | ||
} | ||
} | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { Provider } from '@ethersproject/abstract-provider' | ||
import { BigNumber, PopulatedTransaction } from 'ethers' | ||
import invariant from 'tiny-invariant' | ||
|
||
import { Query, SynapseModule } from '../module' | ||
import { BigintIsh } from '../constants' | ||
import { isNativeToken } from '../utils/handleNativeToken' | ||
import { getGasZipQuote, getGasZipTxStatus } from './api' | ||
import { isSameAddress } from '../utils/addressUtils' | ||
|
||
export class GasZipModule implements SynapseModule { | ||
readonly address = '0x391E7C679d29bD940d63be94AD22A25d25b5A604' | ||
|
||
public readonly chainId: number | ||
public readonly provider: Provider | ||
|
||
constructor(chainId: number, provider: Provider) { | ||
invariant(chainId, 'CHAIN_ID_UNDEFINED') | ||
invariant(provider, 'PROVIDER_UNDEFINED') | ||
this.chainId = chainId | ||
this.provider = provider | ||
} | ||
|
||
/** | ||
* @inheritdoc SynapseModule.bridge | ||
*/ | ||
public async bridge( | ||
to: string, | ||
destChainId: number, | ||
token: string, | ||
amount: BigintIsh, | ||
originQuery: Query, | ||
destQuery: Query | ||
): Promise<PopulatedTransaction> { | ||
if (!isNativeToken(token)) { | ||
throw new Error('Non-native token not supported by gas.zip') | ||
} | ||
if (isSameAddress(to, destQuery.rawParams)) { | ||
return { | ||
to: this.address, | ||
value: BigNumber.from(amount), | ||
data: originQuery.rawParams, | ||
} | ||
} | ||
const quote = await getGasZipQuote(this.chainId, destChainId, amount, to) | ||
if (quote.amountOut.lt(destQuery.minAmountOut)) { | ||
throw new Error('Insufficient amount out') | ||
} | ||
return { | ||
to: this.address, | ||
value: BigNumber.from(amount), | ||
data: quote.calldata, | ||
} | ||
} | ||
ChiTimesChi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* @inheritdoc SynapseModule.getSynapseTxId | ||
*/ | ||
public async getSynapseTxId(txHash: string): Promise<string> { | ||
return txHash | ||
} | ||
|
||
/** | ||
* @inheritdoc SynapseModule.getBridgeTxStatus | ||
*/ | ||
public async getBridgeTxStatus(synapseTxId: string): Promise<boolean> { | ||
return getGasZipTxStatus(synapseTxId) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import { BigNumber } from 'ethers' | ||
import { Provider } from '@ethersproject/abstract-provider' | ||
import { AddressZero, Zero } from '@ethersproject/constants' | ||
|
||
import { | ||
BridgeRoute, | ||
createNoSwapQuery, | ||
FeeConfig, | ||
Query, | ||
SynapseModule, | ||
SynapseModuleSet, | ||
} from '../module' | ||
import { ChainProvider } from '../router' | ||
import { getChainIds, getGasZipQuote } from './api' | ||
import { GasZipModule } from './gasZipModule' | ||
import { isNativeToken } from '../utils/handleNativeToken' | ||
import { BigintIsh } from '../constants' | ||
|
||
const MEDIAN_TIME_GAS_ZIP = 30 | ||
|
||
export class GasZipModuleSet extends SynapseModuleSet { | ||
public readonly bridgeModuleName = 'Gas.zip' | ||
public readonly allEvents = [] | ||
|
||
public modules: { | ||
[chainId: number]: GasZipModule | ||
} | ||
public providers: { | ||
[chainId: number]: Provider | ||
} | ||
|
||
private cachedChainIds: number[] | ||
|
||
constructor(chains: ChainProvider[]) { | ||
super() | ||
this.modules = {} | ||
this.providers = {} | ||
this.cachedChainIds = [] | ||
chains.forEach(({ chainId, provider }) => { | ||
this.modules[chainId] = new GasZipModule(chainId, provider) | ||
this.providers[chainId] = provider | ||
}) | ||
} | ||
|
||
/** | ||
* @inheritdoc SynapseModuleSet.getModule | ||
*/ | ||
public getModule(chainId: number): SynapseModule | undefined { | ||
return this.modules[chainId] | ||
} | ||
|
||
/** | ||
* @inheritdoc SynapseModuleSet.getEstimatedTime | ||
*/ | ||
public getEstimatedTime(): number { | ||
return MEDIAN_TIME_GAS_ZIP | ||
} | ||
|
||
/** | ||
* @inheritdoc SynapseModuleSet.getGasDropAmount | ||
*/ | ||
public async getGasDropAmount(): Promise<BigNumber> { | ||
return Zero | ||
} | ||
|
||
/** | ||
* @inheritdoc SynapseModuleSet.getBridgeRoutes | ||
*/ | ||
public async getBridgeRoutes( | ||
originChainId: number, | ||
destChainId: number, | ||
tokenIn: string, | ||
tokenOut: string, | ||
amountIn: BigintIsh, | ||
originUserAddress?: string | ||
): Promise<BridgeRoute[]> { | ||
// Check that both chains are supported by gas.zip | ||
const supportedChainIds = await this.getChainIds() | ||
if ( | ||
!supportedChainIds.includes(originChainId) || | ||
!supportedChainIds.includes(destChainId) | ||
) { | ||
return [] | ||
} | ||
// Check that both tokens are native assets | ||
if (!isNativeToken(tokenIn) || !isNativeToken(tokenOut)) { | ||
return [] | ||
} | ||
const user = originUserAddress ?? AddressZero | ||
const quote = await getGasZipQuote( | ||
originChainId, | ||
destChainId, | ||
amountIn, | ||
user, | ||
user | ||
) | ||
// Check that non-zero amount is returned | ||
if (quote.amountOut.eq(Zero)) { | ||
return [] | ||
} | ||
// Save user address in the origin query raw params | ||
const originQuery = createNoSwapQuery(tokenIn, BigNumber.from(amountIn)) | ||
originQuery.rawParams = quote.calldata | ||
const destQuery = createNoSwapQuery(tokenOut, quote.amountOut) | ||
destQuery.rawParams = user | ||
const route: BridgeRoute = { | ||
originChainId, | ||
destChainId, | ||
originQuery, | ||
destQuery, | ||
bridgeToken: { | ||
symbol: 'NATIVE', | ||
token: tokenIn, | ||
}, | ||
bridgeModuleName: this.bridgeModuleName, | ||
} | ||
return [route] | ||
} | ||
|
||
/** | ||
* @inheritdoc SynapseModuleSet.getFeeData | ||
*/ | ||
public async getFeeData(): Promise<{ | ||
feeAmount: BigNumber | ||
feeConfig: FeeConfig | ||
}> { | ||
// There's no good way to determine the fee for gas.zip | ||
return { | ||
feeAmount: Zero, | ||
feeConfig: { | ||
bridgeFee: 0, | ||
minFee: BigNumber.from(0), | ||
maxFee: BigNumber.from(0), | ||
}, | ||
} | ||
} | ||
|
||
/** | ||
* @inheritdoc SynapseModuleSet.getDefaultPeriods | ||
*/ | ||
public getDefaultPeriods(): { | ||
originPeriod: number | ||
destPeriod: number | ||
} { | ||
// Deadline settings are not supported by gas.zip | ||
return { | ||
originPeriod: 0, | ||
destPeriod: 0, | ||
} | ||
} | ||
|
||
/** | ||
* @inheritdoc SynapseModuleSet.applySlippage | ||
*/ | ||
public applySlippage( | ||
originQueryPrecise: Query, | ||
destQueryPrecise: Query | ||
): { originQuery: Query; destQuery: Query } { | ||
// Slippage settings are not supported by gas.zip | ||
return { | ||
originQuery: originQueryPrecise, | ||
destQuery: destQueryPrecise, | ||
} | ||
} | ||
|
||
private async getChainIds(): Promise<number[]> { | ||
if (this.cachedChainIds.length === 0) { | ||
this.cachedChainIds = await getChainIds() | ||
} | ||
return this.cachedChainIds | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './api' | ||
export * from './gasZipModule' | ||
export * from './gasZipModuleSet' |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.