Skip to content

Commit

Permalink
Support Ledger Wallets and Fee Estimation (#1366)
Browse files Browse the repository at this point in the history
* support ledger

* add to asset hub

* testing

* added fee estimation methods

* get source address from plan

* ws

* revert comment

* clean up api

* add signer to dry run

* allow transaction creation without a plan

* remove comment

* revert

* update packages
  • Loading branch information
alistair-singh authored Jan 28, 2025
1 parent cb05e1f commit f3dda0b
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 74 deletions.
2 changes: 1 addition & 1 deletion web/packages/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@snowbridge/api",
"version": "0.1.28",
"version": "0.1.29",
"description": "Snowbridge API client",
"license": "Apache-2.0",
"repository": {
Expand Down
115 changes: 105 additions & 10 deletions web/packages/api/src/toEthereum.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ApiPromise } from "@polkadot/api"
import { SubmittableExtrinsic, SubmittableExtrinsicFunction } from "@polkadot/api/types"
import { EventRecord } from "@polkadot/types/interfaces"
import { Codec, IKeyringPair, Signer } from "@polkadot/types/types"
import { AnyTuple, Codec, IKeyringPair, ISubmittableResult, Signer } from "@polkadot/types/types"
import { BN, u8aToHex } from "@polkadot/util"
import { decodeAddress, xxhashAsHex } from "@polkadot/util-crypto"
import { assetStatusInfo, palletAssetsBalance } from "./assets"
Expand Down Expand Up @@ -100,6 +102,91 @@ export const getSendFee = async (
return leFee.eqn(0) ? options.defaultFee : BigInt(leFee.toString())
}

export type SendTokenTx = {
input: {
ethereumChainId: bigint;
sourceAddress: string;
beneficiaryAddress: any;
tokenAddress: string;
amount: bigint;
},
computed: {
assetLocation: any;
sourceAddressHex: `0x${string}`;
destination: any;
beneficiary: any;
assets: any;
fee_asset: number;
weight: string;
extrinsic: SubmittableExtrinsicFunction<"promise", AnyTuple>;
},
tx: SubmittableExtrinsic<"promise", ISubmittableResult>
}

export async function createTx(
sourceParachain: ApiPromise,
ethereumChainId: bigint,
sourceAddress: string,
beneficiaryAddress: string,
tokenAddress: string,
amount: bigint,
): Promise<SendTokenTx> {
const assetLocation = {
parents: 2,
interior: {
X2: [
{ GlobalConsensus: { Ethereum: { chain_id: ethereumChainId } } },
{ AccountKey20: { key: tokenAddress } },
],
},
}
const sourceAddressHex = u8aToHex(decodeAddress(sourceAddress))
const fee_asset = 0
const weight = "Unlimited"
const versionKey = "V3"
const assets: { [key: string]: any } = {}
const transferAsset = {
id: { Concrete: assetLocation },
fun: { Fungible: amount },
}
assets[versionKey] = [transferAsset]
const destination: { [key: string]: any } = {}
destination[versionKey] = {
parents: 2,
interior: {
X1: { GlobalConsensus: { Ethereum: { chain_id: ethereumChainId } } },
},
}
const beneficiaryLocation: { [key: string]: any } = {}
beneficiaryLocation[versionKey] = {
parents: 0,
interior: { X1: { AccountKey20: { key: beneficiaryAddress } } },
}
const extrinsic = sourceParachain.tx.polkadotXcm.transferAssets
const tx = extrinsic(destination, beneficiaryLocation, assets, fee_asset, weight)

return {
input: {
ethereumChainId,
sourceAddress,
beneficiaryAddress,
amount,
tokenAddress,
},
computed: {
assetLocation,
sourceAddressHex,
destination,
beneficiary: beneficiaryLocation,
assets,
fee_asset,
weight,
extrinsic
},
tx
}
}

export const validateSend = async (
context: Context,
signer: WalletOrKeypair,
Expand Down Expand Up @@ -215,7 +302,7 @@ export const validateSend = async (
})

const bridgeStatus = await bridgeStatusInfo(context);

const bridgeOperational = bridgeStatus.toEthereum.operatingMode.outbound === "Normal"
const lightClientLatencyIsAcceptable =
bridgeStatus.toEthereum.latencySeconds < options.acceptableLatencyInSeconds
Expand Down Expand Up @@ -418,7 +505,7 @@ export const send = async (

const parachainSignedTx = await parachainApi.tx.polkadotXcm
.transferAssets(pDestination, pBeneficiary, pAssets, fee_asset, weight)
.signAsync(addressOrPair, { signer: walletSigner })
.signAsync(addressOrPair, { signer: walletSigner, withSignedTransaction: true })

pResult = await new Promise<{
blockNumber: number
Expand Down Expand Up @@ -523,9 +610,17 @@ export const send = async (
interior: { X1: { AccountKey20: { key: plan.success.beneficiary } } },
}

const assetHubSignedTx = await assetHub.tx.polkadotXcm
.transferAssets(destination, beneficiary, assets, fee_asset, weight)
.signAsync(addressOrPair, { signer: walletSigner })
const assetHubUnsigned = await createTx(
assetHub,
plan.success.ethereumChainId,
plan.success.sourceAddress,
plan.success.beneficiary,
plan.success.tokenAddress,
plan.success.amount
);

const assetHubSignedTx = await assetHubUnsigned.tx
.signAsync(addressOrPair, { signer: walletSigner, withSignedTransaction: true })

let result = await new Promise<{
blockNumber: number
Expand Down Expand Up @@ -762,7 +857,7 @@ export const trackSendProgressPolling = async (
messageID.toLowerCase() === success.messageId?.toLowerCase() &&
nonce === success.bridgeHub.nonce &&
channelID.toLowerCase() ==
paraIdToChannelId(success.plan.success?.assetHub.paraId ?? 1000).toLowerCase()
paraIdToChannelId(success.plan.success?.assetHub.paraId ?? 1000).toLowerCase()
) {
success.ethereum.transferBlockNumber = blockNumber
success.ethereum.transferBlockHash = blockHash
Expand Down Expand Up @@ -873,9 +968,9 @@ export async function* trackSendProgress(
messageId.toLowerCase() === success.messageId?.toLowerCase() &&
nonce === success.bridgeHub.nonce &&
channelId.toLowerCase() ==
paraIdToChannelId(
success.plan.success?.assetHub.paraId ?? 1000
).toLowerCase()
paraIdToChannelId(
success.plan.success?.assetHub.paraId ?? 1000
).toLowerCase()
) {
resolve(dispatchSuccess)
gateway.removeListener(InboundMessageDispatched, listener)
Expand Down
Loading

0 comments on commit f3dda0b

Please sign in to comment.