-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(xcm-api): Add support for Snowbridge ❄️
- Loading branch information
1 parent
8c0b76d
commit 6866142
Showing
11 changed files
with
379 additions
and
13 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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,24 @@ | ||
import { z } from 'zod'; | ||
|
||
export const XTransferEthDtoSchema = z.object({ | ||
to: z.string(), | ||
amount: z.union([ | ||
z.string().refine( | ||
(val) => { | ||
const num = parseFloat(val); | ||
return !isNaN(num) && num > 0; | ||
}, | ||
{ | ||
message: 'Amount must be a positive number', | ||
}, | ||
), | ||
z.number().positive({ message: 'Amount must be a positive number' }), | ||
]), | ||
address: z.string().min(1, { message: 'Source address is required' }), | ||
destAddress: z | ||
.string() | ||
.min(1, { message: 'Destination address is required' }), | ||
currency: z.string(), | ||
}); | ||
|
||
export type XTransferEthDto = z.infer<typeof XTransferEthDtoSchema>; |
37 changes: 37 additions & 0 deletions
37
apps/xcm-api/src/x-transfer-eth/x-transfer-eth.controller.ts
This file contains 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,37 @@ | ||
import { Body, Controller, Post, Req, UsePipes } from '@nestjs/common'; | ||
import { AnalyticsService } from '../analytics/analytics.service.js'; | ||
import { XTransferEthService } from './x-transfer-eth.service.js'; | ||
import { EventName } from '../analytics/EventName.js'; | ||
import { ZodValidationPipe } from '../zod-validation-pipe.js'; | ||
import { | ||
XTransferEthDtoSchema, | ||
XTransferEthDto, | ||
} from './dto/x-transfer-eth.dto.js'; | ||
|
||
@Controller('x-transfer-eth') | ||
export class XTransferEthController { | ||
constructor( | ||
private xTransferEthService: XTransferEthService, | ||
private analyticsService: AnalyticsService, | ||
) {} | ||
|
||
private trackAnalytics( | ||
eventName: EventName, | ||
req: Request, | ||
params: XTransferEthDto, | ||
) { | ||
const { to, address, currency } = params; | ||
this.analyticsService.track(eventName, req, { | ||
to, | ||
address, | ||
currency, | ||
}); | ||
} | ||
|
||
@Post() | ||
@UsePipes(new ZodValidationPipe(XTransferEthDtoSchema)) | ||
generateXcmCall(@Body() bodyParams: XTransferEthDto, @Req() req: Request) { | ||
this.trackAnalytics(EventName.GENERATE_ETH_CALL, req, bodyParams); | ||
return this.xTransferEthService.generateEthCall(bodyParams); | ||
} | ||
} |
This file contains 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,9 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { XTransferEthController } from './x-transfer-eth.controller.js'; | ||
import { XTransferEthService } from './x-transfer-eth.service.js'; | ||
|
||
@Module({ | ||
controllers: [XTransferEthController], | ||
providers: [XTransferEthService], | ||
}) | ||
export class XTransferEthModule {} |
103 changes: 103 additions & 0 deletions
103
apps/xcm-api/src/x-transfer-eth/x-transfer-eth.service.ts
This file contains 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,103 @@ | ||
import { | ||
BadRequestException, | ||
Injectable, | ||
InternalServerErrorException, | ||
} from '@nestjs/common'; | ||
import { | ||
getOtherAssets, | ||
getParaId, | ||
InvalidCurrencyError, | ||
NODE_NAMES, | ||
TNode, | ||
} from '@paraspell/sdk'; | ||
import { isValidPolkadotAddress } from '../utils.js'; | ||
import { XTransferEthDto } from './dto/x-transfer-eth.dto.js'; | ||
import { toPolkadot, environment, contextFactory } from '@snowbridge/api'; | ||
|
||
@Injectable() | ||
export class XTransferEthService { | ||
async generateEthCall({ | ||
to, | ||
amount, | ||
address, | ||
destAddress, | ||
currency, | ||
}: XTransferEthDto) { | ||
const toNode = to as TNode; | ||
|
||
if (!NODE_NAMES.includes(toNode)) { | ||
throw new BadRequestException( | ||
`Node ${toNode} is not valid. Check docs for valid nodes.`, | ||
); | ||
} | ||
|
||
if (!isValidPolkadotAddress(destAddress)) { | ||
throw new BadRequestException('Invalid wallet address.'); | ||
} | ||
|
||
const ethAssets = getOtherAssets('Ethereum'); | ||
const ethAsset = ethAssets.find((asset) => asset.symbol === currency); | ||
if (!ethAsset) { | ||
throw new InvalidCurrencyError( | ||
`Currency ${currency} is not supported for Ethereum transfers`, | ||
); | ||
} | ||
|
||
const env = environment.SNOWBRIDGE_ENV['polkadot_mainnet']; | ||
const { config } = env; | ||
|
||
const EXECUTION_URL = 'https://eth.llamarpc.com'; | ||
|
||
const context = await contextFactory({ | ||
ethereum: { | ||
execution_url: EXECUTION_URL, | ||
beacon_url: config.BEACON_HTTP_API, | ||
}, | ||
polkadot: { | ||
url: { | ||
bridgeHub: config.BRIDGE_HUB_URL, | ||
assetHub: config.ASSET_HUB_URL, | ||
relaychain: config.RELAY_CHAIN_URL, | ||
parachains: config.PARACHAINS, | ||
}, | ||
}, | ||
appContracts: { | ||
gateway: config.GATEWAY_CONTRACT, | ||
beefy: config.BEEFY_CONTRACT, | ||
}, | ||
}); | ||
const destParaId = getParaId(toNode); | ||
|
||
const signer = { | ||
getAddress: () => Promise.resolve(address), | ||
}; | ||
|
||
try { | ||
const plan = await toPolkadot.validateSend( | ||
context, | ||
signer, | ||
destAddress, | ||
ethAsset.assetId, | ||
destParaId, | ||
BigInt(amount), | ||
BigInt(0), | ||
); | ||
|
||
if (plan.failure) { | ||
throw new Error( | ||
`Failed to validate send: ${plan.failure.errors.map((e) => e.message).join('\n\n')}`, | ||
); | ||
} | ||
|
||
return { | ||
token: plan.success.token, | ||
destinationParaId: plan.success.destinationParaId, | ||
destinationFee: plan.success.destinationFee, | ||
amount: plan.success.amount, | ||
}; | ||
} catch (e) { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access | ||
throw new InternalServerErrorException(e.message); | ||
} | ||
} | ||
} |
Oops, something went wrong.