-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* [Chainport] Add trpc endpoints * Remove ethers dependency
- Loading branch information
Showing
9 changed files
with
556 additions
and
0 deletions.
There are no files selected for viewing
46 changes: 46 additions & 0 deletions
46
main/api/chainport/handleGetChainportBridgeTransactionEstimatedFees.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,46 @@ | ||
import { CreateTransactionResponse, RawTransactionSerde } from "@ironfish/sdk"; | ||
|
||
import { | ||
BuildTransactionRequestParamsInputs, | ||
buildTransactionRequestParams, | ||
} from "./utils/buildTransactionRequestParams"; | ||
import { manager } from "../manager"; | ||
|
||
export async function handleGetChainportBridgeTransactionEstimatedFees({ | ||
fromAccount, | ||
txDetails, | ||
}: BuildTransactionRequestParamsInputs) { | ||
const ironfish = await manager.getIronfish(); | ||
const rpcClient = await ironfish.rpcClient(); | ||
const estimatedFees = await rpcClient.chain.estimateFeeRates(); | ||
|
||
const results: CreateTransactionResponse[] = await Promise.all( | ||
[ | ||
estimatedFees.content.slow, | ||
estimatedFees.content.average, | ||
estimatedFees.content.fast, | ||
].map((feeRate) => { | ||
const transactionRequestParams = buildTransactionRequestParams({ | ||
fromAccount, | ||
txDetails, | ||
feeRate, | ||
}); | ||
|
||
return rpcClient.wallet | ||
.createTransaction(transactionRequestParams) | ||
.then((result) => result.content); | ||
}), | ||
); | ||
|
||
const fees: bigint[] = results.map( | ||
(result) => | ||
RawTransactionSerde.deserialize(Buffer.from(result.transaction, "hex")) | ||
.fee, | ||
); | ||
|
||
return { | ||
slow: parseInt(fees[0].toString()), | ||
average: parseInt(fees[1].toString()), | ||
fast: parseInt(fees[2].toString()), | ||
}; | ||
} |
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,20 @@ | ||
import axios from "axios"; | ||
|
||
import { getChainportEndpoints } from "./utils/getChainportEndpoints"; | ||
import { ChainportTransactionStatus } from "../../../shared/chainport"; | ||
|
||
export async function handleGetChainportTransactionStatus({ | ||
txHash, | ||
baseNetworkId, | ||
}: { | ||
txHash: string; | ||
baseNetworkId: number; | ||
}) { | ||
const endpoints = await getChainportEndpoints(); | ||
const url = `${endpoints.baseUrl}/api/port?base_tx_hash=${txHash}&base_network_id=${baseNetworkId}`; | ||
|
||
const response = await axios(url); | ||
const data = response.data as ChainportTransactionStatus; | ||
|
||
return data; | ||
} |
39 changes: 39 additions & 0 deletions
39
main/api/chainport/handleSendChainportBridgeTransaction.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,39 @@ | ||
import { RawTransactionSerde } from "@ironfish/sdk"; | ||
import { z } from "zod"; | ||
|
||
import { | ||
buildTransactionRequestParams, | ||
buildTransactionRequestParamsInputs, | ||
} from "./utils/buildTransactionRequestParams"; | ||
import { manager } from "../manager"; | ||
|
||
export const handleSendChainportBridgeTransactionInput = | ||
buildTransactionRequestParamsInputs.extend({ | ||
fee: z.number(), | ||
}); | ||
|
||
export async function handleSendChainportBridgeTransaction({ | ||
fromAccount, | ||
txDetails, | ||
fee, | ||
}: z.infer<typeof handleSendChainportBridgeTransactionInput>) { | ||
const ironfish = await manager.getIronfish(); | ||
const rpcClient = await ironfish.rpcClient(); | ||
|
||
const params = buildTransactionRequestParams({ | ||
fromAccount, | ||
txDetails, | ||
fee, | ||
}); | ||
|
||
const createResponse = await rpcClient.wallet.createTransaction(params); | ||
const bytes = Buffer.from(createResponse.content.transaction, "hex"); | ||
const rawTx = RawTransactionSerde.deserialize(bytes); | ||
|
||
const postResponse = await rpcClient.wallet.postTransaction({ | ||
transaction: RawTransactionSerde.serialize(rawTx).toString("hex"), | ||
account: fromAccount, | ||
}); | ||
|
||
return postResponse.content; | ||
} |
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,185 @@ | ||
import axios from "axios"; | ||
import { z } from "zod"; | ||
|
||
import { handleGetChainportBridgeTransactionEstimatedFees } from "./handleGetChainportBridgeTransactionEstimatedFees"; | ||
import { handleGetChainportTransactionStatus } from "./handleGetChainportTransactionStatus"; | ||
import { | ||
handleSendChainportBridgeTransaction, | ||
handleSendChainportBridgeTransactionInput, | ||
} from "./handleSendChainportBridgeTransaction"; | ||
import { buildTransactionRequestParamsInputs } from "./utils/buildTransactionRequestParams"; | ||
import { decodeChainportMemo } from "./utils/decodeChainportMemo"; | ||
import { getChainportEndpoints } from "./utils/getChainportEndpoints"; | ||
import { | ||
ChainportBridgeTransaction, | ||
ChainportTargetNetwork, | ||
ChainportToken, | ||
assertMetadataApiResponse, | ||
assertTokensApiResponse, | ||
} from "../../../shared/chainport"; | ||
import { logger } from "../ironfish/logger"; | ||
import { t } from "../trpc"; | ||
|
||
export const chainportRouter = t.router({ | ||
getChainportTokens: t.procedure.query(async () => { | ||
const { tokensEndpoint, metadataEndpoint } = await getChainportEndpoints(); | ||
|
||
try { | ||
const [tokensResponse, metadataResponse] = await Promise.all([ | ||
axios.get(tokensEndpoint), | ||
axios.get(metadataEndpoint), | ||
]); | ||
|
||
const tokensData = assertTokensApiResponse(tokensResponse.data); | ||
const chainportMeta = assertMetadataApiResponse(metadataResponse.data); | ||
|
||
const chainportTokens = tokensData.verified_tokens.map((token) => { | ||
const targetNetworks: Array<ChainportTargetNetwork> = | ||
token.target_networks | ||
.map((networkId) => { | ||
const networkDetails = chainportMeta.cp_network_ids[networkId]; | ||
if (!networkDetails) { | ||
throw new Error(`Unknown network id: ${networkId}`); | ||
} | ||
return { | ||
chainportNetworkId: networkId, | ||
label: networkDetails.label, | ||
networkIcon: networkDetails.network_icon, | ||
chainId: networkDetails.chain_id, | ||
value: networkDetails.chain_id | ||
? networkDetails.chain_id.toString() | ||
: "", | ||
}; | ||
}) | ||
.filter((item) => { | ||
return item.value !== null; | ||
}); | ||
|
||
return { | ||
chainportId: token.id, | ||
ironfishId: token.web3_address, | ||
symbol: token.symbol, | ||
name: token.name, | ||
decimals: token.decimals, | ||
targetNetworks, | ||
}; | ||
}); | ||
|
||
const tokenEntries = chainportTokens.map<[string, ChainportToken]>( | ||
(token) => [token.ironfishId, token], | ||
); | ||
const chainportTokensMap: Record<string, ChainportToken> = | ||
Object.fromEntries(tokenEntries); | ||
|
||
const networksEntries = chainportTokens.flatMap((token) => | ||
token.targetNetworks.map<[string, ChainportTargetNetwork]>( | ||
(network) => [network.value, network], | ||
), | ||
); | ||
const chainportNetworksMap: Record<string, ChainportTargetNetwork> = | ||
Object.fromEntries(networksEntries); | ||
return { | ||
chainportTokens, | ||
chainportTokensMap, | ||
chainportNetworksMap, | ||
}; | ||
} catch (err) { | ||
logger.error(`Failed to fetch Chainport tokens data. | ||
${err} | ||
`); | ||
throw err; | ||
} | ||
}), | ||
getChainportBridgeTransactionDetails: t.procedure | ||
.input( | ||
z.object({ | ||
amount: z.string(), | ||
assetId: z.string(), | ||
to: z.string(), | ||
selectedNetwork: z.string(), | ||
}), | ||
) | ||
.query(async (opts) => { | ||
const endpoints = await getChainportEndpoints(); | ||
|
||
const { amount, assetId, to, selectedNetwork } = opts.input; | ||
|
||
const url = `${ | ||
endpoints.baseUrl | ||
}/ironfish/metadata?raw_amount=${amount.toString()}&asset_id=${assetId}&target_network_id=${selectedNetwork}&target_web3_address=${to}`; | ||
|
||
const response = await fetch(url); | ||
const data: | ||
| ChainportBridgeTransaction | ||
| { | ||
error: { | ||
code: string; | ||
description: string; | ||
}; | ||
} = await response.json(); | ||
|
||
if ("error" in data) { | ||
throw new Error(data.error.description); | ||
} | ||
|
||
if (!response.ok) { | ||
logger.error(`Failed to fetch chainport bridge transaction details. | ||
${data}`); | ||
throw new Error("A network error occured, please try again"); | ||
} | ||
|
||
return data; | ||
}), | ||
getChainportBridgeTransactionEstimatedFees: t.procedure | ||
.input(buildTransactionRequestParamsInputs) | ||
.query(async (opts) => { | ||
const result = await handleGetChainportBridgeTransactionEstimatedFees( | ||
opts.input, | ||
); | ||
return result; | ||
}), | ||
sendChainportBridgeTransaction: t.procedure | ||
.input(handleSendChainportBridgeTransactionInput) | ||
.mutation(async (opts) => { | ||
const result = await handleSendChainportBridgeTransaction(opts.input); | ||
return result; | ||
}), | ||
getChainportTransactionStatus: t.procedure | ||
.input( | ||
z.object({ | ||
transactionHash: z.string(), | ||
baseNetworkId: z.number(), | ||
}), | ||
) | ||
.query(async (opts) => { | ||
const result = handleGetChainportTransactionStatus({ | ||
txHash: opts.input.transactionHash, | ||
baseNetworkId: opts.input.baseNetworkId, | ||
}); | ||
return result; | ||
}), | ||
getChainportMeta: t.procedure.query(async () => { | ||
const { metadataEndpoint } = await getChainportEndpoints(); | ||
const response = await axios.get(metadataEndpoint); | ||
const data = assertMetadataApiResponse(response.data); | ||
return data; | ||
}), | ||
decodeMemo: t.procedure | ||
.input( | ||
z.object({ | ||
memo: z.string(), | ||
}), | ||
) | ||
.query(async (opts) => { | ||
try { | ||
const result = decodeChainportMemo( | ||
Buffer.from(opts.input.memo).toString(), | ||
); | ||
return result; | ||
} catch (_err) { | ||
return null; | ||
} | ||
}), | ||
}); |
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,55 @@ | ||
import { CreateTransactionRequest, CurrencyUtils } from "@ironfish/sdk"; | ||
import { z } from "zod"; | ||
|
||
export const buildTransactionRequestParamsInputs = z.object({ | ||
fromAccount: z.string(), | ||
txDetails: z.object({ | ||
bridge_output: z.object({ | ||
publicAddress: z.string(), | ||
amount: z.string(), | ||
memoHex: z.string(), | ||
assetId: z.string(), | ||
}), | ||
gas_fee_output: z.object({ | ||
publicAddress: z.string(), | ||
amount: z.string(), | ||
memo: z.string(), | ||
}), | ||
}), | ||
fee: z.number().optional(), | ||
feeRate: z.string().optional(), | ||
}); | ||
|
||
export type BuildTransactionRequestParamsInputs = z.infer< | ||
typeof buildTransactionRequestParamsInputs | ||
>; | ||
|
||
export function buildTransactionRequestParams({ | ||
fromAccount, | ||
txDetails, | ||
fee, | ||
feeRate, | ||
}: BuildTransactionRequestParamsInputs) { | ||
const params: CreateTransactionRequest = { | ||
account: fromAccount, | ||
outputs: [ | ||
{ | ||
publicAddress: txDetails.bridge_output.publicAddress, | ||
amount: txDetails.bridge_output.amount, | ||
memoHex: txDetails.bridge_output.memoHex, | ||
assetId: txDetails.bridge_output.assetId, | ||
}, | ||
{ | ||
publicAddress: txDetails.gas_fee_output.publicAddress, | ||
amount: txDetails.gas_fee_output.amount, | ||
memo: txDetails.gas_fee_output.memo, | ||
}, | ||
], | ||
fee: fee ? CurrencyUtils.encode(BigInt(fee)) : null, | ||
feeRate: feeRate ?? null, | ||
expiration: undefined, | ||
confirmations: undefined, | ||
}; | ||
|
||
return params; | ||
} |
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,33 @@ | ||
function decodeNumberFrom10Bits(bits: string) { | ||
return parseInt("0" + bits.slice(1, 10), 2); | ||
} | ||
|
||
function decodeCharFrom6Bits(bits: string) { | ||
const num = parseInt(bits, 2); | ||
if (num < 10) { | ||
return num.toString(); | ||
} | ||
return String.fromCharCode(num - 10 + "a".charCodeAt(0)); | ||
} | ||
|
||
export function decodeChainportMemo( | ||
encodedHex: string, | ||
): [number, string, boolean] { | ||
const hexInteger = BigInt("0x" + encodedHex); | ||
const encodedString = hexInteger.toString(2); | ||
const padded = encodedString.padStart(250, "0"); | ||
const networkId = decodeNumberFrom10Bits(padded); | ||
|
||
const toIronfish = padded[0] === "1"; | ||
const addressCharacters = []; | ||
|
||
for (let i = 10; i < padded.length; i += 6) { | ||
const j = i + 6; | ||
const charBits = padded.slice(i, j); | ||
addressCharacters.push(decodeCharFrom6Bits(charBits)); | ||
} | ||
|
||
const address = "0x" + addressCharacters.join(""); | ||
|
||
return [networkId, address.toLowerCase(), toIronfish]; | ||
} |
Oops, something went wrong.