8000 feat(sdk-router): Gas.zip module [SYN-37] by ChiTimesChi · Pull Request #3528 · synapsecns/sanguine · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat(sdk-router): Gas.zip module [SYN-37] #3528

8000
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 27 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
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 Feb 21, 2025
2aa3ed5
refactor: isolate logging utils
ChiTimesChi Feb 20, 2025
9d21a92
refactor: isolate API utils
ChiTimesChi Feb 20, 2025
4706c76
feat: bridge, status first impl
ChiTimesChi Feb 21, 2025
7456b42
feat: supported chain ID check
ChiTimesChi Feb 21, 2025
19c9757
feat: `isSameAddress`
ChiTimesChi Nov 8, 2024
16fdfc4
feat: gas.zip quotes
ChiTimesChi Feb 21, 2025
d9a661f
feat: no-op slippage
ChiTimesChi Feb 21, 2025
232840c
feat: add gas.zip module
ChiTimesChi Feb 21, 2025
4c15047
fix: sanitize expected amount from gas.zip
ChiTimesChi Feb 21, 2025
7827627
fix: bridge module name
ChiTimesChi Feb 21, 2025
3072d75
feat: prioritize gas.zip quotes for testing [REVERT LATER]
ChiTimesChi Feb 21, 2025
b4861b7
feat: regenerate bridge map with gas.zip [SYN-38]
ChiTimesChi Feb 22, 2025
c000cd9
feat: don't show slippage for gas.zip for now [SYN-39]
ChiTimesChi Feb 22, 2025
24ff10d
feat: add BNB, BERA and other native tokens
ChiTimesChi Feb 23, 2025
229f41f
feat: add HyperEVM to the list of chains
ChiTimesChi Feb 23, 2025
67466d9
feat: add HYPE to bridge map
ChiTimesChi Feb 23, 2025
1c20008
chore: add hyperEVM to spellcheck
ChiTimesChi Feb 24, 2025
0a754e3
chore: clean up TODOs
ChiTimesChi Feb 24, 2025
8f66b0e
fix: remove trailing slash
ChiTimesChi Feb 24, 2025
219f604
Merge branch 'master' into feat/gas-zip-module
ChiTimesChi Feb 25, 2025
d896cc6
fix: remove unsupported gas.zip assets [SYN-53]
ChiTimesChi Feb 26, 2025
8991ac0
Merge branch 'master' into feat/gas-zip-module
ChiTimesChi Feb 26, 2025
dbccbce
Revert "feat: prioritize gas.zip quotes for testing [REVERT LATER]"
ChiTimesChi Feb 26, 2025
061ee1f
feat: track gas.zip refund status
ChiTimesChi Feb 26, 2025
2b71f07
fix: gas.zip refund tracking
ChiTimesChi Feb 26, 2025
29910be
refactor: logs cleanup, better typing
ChiTimesChi Feb 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"gitbook",
"gorm",
"headlessui",
"hyperevm",
"hyperliquid",
"incentivized",
"interchain",
Expand Down
108 changes: 108 additions & 0 deletions packages/sdk-router/src/gaszip/api.ts
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,
}
}
69 changes: 69 additions & 0 deletions packages/sdk-router/src/gaszip/gasZipModule.ts
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,
}
}

/**
* @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)
}
}
172 changes: 172 additions & 0 deletions packages/sdk-router/src/gaszip/gasZipModuleSet.ts
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
}
}
3 changes: 3 additions & 0 deletions packages/sdk-router/src/gaszip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './api'
export * from './gasZipModule'
export * from './gasZipModuleSet'
Loading
Loading
0