Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file added apps/dapp/public/skipgo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/sda/public/skipgo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@
"optional": "optional",
"options": "Options",
"orUploadOne": "...or upload one below.",
"outputAmount": "Output amount",
"outputToken": "Output token",
"parameterChanges": "Parameter changes",
"passingThresholdDescription": "The proportion of voters on a single choice (Yes/No/Abstain) proposal who must vote 'Yes' for it to pass. Passing threshold works differently depending on whether your DAO has a quorum.\n- If your DAO *has a quorum*, the passing threshold is only calculated from those who voted.\n- If your DAO has *no quorum*, this is the percentage of the DAO's voting power that must vote 'yes' for a proposal to pass.",
Expand Down
11 changes: 9 additions & 2 deletions packages/state/indexer/snapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@ export const querySnapper = async <T = any>({
const response = await fetch(url)

if (response.status >= 300) {
throw new Error(
const text = await (await response.text().catch(() => '')).trim()

console.error(
`Error querying snapper for ${query} with params ${params.toString()}: ${
response.status
} ${await response.text().catch(() => '')}`.trim()
} ${response.statusText} ${text}`.trim()
)

throw new Error(
text ||
`Unknown Snapper query error: ${response.status} ${response.statusText}`
)
}

Expand Down
2 changes: 2 additions & 0 deletions packages/state/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"@dao-dao/math": "2.6.0-rc.1",
"@dao-dao/utils": "2.6.0-rc.1",
"@tanstack/react-query": "^5.40.0",
"ethers": "^6.13.5",
"graphql": "^16.8.1",
"json5": "^2.2.0",
"lodash.uniq": "^4.5.0",
Expand All @@ -37,6 +38,7 @@
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/client-preset": "^4.1.0",
"@graphql-typed-document-node/core": "^3.2.0",
"@skip-go/client": "^0.16.5",
"@types/lodash.uniq": "^4.5.7",
"typescript": "5.4.5"
},
Expand Down
151 changes: 148 additions & 3 deletions packages/state/query/queries/skip.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import type { MsgsDirectResponse } from '@skip-go/client'
import { QueryClient, queryOptions } from '@tanstack/react-query'

import {
AnyChain,
AnyChainSkip,
GenericToken,
GenericTokenSource,
SkipAsset,
SkipChain,
TokenType,
} from '@dao-dao/types'
import { convertSkipChainToAnyChain } from '@dao-dao/utils'
import {
convertSkipAssetToGenericToken,
convertSkipChainToAnyChain,
} from '@dao-dao/utils'

import { indexerQueries } from './indexer'

Expand All @@ -21,7 +26,7 @@ export const fetchSkipChain = async (
}: {
chainId: string
}
): Promise<AnyChain> => {
): Promise<AnyChainSkip> => {
const chain = await queryClient.fetchQuery(
indexerQueries.snapper<SkipChain>({
query: 'skip-chain',
Expand All @@ -38,6 +43,28 @@ export const fetchSkipChain = async (
return convertSkipChainToAnyChain(chain)
}

/**
* Fetch all Skip chains.
*/
export const fetchAllSkipChains = async (
queryClient: QueryClient
): Promise<AnyChainSkip[]> => {
const chains = await queryClient.fetchQuery(
indexerQueries.snapper<SkipChain[]>({
query: 'skip-chains',
parameters: {
all: true,
},
})
)

return (
chains
?.map(convertSkipChainToAnyChain)
.sort((a, b) => a.prettyName.localeCompare(b.prettyName)) ?? []
)
}

/**
* Fetch Skip asset.
*/
Expand All @@ -63,6 +90,30 @@ export const fetchSkipAsset = async (
return asset
}

/**
* Fetch all Skip assets as generic tokens.
*
* Returns a map of chainId to assets.
*/
export const fetchAllSkipAssets = async (
queryClient: QueryClient
): Promise<Record<string, GenericToken[]>> => {
const assets = await queryClient.fetchQuery(
indexerQueries.snapper<Record<string, { assets: SkipAsset[] }>>({
query: 'skip-all-assets',
})
)

return Object.fromEntries(
Object.entries(assets ?? {}).map(([chainId, { assets }]) => [
chainId,
assets
.map(convertSkipAssetToGenericToken)
.sort((a, b) => a.symbol.localeCompare(b.symbol)),
])
)
}

/**
* Fetch Skip recommended asset.
*/
Expand Down Expand Up @@ -112,6 +163,73 @@ export const fetchSkipChainPfmEnabled = async (
)
}

/**
* Fetch the route and messages for a transfer via Skip Go.
*/
export const fetchSkipGoMsgsDirect = async (
queryClient: QueryClient,
{
fromChainId,
fromTokenType,
fromDenomOrAddress,
toChainId,
toTokenType,
toDenomOrAddress,
amount,
slippageTolerancePercent,
timeoutSeconds,
addresses,
smartRelay,
allowSwaps,
}: {
fromChainId: string
fromTokenType: TokenType
fromDenomOrAddress: string
toChainId: string
toTokenType: TokenType
toDenomOrAddress: string
amount: string
slippageTolerancePercent: number
timeoutSeconds: number
addresses: Record<string, string>
smartRelay: boolean
allowSwaps: boolean
}
): Promise<MsgsDirectResponse> => {
const fromDenom =
fromTokenType === TokenType.Cw20
? 'cw20:' + fromDenomOrAddress
: fromDenomOrAddress
const toDenom =
toTokenType === TokenType.Cw20
? 'cw20:' + toDenomOrAddress
: toDenomOrAddress

const data = await queryClient.fetchQuery(
indexerQueries.snapper<MsgsDirectResponse>({
query: 'skip-go-msgs-direct',
parameters: {
fromChainId,
fromDenom,
toChainId,
toDenom,
amountIn: amount,
slippageTolerancePercent,
timeoutSeconds,
addresses: JSON.stringify(addresses),
smartRelay,
allowSwaps,
},
})
)

if (!data) {
throw new Error('No Skip Go msgs direct found')
}

return data
}

export const skipQueries = {
/**
* Fetch Skip chain.
Expand All @@ -124,6 +242,14 @@ export const skipQueries = {
queryKey: ['skip', 'chain', options],
queryFn: () => fetchSkipChain(queryClient, options),
}),
/**
* Fetch all Skip chains.
*/
chains: (queryClient: QueryClient) =>
queryOptions({
queryKey: ['skip', 'chains'],
queryFn: () => fetchAllSkipChains(queryClient),
}),
/**
* Fetch Skip asset.
*/
Expand All @@ -135,6 +261,14 @@ export const skipQueries = {
queryKey: ['skip', 'asset', options],
queryFn: () => fetchSkipAsset(queryClient, options),
}),
/**
* Fetch all Skip assets.
*/
allAssets: (queryClient: QueryClient) =>
queryOptions({
queryKey: ['skip', 'allAssets'],
queryFn: () => fetchAllSkipAssets(queryClient),
}),
/**
* Fetch Skip recommended asset.
*/
Expand Down Expand Up @@ -172,4 +306,15 @@ export const skipQueries = {
queryKey: ['skip', 'chainPfmEnabled', options],
queryFn: () => fetchSkipChainPfmEnabled(queryClient, options),
}),
/**
* Fetch the route and messages for a transfer via Skip Go.
*/
skipGoMsgsDirect: (
queryClient: QueryClient,
options: Parameters<typeof fetchSkipGoMsgsDirect>[1]
) =>
queryOptions({
queryKey: ['skip', 'skipGoMsgsDirect', options],
queryFn: () => fetchSkipGoMsgsDirect(queryClient, options),
}),
}
43 changes: 43 additions & 0 deletions packages/state/query/queries/token.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { QueryClient, queryOptions } from '@tanstack/react-query'
import { ethers } from 'ethers'

import {
ChainId,
Expand All @@ -13,6 +14,7 @@ import {
MAINNET,
bitsongProtoRpcClientRouter,
convertChainRegistryAssetToGenericToken,
ethereumClientRouter,
getChainForChainName,
getFallbackImage,
getIbcTransferInfoFromChannel,
Expand Down Expand Up @@ -114,6 +116,39 @@ export const fetchTokenInfo = async (
}
}

if (type === TokenType.Erc20) {
const provider = await ethereumClientRouter.connect(chainId)

const strategyBaseContract = new ethers.Contract(
denomOrAddress,
[
'function decimals() view returns (uint8)',
'function symbol() view returns (string)',
],
provider
)
const [decimals, symbol] = (await Promise.all([
strategyBaseContract.decimals(),
strategyBaseContract.symbol(),
])) as [bigint, string]

return {
chainId,
type,
denomOrAddress,
symbol,
decimals: Number(decimals),
imageUrl: getFallbackImage(denomOrAddress),
source: await queryClient.fetchQuery(
tokenQueries.source(queryClient, {
chainId,
type,
denomOrAddress,
})
),
}
}

if (type === TokenType.Cw20) {
const [tokenInfo, imageUrl] = await Promise.all([
queryClient.fetchQuery(
Expand Down Expand Up @@ -293,6 +328,14 @@ export const fetchTokenSource = async (
queryClient: QueryClient,
{ chainId, type, denomOrAddress }: GenericTokenSource
): Promise<GenericTokenSource> => {
if (type === TokenType.Erc20) {
return {
chainId,
type,
denomOrAddress,
}
}

// Check if Skip API has the info.
const skipAsset = await queryClient
.fetchQuery(
Expand Down
Loading
Loading