Skip to content

Commit

Permalink
Feat/chainflip broker improvs (#1129)
Browse files Browse the repository at this point in the history
* add new props to chainflip getDepositAddress request

* add changeset

* clean up logic

* lint

* feat: granular export of api methods

---------

Co-authored-by: towan <95243956+towanTG@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 12, 2024
1 parent 6a0e9d9 commit a3ef441
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 227 deletions.
5 changes: 5 additions & 0 deletions .changeset/blue-rings-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@swapkit/api": minor
---

Export sub api collections
5 changes: 5 additions & 0 deletions .changeset/old-bags-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@swapkit/plugin-chainflip": minor
---

Add props to getDepositAddress
121 changes: 2 additions & 119 deletions packages/plugins/chainflip/src/broker.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { Assets, Chains } from "@chainflip/sdk/swap";
import { AssetValue, SwapKitError, SwapKitNumber, wrapWithThrow } from "@swapkit/helpers";
import { AssetValue, SwapKitError, wrapWithThrow } from "@swapkit/helpers";
import { Chain } from "@swapkit/helpers";
import type { ETHToolbox } from "@swapkit/toolbox-evm";
import type { ChainflipToolbox } from "@swapkit/toolbox-substrate";
import bs58 from "bs58";

import { decodeAddress } from "@polkadot/keyring";
import { isHex, u8aToHex } from "@polkadot/util";
import { toCFTicker } from "./assets";
import { chainflipGateway } from "./chainflipGatewayABI";
import type {
RequestSwapDepositAddressParams,
SwapDepositResponse,
WithdrawFeeResponse,
} from "./types";
import type { WithdrawFeeResponse } from "./types";

export const chainToChainflipChain = new Map<Chain, keyof typeof Chains>([
[Chain.Arbitrum, Chains.Arbitrum],
Expand Down Expand Up @@ -47,27 +41,6 @@ export const assetIdentifierToChainflipTicker = new Map<string, string>([
["SOL.USDC-EPJFWDD5AUFQSSQEM2QN1XZYBAPC8G4WEGGKZWYTDT1V", "SolUsdc"],
]);

const decodeChainflipAddress = (address: string, chain: Chain) => {
switch (chain) {
case Chain.Solana:
return bs58.encode(new Uint8Array(Buffer.from(address.replace("0x", ""), "hex")));
default:
return address;
}
};

const encodeChainflipAddress =
(toolbox: Awaited<ReturnType<typeof ChainflipToolbox>>) => (address: string, chain: Chain) => {
switch (chain) {
case Chain.Solana:
return toolbox.encodeAddress(bs58.decode(address), "hex");
case Chain.Polkadot:
return toolbox.encodeAddress(toolbox.decodeAddress(address), "hex");
default:
return address;
}
};

const registerAsBroker = (toolbox: Awaited<ReturnType<typeof ChainflipToolbox>>) => () => {
const extrinsic = toolbox.api.tx.swapping?.registerAsBroker?.();

Expand All @@ -78,94 +51,6 @@ const registerAsBroker = (toolbox: Awaited<ReturnType<typeof ChainflipToolbox>>)
return toolbox.signAndBroadcast(extrinsic);
};

const requestSwapDepositAddress =
(toolbox: Awaited<ReturnType<typeof ChainflipToolbox>>) =>
async ({
route,
sellAsset,
buyAsset,
recipient: _recipient,
brokerCommissionBPS = 0,
ccmMetadata = null,
maxBoostFeeBps = 0,
}: RequestSwapDepositAddressParams) => {
const sellAssetValue = sellAsset || (route && AssetValue.from({ asset: route.sellAsset }));
const buyAssetValue = buyAsset || (route && AssetValue.from({ asset: route.buyAsset }));
const recipient = _recipient || route?.destinationAddress;

if (!(sellAssetValue && buyAssetValue && recipient)) {
throw new SwapKitError("chainflip_broker_invalid_params");
}

const recipientAddress = wrapWithThrow(() => {
return encodeChainflipAddress(toolbox)(recipient, buyAsset?.chain || buyAssetValue.chain);
}, "chainflip_broker_recipient_error");

return new Promise<SwapDepositResponse>((resolve) => {
const tx = toolbox.api.tx.swapping?.requestSwapDepositAddress?.(
toCFTicker(sellAssetValue),
toCFTicker(buyAssetValue),
{ [buyAssetValue.chain.toLowerCase()]: recipientAddress },
SwapKitNumber.fromBigInt(BigInt(brokerCommissionBPS)).getBaseValue("number"),
ccmMetadata,
maxBoostFeeBps,
);

if (!tx) {
throw new SwapKitError("chainflip_broker_tx_error");
}

toolbox.signAndBroadcast(tx, async (result) => {
if (!result.status?.isFinalized) {
return;
}

const depositChannelEvent = result.events.find(
(event) => event.event.method === "SwapDepositAddressReady",
);

if (!depositChannelEvent) {
throw new SwapKitError(
"chainflip_channel_error",
"Could not find 'SwapDepositAddressReady' event",
);
}

const {
event: {
data: {
depositAddress: depositAddressRaw,
sourceChainExpiryBlock,
destinationAddress,
channelId,
},
},
} = depositChannelEvent.toHuman() as any;

const hash = result.status?.toJSON?.() as { finalized: string };
const header = await toolbox.api.rpc.chain.getHeader(hash?.finalized);
const depositChannelId = `${header.number}-${chainToChainflipChain.get(
sellAssetValue.chain,
)}-${channelId.replaceAll(",", "")}`;

const depositAddress = decodeChainflipAddress(
Object.values(depositAddressRaw)[0] as string,
sellAssetValue.chain,
);

resolve({
brokerCommissionBPS,
buyAsset: buyAssetValue,
depositAddress,
depositChannelId,
recipient: Object.values(destinationAddress)[0] as string,
sellAsset: sellAssetValue,
srcChainExpiryBlock: Number((sourceChainExpiryBlock as string).replaceAll(",", "")),
});
});
});
};

const withdrawFee =
(toolbox: Awaited<ReturnType<typeof ChainflipToolbox>>) =>
({ feeAsset, recipient }: { feeAsset: AssetValue; recipient: string }) => {
Expand Down Expand Up @@ -258,8 +143,6 @@ export const ChainflipBroker = (
chainflipToolbox: Awaited<ReturnType<typeof ChainflipToolbox>>,
) => ({
registerAsBroker: registerAsBroker(chainflipToolbox),
requestSwapDepositAddress: requestSwapDepositAddress(chainflipToolbox),
fundStateChainAccount: fundStateChainAccount(chainflipToolbox),
withdrawFee: withdrawFee(chainflipToolbox),
decodeChainflipAddress,
});
117 changes: 11 additions & 106 deletions packages/plugins/chainflip/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type DepositAddressRequest, SwapSDK } from "@chainflip/sdk/swap";
import { swapkitApiEndpoints } from "@swapkit/api";
import {
AssetValue,
type EVMWallets,
Expand All @@ -9,111 +9,19 @@ import {
type SwapKitPluginParams,
type UTXOWallets,
} from "@swapkit/helpers";
import { assetTickerToChainflipAsset, chainToChainflipChain } from "./broker";
import type { RequestSwapDepositAddressParams } from "./types";

type SupportedChain = keyof (EVMWallets & SubstrateWallets & UTXOWallets & SolanaWallets);

export async function getDepositAddress({
buyAsset,
sellAsset,
recipient,
brokerEndpoint,
maxBoostFeeBps,
brokerCommissionBPS,
ccmParams,
ccmMetadata,
fillOrKillParams,
dcaParams,
chainflipSDKBroker,
}: {
buyAsset: AssetValue;
sellAsset: AssetValue;
recipient: string;
brokerEndpoint: string;
maxBoostFeeBps: number;
brokerCommissionBPS?: number;
ccmParams?: DepositAddressRequest["ccmParams"];
ccmMetadata?: DepositAddressRequest["ccmMetadata"];
dcaParams?: DepositAddressRequest["dcaParams"];
fillOrKillParams?: DepositAddressRequest["fillOrKillParams"];
chainflipSDKBroker?: boolean;
}) {
try {
if (chainflipSDKBroker) {
const chainflipSDK = new SwapSDK({
broker: { url: brokerEndpoint, commissionBps: brokerCommissionBPS || 0 },
network: "mainnet",
});

const srcAsset = assetTickerToChainflipAsset.get(sellAsset.ticker);
const srcChain = chainToChainflipChain.get(sellAsset.chain);
const destAsset = assetTickerToChainflipAsset.get(buyAsset.ticker);
const destChain = chainToChainflipChain.get(buyAsset.chain);

if (!(srcAsset && srcChain && destAsset && destChain)) {
throw new SwapKitError("chainflip_unknown_asset", { sellAsset, buyAsset });
}

const resp = await chainflipSDK.requestDepositAddress({
destAddress: recipient,
srcAsset,
srcChain,
destAsset,
destChain,
amount: sellAsset.getBaseValue("string"),
maxBoostFeeBps,
ccmParams,
ccmMetadata,
fillOrKillParams,
dcaParams,
});

return {
channelId: resp.depositChannelId,
depositAddress: resp.depositAddress,
chain: buyAsset.chain,
};
}

const response = await fetch(brokerEndpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
buyAsset: buyAsset.toString(),
sellAsset: sellAsset.toString(),
destinationAddress: recipient,
maxBoostFeeBps,
ccmParams,
ccmMetadata,
fillOrKillParams,
dcaParams,
}),
}).then((res) => res.json());

if (chainflipSDKBroker && "error" in response.data) {
throw new Error(`RPC error [${response.data.error.code}]: ${response.data.error.message}`);
}

return response as {
channelId: string;
depositAddress: string;
chain: string;
};
} catch (error) {
throw new SwapKitError("chainflip_channel_error", error);
}
}

function plugin({
getWallet,
config: { chainflipBrokerUrl: legacyChainflipBrokerUrl, chainflipBrokerConfig },
}: SwapKitPluginParams<{
chainflipBrokerUrl?: string;
chainflipBrokerConfig?: { chainflipBrokerUrl: string; useChainflipSDKBroker?: boolean };
chainflipBrokerConfig?: { chainflipBrokerUrl: string };
}>) {
async function swap(swapParams: RequestSwapDepositAddressParams) {
const { chainflipBrokerUrl, useChainflipSDKBroker } = chainflipBrokerConfig || {};
const { chainflipBrokerUrl } = chainflipBrokerConfig || {};

const brokerUrl = chainflipBrokerUrl || legacyChainflipBrokerUrl;

Expand Down Expand Up @@ -151,16 +59,14 @@ function plugin({
throw new SwapKitError("core_wallet_connection_not_found");
}

const buyAsset = await AssetValue.from({ asyncTokenLookup: true, asset: buyAssetString });

const { depositAddress } = await getDepositAddress({
brokerEndpoint: brokerUrl,
buyAsset,
recipient,
sellAsset,
maxBoostFeeBps,
chainflipSDKBroker: useChainflipSDKBroker,
...(chainflip ? chainflip : {}),
const { depositAddress } = await swapkitApiEndpoints.getChainflipDepositChannel({
body: {
buyAsset: buyAssetString,
recipient,
sellAsset: sellAssetString,
maxBoostFeeBps,
...(chainflip ? chainflip : {}),
},
});

const tx = await wallet.transfer({
Expand All @@ -175,7 +81,6 @@ function plugin({

return {
swap,
getDepositAddress,
supportedSwapkitProviders: [ProviderName.CHAINFLIP],
};
}
Expand Down
14 changes: 14 additions & 0 deletions packages/plugins/chainflip/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ export type DepositChannelRequest = {
brokerCommissionBPS: number;
ccmMetadata: ccmMetadata | null;
maxBoostFeeBps?: number;
affiliateFees?: AffiliateBroker[];
refundParameters?: SwapRefundParameters;
};

export type ccmMetadata = {
message: string;
gasBudget: string;
cfParameters: string;
};

export type SwapDepositResponse = {
Expand All @@ -30,5 +33,16 @@ export type SwapDepositResponse = {
brokerCommissionBPS: number;
};

export type AffiliateBroker = {
brokerAddress: string;
basisPoints: number;
};

export type SwapRefundParameters = {
retryDuration: number;
refundAddress: string;
minPrice: string;
};

export type RequestSwapDepositAddressParams = Partial<SwapParams<"chainflip", QuoteResponseRoute>> &
Partial<DepositChannelRequest>;
6 changes: 6 additions & 0 deletions packages/swapkit/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ export * from "./thorswapStatic/types";
export * from "./thornode/types";
export * from "./swapkitApi/types";

export { microgardEndpoints };
export { swapkitApiEndpoints };
export { thornodeEndpoints };
export { thorswapStaticEndpoints };
export { mayachainMidgard, thorchainMidgard };

export const SwapKitApi = {
...microgardEndpoints,
...thornodeEndpoints,
Expand Down
Loading

0 comments on commit a3ef441

Please sign in to comment.