From 1bc28d9584b8b4671e10f86e6769d08d3801340b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Thu, 4 Jul 2024 18:53:58 +0200 Subject: [PATCH 01/16] feat: adapt code organization for alpaca calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- libs/coin-modules/coin-stellar/package.json | 1 + .../coin-stellar/src/api/index.ts | 0 .../src/{ => bridge}/broadcast.ts | 4 +- .../{ => bridge}/buildOptimisticOperation.ts | 4 +- .../src/{ => bridge}/buildTransaction.ts | 5 +- .../src/{ => bridge}/createTransaction.ts | 2 +- .../{ => bridge}/deviceTransactionConfig.ts | 2 +- .../src/{ => bridge}/estimateMaxSpendable.ts | 2 +- .../src/{ => bridge}/getTransactionStatus.ts | 6 +- .../coin-stellar/src/bridge/index.ts | 20 +-- .../coin-stellar/src/{ => bridge}/logic.ts | 4 +- .../src/{ => bridge}/prepareTransaction.ts | 4 +- .../src/{ => bridge}/signOperation.ts | 4 +- .../src/{ => bridge}/synchronization.ts | 5 +- .../coin-stellar/src/{ => bridge}/tokens.ts | 2 +- .../src/{ => bridge}/transaction.ts | 2 +- libs/coin-modules/coin-stellar/src/config.ts | 19 +-- libs/coin-modules/coin-stellar/src/index.ts | 1 + .../coin-stellar/src/logic/index.ts | 0 .../coin-stellar/src/network/horizon.ts | 2 +- .../getAddress.ts} | 6 +- .../coin-stellar/src/signer/index.ts | 7 + .../bot-deviceActions.ts} | 2 +- .../src/{specs.ts => test/bot-specs.ts} | 4 +- .../bridgeDatasetTest.ts} | 10 +- .../coin-stellar/src/{ => test}/cli.ts | 4 +- .../coin-stellar/src/types/bridge.ts | 110 ++++++++++++++ .../coin-stellar/src/{ => types}/errors.ts | 0 .../coin-stellar/src/types/index.ts | 113 +-------------- pnpm-lock.yaml | 134 +++++++++++++++++- 30 files changed, 300 insertions(+), 179 deletions(-) create mode 100644 libs/coin-modules/coin-stellar/src/api/index.ts rename libs/coin-modules/coin-stellar/src/{ => bridge}/broadcast.ts (85%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/buildOptimisticOperation.ts (94%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/buildTransaction.ts (94%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/createTransaction.ts (92%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/deviceTransactionConfig.ts (96%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/estimateMaxSpendable.ts (96%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/getTransactionStatus.ts (98%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/logic.ts (99%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/prepareTransaction.ts (91%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/signOperation.ts (94%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/synchronization.ts (93%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/tokens.ts (97%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/transaction.ts (98%) create mode 100644 libs/coin-modules/coin-stellar/src/index.ts create mode 100644 libs/coin-modules/coin-stellar/src/logic/index.ts rename libs/coin-modules/coin-stellar/src/{hw-getAddress.ts => signer/getAddress.ts} (81%) create mode 100644 libs/coin-modules/coin-stellar/src/signer/index.ts rename libs/coin-modules/coin-stellar/src/{speculos-deviceActions.ts => test/bot-deviceActions.ts} (98%) rename libs/coin-modules/coin-stellar/src/{specs.ts => test/bot-specs.ts} (98%) rename libs/coin-modules/coin-stellar/src/{bridge/bridge.integration.test.ts => test/bridgeDatasetTest.ts} (98%) rename libs/coin-modules/coin-stellar/src/{ => test}/cli.ts (95%) create mode 100644 libs/coin-modules/coin-stellar/src/types/bridge.ts rename libs/coin-modules/coin-stellar/src/{ => types}/errors.ts (100%) diff --git a/libs/coin-modules/coin-stellar/package.json b/libs/coin-modules/coin-stellar/package.json index 44d8dfb10857..45cd3df4395d 100644 --- a/libs/coin-modules/coin-stellar/package.json +++ b/libs/coin-modules/coin-stellar/package.json @@ -51,6 +51,7 @@ "@faker-js/faker": "^8.4.1", "@types/invariant": "^2.2.2", "@types/jest": "^29.5.10", + "@types/node": "^16.11.7", "jest": "^29.7.0", "ts-jest": "^29.1.1" }, diff --git a/libs/coin-modules/coin-stellar/src/api/index.ts b/libs/coin-modules/coin-stellar/src/api/index.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/libs/coin-modules/coin-stellar/src/broadcast.ts b/libs/coin-modules/coin-stellar/src/bridge/broadcast.ts similarity index 85% rename from libs/coin-modules/coin-stellar/src/broadcast.ts rename to libs/coin-modules/coin-stellar/src/bridge/broadcast.ts index 0dd1c58b23b9..31c1f57a4af1 100644 --- a/libs/coin-modules/coin-stellar/src/broadcast.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/broadcast.ts @@ -1,7 +1,7 @@ import { patchOperationWithHash } from "@ledgerhq/coin-framework/operation"; import type { AccountBridge, Operation, SignedOperation } from "@ledgerhq/types-live"; -import { broadcastTransaction as apiBroadcast } from "./network"; -import { Transaction } from "./types"; +import { broadcastTransaction as apiBroadcast } from "../network"; +import { Transaction } from "../types"; /** * Broadcast a signed transaction diff --git a/libs/coin-modules/coin-stellar/src/buildOptimisticOperation.ts b/libs/coin-modules/coin-stellar/src/bridge/buildOptimisticOperation.ts similarity index 94% rename from libs/coin-modules/coin-stellar/src/buildOptimisticOperation.ts rename to libs/coin-modules/coin-stellar/src/bridge/buildOptimisticOperation.ts index bc298941a64a..18bb126b4435 100644 --- a/libs/coin-modules/coin-stellar/src/buildOptimisticOperation.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/buildOptimisticOperation.ts @@ -1,9 +1,9 @@ import BigNumber from "bignumber.js"; import { Account } from "@ledgerhq/types-live"; import { encodeOperationId } from "@ledgerhq/coin-framework/operation"; -import { StellarOperation, Transaction } from "./types"; +import { StellarOperation, Transaction } from "../types"; import { getAmountValue } from "./logic"; -import { fetchSequence } from "./network"; +import { fetchSequence } from "../network"; export async function buildOptimisticOperation( account: Account, diff --git a/libs/coin-modules/coin-stellar/src/buildTransaction.ts b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts similarity index 94% rename from libs/coin-modules/coin-stellar/src/buildTransaction.ts rename to libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts index 78c8261eca75..a9f3c8f1ddb0 100644 --- a/libs/coin-modules/coin-stellar/src/buildTransaction.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts @@ -2,16 +2,15 @@ import invariant from "invariant"; import { Memo, Operation as StellarSdkOperation, xdr } from "@stellar/stellar-sdk"; import { AmountRequired, FeeNotLoaded, NetworkDown } from "@ledgerhq/errors"; import type { Account } from "@ledgerhq/types-live"; -import type { Transaction } from "./types"; +import { StellarAssetRequired, StellarMuxedAccountNotExist, type Transaction } from "../types"; import { buildPaymentOperation, buildCreateAccountOperation, buildTransactionBuilder, buildChangeTrustOperation, loadAccount, -} from "./network"; +} from "../network"; import { getRecipientAccount, getAmountValue } from "./logic"; -import { StellarAssetRequired, StellarMuxedAccountNotExist } from "./errors"; /** * @param {Account} account diff --git a/libs/coin-modules/coin-stellar/src/createTransaction.ts b/libs/coin-modules/coin-stellar/src/bridge/createTransaction.ts similarity index 92% rename from libs/coin-modules/coin-stellar/src/createTransaction.ts rename to libs/coin-modules/coin-stellar/src/bridge/createTransaction.ts index c07bc1b9eff0..e1d848016364 100644 --- a/libs/coin-modules/coin-stellar/src/createTransaction.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/createTransaction.ts @@ -1,6 +1,6 @@ import { BigNumber } from "bignumber.js"; import { AccountBridge } from "@ledgerhq/types-live"; -import type { Transaction } from "./types"; +import type { Transaction } from "../types"; /** * Create an empty transaction diff --git a/libs/coin-modules/coin-stellar/src/deviceTransactionConfig.ts b/libs/coin-modules/coin-stellar/src/bridge/deviceTransactionConfig.ts similarity index 96% rename from libs/coin-modules/coin-stellar/src/deviceTransactionConfig.ts rename to libs/coin-modules/coin-stellar/src/bridge/deviceTransactionConfig.ts index c361c291ea41..3a74053a4f6f 100644 --- a/libs/coin-modules/coin-stellar/src/deviceTransactionConfig.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/deviceTransactionConfig.ts @@ -1,6 +1,6 @@ import type { AccountLike, Account } from "@ledgerhq/types-live"; import type { CommonDeviceTransactionField as DeviceTransactionField } from "@ledgerhq/coin-framework/transaction/common"; -import type { Transaction, TransactionStatus } from "./types"; +import type { Transaction, TransactionStatus } from "../types"; export type ExtraDeviceTransactionField = | { diff --git a/libs/coin-modules/coin-stellar/src/estimateMaxSpendable.ts b/libs/coin-modules/coin-stellar/src/bridge/estimateMaxSpendable.ts similarity index 96% rename from libs/coin-modules/coin-stellar/src/estimateMaxSpendable.ts rename to libs/coin-modules/coin-stellar/src/bridge/estimateMaxSpendable.ts index 8fc3b31913a2..0ede5e5dd260 100644 --- a/libs/coin-modules/coin-stellar/src/estimateMaxSpendable.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/estimateMaxSpendable.ts @@ -4,7 +4,7 @@ import { getMainAccount } from "@ledgerhq/coin-framework/account"; import { getTransactionStatus } from "./getTransactionStatus"; import { prepareTransaction } from "./prepareTransaction"; import { createTransaction } from "./createTransaction"; -import type { Transaction } from "./types"; +import type { Transaction } from "../types"; const notCreatedStellarMockAddress = "GAW46JE3SHIAYLNNNQCAZFQ437WB5ZH7LDRDWR5LVDWHCTHCKYB6RCCH"; diff --git a/libs/coin-modules/coin-stellar/src/getTransactionStatus.ts b/libs/coin-modules/coin-stellar/src/bridge/getTransactionStatus.ts similarity index 98% rename from libs/coin-modules/coin-stellar/src/getTransactionStatus.ts rename to libs/coin-modules/coin-stellar/src/bridge/getTransactionStatus.ts index 9736b57d203b..554c699cedd8 100644 --- a/libs/coin-modules/coin-stellar/src/getTransactionStatus.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/getTransactionStatus.ts @@ -14,8 +14,7 @@ import type { AccountBridge } from "@ledgerhq/types-live"; import { findSubAccountById } from "@ledgerhq/coin-framework/account/index"; import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index"; import { isAddressValid, isAccountMultiSign, isMemoValid, getRecipientAccount } from "./logic"; -import { BASE_RESERVE, MIN_BALANCE } from "./network"; -import type { Transaction } from "./types"; +import { BASE_RESERVE, MIN_BALANCE } from "../network"; import { StellarWrongMemoFormat, StellarAssetRequired, @@ -27,7 +26,8 @@ import { StellarNotEnoughNativeBalanceToAddTrustline, StellarMuxedAccountNotExist, StellarSourceHasMultiSign, -} from "./errors"; + type Transaction, +} from "../types"; export const getTransactionStatus: AccountBridge["getTransactionStatus"] = async ( account, diff --git a/libs/coin-modules/coin-stellar/src/bridge/index.ts b/libs/coin-modules/coin-stellar/src/bridge/index.ts index 2e170f3d4b41..9b6aeb2d0b2a 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/index.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/index.ts @@ -5,19 +5,19 @@ import { makeScanAccounts, makeSync, } from "@ledgerhq/coin-framework/bridge/jsHelpers"; -import { estimateMaxSpendable } from "../estimateMaxSpendable"; -import { getTransactionStatus } from "../getTransactionStatus"; +import { estimateMaxSpendable } from "./estimateMaxSpendable"; +import { getTransactionStatus } from "./getTransactionStatus"; import type { StellarSigner, Transaction, TransactionStatus } from "../types"; -import { prepareTransaction } from "../prepareTransaction"; -import { createTransaction } from "../createTransaction"; -import { buildSignOperation } from "../signOperation"; -import { broadcast } from "../broadcast"; +import { prepareTransaction } from "./prepareTransaction"; +import { createTransaction } from "./createTransaction"; +import { buildSignOperation } from "./signOperation"; +import { broadcast } from "./broadcast"; import getAddressWrapper from "@ledgerhq/coin-framework/bridge/getAddressWrapper"; -import resolver from "../hw-getAddress"; +import resolver from "../signer"; import { SignerContext } from "@ledgerhq/coin-framework/signer"; -import { getAccountShape } from "../synchronization"; +import { getAccountShape } from "./synchronization"; import { CoinConfig } from "@ledgerhq/coin-framework/config"; -import { StellarCoinConfig, setCoinConfig } from "../config"; +import stellarCoinConfig, { type StellarCoinConfig } from "../config"; const PRELOAD_MAX_AGE = 30 * 60 * 1000; // 30 minutes @@ -68,7 +68,7 @@ export function createBridges( signerContext: SignerContext, coinConfig: CoinConfig, ) { - setCoinConfig(coinConfig); + stellarCoinConfig.setCoinConfig(coinConfig); return { currencyBridge: buildCurrencyBridge(signerContext), diff --git a/libs/coin-modules/coin-stellar/src/logic.ts b/libs/coin-modules/coin-stellar/src/bridge/logic.ts similarity index 99% rename from libs/coin-modules/coin-stellar/src/logic.ts rename to libs/coin-modules/coin-stellar/src/bridge/logic.ts index 84470c8c06b5..351c875cbf98 100644 --- a/libs/coin-modules/coin-stellar/src/logic.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/logic.ts @@ -19,14 +19,14 @@ import { fetchBaseFee, fetchSigners, loadAccount, -} from "./network"; +} from "../network"; import type { BalanceAsset, RawOperation, StellarOperation, Transaction, TransactionRaw, -} from "./types"; +} from "../types"; export const STELLAR_BURN_ADDRESS = "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF"; diff --git a/libs/coin-modules/coin-stellar/src/prepareTransaction.ts b/libs/coin-modules/coin-stellar/src/bridge/prepareTransaction.ts similarity index 91% rename from libs/coin-modules/coin-stellar/src/prepareTransaction.ts rename to libs/coin-modules/coin-stellar/src/bridge/prepareTransaction.ts index 2f6d31de8107..9103c62f03a2 100644 --- a/libs/coin-modules/coin-stellar/src/prepareTransaction.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/prepareTransaction.ts @@ -1,9 +1,9 @@ import invariant from "invariant"; import type { AccountBridge } from "@ledgerhq/types-live"; import { defaultUpdateTransaction } from "@ledgerhq/coin-framework/bridge/jsHelpers"; -import { fetchAccountNetworkInfo } from "./network"; +import { fetchAccountNetworkInfo } from "../network"; import { getAssetCodeIssuer } from "./logic"; -import type { Transaction } from "./types"; +import type { Transaction } from "../types"; export const prepareTransaction: AccountBridge["prepareTransaction"] = async ( account, diff --git a/libs/coin-modules/coin-stellar/src/signOperation.ts b/libs/coin-modules/coin-stellar/src/bridge/signOperation.ts similarity index 94% rename from libs/coin-modules/coin-stellar/src/signOperation.ts rename to libs/coin-modules/coin-stellar/src/bridge/signOperation.ts index 519b5ffe6b15..fece6ee2b96d 100644 --- a/libs/coin-modules/coin-stellar/src/signOperation.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/signOperation.ts @@ -2,10 +2,10 @@ import { Observable } from "rxjs"; import { FeeNotLoaded } from "@ledgerhq/errors"; import type { Account, AccountBridge } from "@ledgerhq/types-live"; import { SignerContext } from "@ledgerhq/coin-framework/signer"; -import type { Transaction } from "./types"; +import type { Transaction } from "../types"; import { buildTransaction } from "./buildTransaction"; import { buildOptimisticOperation } from "./buildOptimisticOperation"; -import { StellarSigner } from "./types/signer"; +import { StellarSigner } from "../types/signer"; export function buildSignOperation( signerContext: SignerContext, diff --git a/libs/coin-modules/coin-stellar/src/synchronization.ts b/libs/coin-modules/coin-stellar/src/bridge/synchronization.ts similarity index 93% rename from libs/coin-modules/coin-stellar/src/synchronization.ts rename to libs/coin-modules/coin-stellar/src/bridge/synchronization.ts index 2d1554b44d19..1b931e141f90 100644 --- a/libs/coin-modules/coin-stellar/src/synchronization.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/synchronization.ts @@ -2,11 +2,10 @@ import { encodeAccountId } from "@ledgerhq/coin-framework/account/index"; import { inferSubOperations } from "@ledgerhq/coin-framework/serialization/index"; import { Account } from "@ledgerhq/types-live"; import { GetAccountShape, mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers"; -import { fetchAccount, fetchOperations } from "./network"; +import { fetchAccount, fetchOperations } from "../network"; import { buildSubAccounts } from "./tokens"; -import { StellarOperation } from "./types"; +import { StellarBurnAddressError, StellarOperation } from "../types"; import { STELLAR_BURN_ADDRESS } from "./logic"; -import { StellarBurnAddressError } from "./errors"; export const getAccountShape: GetAccountShape = async (info, syncConfig) => { const { address, currency, initialAccount, derivationMode } = info; diff --git a/libs/coin-modules/coin-stellar/src/tokens.ts b/libs/coin-modules/coin-stellar/src/bridge/tokens.ts similarity index 97% rename from libs/coin-modules/coin-stellar/src/tokens.ts rename to libs/coin-modules/coin-stellar/src/bridge/tokens.ts index 1daa366f655c..3469100b5053 100644 --- a/libs/coin-modules/coin-stellar/src/tokens.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/tokens.ts @@ -5,7 +5,7 @@ import type { SyncConfig, TokenAccount } from "@ledgerhq/types-live"; import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies/parseCurrencyUnit"; import { encodeOperationId } from "@ledgerhq/coin-framework/operation"; import { findTokenById, listTokensForCryptoCurrency } from "@ledgerhq/cryptoassets"; -import type { BalanceAsset, StellarOperation } from "./types"; +import type { BalanceAsset, StellarOperation } from "../types"; export const getAssetIdFromTokenId = (tokenId: string): string => tokenId.split("/")[2]; diff --git a/libs/coin-modules/coin-stellar/src/transaction.ts b/libs/coin-modules/coin-stellar/src/bridge/transaction.ts similarity index 98% rename from libs/coin-modules/coin-stellar/src/transaction.ts rename to libs/coin-modules/coin-stellar/src/bridge/transaction.ts index bd8d41c10650..21a075cbdd11 100644 --- a/libs/coin-modules/coin-stellar/src/transaction.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/transaction.ts @@ -10,7 +10,7 @@ import type { Account } from "@ledgerhq/types-live"; import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/formatCurrencyUnit"; import { getAccountCurrency } from "@ledgerhq/coin-framework/account/helpers"; import { getAssetCodeIssuer } from "./logic"; -import type { Transaction, TransactionRaw } from "./types"; +import type { Transaction, TransactionRaw } from "../types"; export function formatTransaction( { amount, recipient, fees, memoValue, useAllAmount, subAccountId }: Transaction, diff --git a/libs/coin-modules/coin-stellar/src/config.ts b/libs/coin-modules/coin-stellar/src/config.ts index 577996a85f6f..57a17cd93368 100644 --- a/libs/coin-modules/coin-stellar/src/config.ts +++ b/libs/coin-modules/coin-stellar/src/config.ts @@ -1,18 +1,9 @@ -import { CurrencyConfig, CoinConfig } from "@ledgerhq/coin-framework/config"; -import { MissingCoinConfig } from "@ledgerhq/coin-framework/errors"; +import buildCoinConfig, { type CurrencyConfig } from "@ledgerhq/coin-framework/config"; -export type StellarCoinConfig = CurrencyConfig; +export type StellarConfig = Record; -let coinConfig: CoinConfig | undefined; +export type StellarCoinConfig = CurrencyConfig & StellarConfig; -export function setCoinConfig(config: CoinConfig): void { - coinConfig = config; -} +const coinConfig = buildCoinConfig(); -export function getCoinConfig(): StellarCoinConfig { - if (!coinConfig) { - throw new MissingCoinConfig(); - } - - return coinConfig(); -} +export default coinConfig; diff --git a/libs/coin-modules/coin-stellar/src/index.ts b/libs/coin-modules/coin-stellar/src/index.ts new file mode 100644 index 000000000000..b1bb866826d3 --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/index.ts @@ -0,0 +1 @@ +export { createBridges } from "./bridge"; diff --git a/libs/coin-modules/coin-stellar/src/logic/index.ts b/libs/coin-modules/coin-stellar/src/logic/index.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/libs/coin-modules/coin-stellar/src/network/horizon.ts b/libs/coin-modules/coin-stellar/src/network/horizon.ts index 7b95bd301212..25b99aa052e4 100644 --- a/libs/coin-modules/coin-stellar/src/network/horizon.ts +++ b/libs/coin-modules/coin-stellar/src/network/horizon.ts @@ -29,7 +29,7 @@ import { getAccountSpendableBalance, getReservedBalance, rawOperationsToOperations, -} from "../logic"; +} from "../bridge/logic"; import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies"; const LIMIT = getEnv("API_STELLAR_HORIZON_FETCH_LIMIT"); diff --git a/libs/coin-modules/coin-stellar/src/hw-getAddress.ts b/libs/coin-modules/coin-stellar/src/signer/getAddress.ts similarity index 81% rename from libs/coin-modules/coin-stellar/src/hw-getAddress.ts rename to libs/coin-modules/coin-stellar/src/signer/getAddress.ts index 3d497a97b33b..48047f90e037 100644 --- a/libs/coin-modules/coin-stellar/src/hw-getAddress.ts +++ b/libs/coin-modules/coin-stellar/src/signer/getAddress.ts @@ -2,9 +2,9 @@ import { GetAddressFn } from "@ledgerhq/coin-framework/bridge/getAddressWrapper" import { SignerContext } from "@ledgerhq/coin-framework/signer"; import { GetAddressOptions } from "@ledgerhq/coin-framework/derivation"; import { StrKey } from "@stellar/stellar-sdk"; -import { StellarSigner } from "./types/signer"; +import { StellarSigner } from "../types/signer"; -function resolver(signerContext: SignerContext): GetAddressFn { +function getAddress(signerContext: SignerContext): GetAddressFn { return async (deviceId: string, { path, verify }: GetAddressOptions) => { const rawPublicKey = await signerContext(deviceId, async signer => { const { rawPublicKey } = await signer.getPublicKey(path, verify); @@ -21,4 +21,4 @@ function resolver(signerContext: SignerContext): GetAddressFn { }; } -export default resolver; +export default getAddress; diff --git a/libs/coin-modules/coin-stellar/src/signer/index.ts b/libs/coin-modules/coin-stellar/src/signer/index.ts new file mode 100644 index 000000000000..1f3f13571b7e --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/signer/index.ts @@ -0,0 +1,7 @@ +/** + * This directory is the home for all types and logic based on Ledgers signer. + */ + +import getAddress from "./getAddress"; + +export default getAddress; diff --git a/libs/coin-modules/coin-stellar/src/speculos-deviceActions.ts b/libs/coin-modules/coin-stellar/src/test/bot-deviceActions.ts similarity index 98% rename from libs/coin-modules/coin-stellar/src/speculos-deviceActions.ts rename to libs/coin-modules/coin-stellar/src/test/bot-deviceActions.ts index 68a4a6f8df4a..c628df2d0b88 100644 --- a/libs/coin-modules/coin-stellar/src/speculos-deviceActions.ts +++ b/libs/coin-modules/coin-stellar/src/test/bot-deviceActions.ts @@ -7,7 +7,7 @@ import { SpeculosButton, } from "@ledgerhq/coin-framework/bot/specs"; import { Account } from "@ledgerhq/types-live"; -import type { Transaction, TransactionStatus } from "./types"; +import type { Transaction, TransactionStatus } from "../types"; function expectedAmount({ account, diff --git a/libs/coin-modules/coin-stellar/src/specs.ts b/libs/coin-modules/coin-stellar/src/test/bot-specs.ts similarity index 98% rename from libs/coin-modules/coin-stellar/src/specs.ts rename to libs/coin-modules/coin-stellar/src/test/bot-specs.ts index f33f5a1f1111..5adb12abc504 100644 --- a/libs/coin-modules/coin-stellar/src/specs.ts +++ b/libs/coin-modules/coin-stellar/src/test/bot-specs.ts @@ -9,8 +9,8 @@ import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies"; import { AppSpec } from "@ledgerhq/coin-framework/bot/types"; import { botTest, pickSiblings } from "@ledgerhq/coin-framework/bot/specs"; import { listTokensForCryptoCurrency } from "@ledgerhq/cryptoassets/tokens"; -import { acceptTransaction } from "./speculos-deviceActions"; -import type { Transaction } from "./types"; +import { acceptTransaction } from "./bot-deviceActions"; +import type { Transaction } from "../types"; const currency = getCryptoCurrencyById("stellar"); const minAmountCutoff = parseCurrencyUnit(currency.units[0], "0.1"); diff --git a/libs/coin-modules/coin-stellar/src/bridge/bridge.integration.test.ts b/libs/coin-modules/coin-stellar/src/test/bridgeDatasetTest.ts similarity index 98% rename from libs/coin-modules/coin-stellar/src/bridge/bridge.integration.test.ts rename to libs/coin-modules/coin-stellar/src/test/bridgeDatasetTest.ts index 768334cccdef..f36e5c0f70bf 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/bridge.integration.test.ts +++ b/libs/coin-modules/coin-stellar/src/test/bridgeDatasetTest.ts @@ -6,8 +6,8 @@ import { NotEnoughBalanceBecauseDestinationNotCreated, } from "@ledgerhq/errors"; import type { Transaction } from "../types/index"; -import transactionTransformer from "../transaction"; -import { StellarWrongMemoFormat } from "../errors"; +import transactionTransformer from "../bridge/transaction"; +import { StellarWrongMemoFormat } from "../types"; export const dataset: DatasetTest = { implementations: ["js"], @@ -365,9 +365,3 @@ export const dataset: DatasetTest = { }, }, }; - -describe("Stellar bridge", () => { - test.todo( - "This is an empty test to make jest command pass. Remove it once there is a real test.", - ); -}); diff --git a/libs/coin-modules/coin-stellar/src/cli.ts b/libs/coin-modules/coin-stellar/src/test/cli.ts similarity index 95% rename from libs/coin-modules/coin-stellar/src/cli.ts rename to libs/coin-modules/coin-stellar/src/test/cli.ts index 974703b53d19..fa1ea4d1d283 100644 --- a/libs/coin-modules/coin-stellar/src/cli.ts +++ b/libs/coin-modules/coin-stellar/src/test/cli.ts @@ -1,8 +1,8 @@ import invariant from "invariant"; import type { AccountLike, Account, AccountLikeArray } from "@ledgerhq/types-live"; import { getAccountCurrency } from "@ledgerhq/coin-framework/account/helpers"; -import type { Transaction } from "./types"; -import { getAssetIdFromTokenId } from "./tokens"; +import type { Transaction } from "../types"; +import { getAssetIdFromTokenId } from "../bridge/tokens"; const options = [ { diff --git a/libs/coin-modules/coin-stellar/src/types/bridge.ts b/libs/coin-modules/coin-stellar/src/types/bridge.ts new file mode 100644 index 000000000000..ed0a5f21c03b --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/types/bridge.ts @@ -0,0 +1,110 @@ +import { Horizon } from "@stellar/stellar-sdk"; +import type { BigNumber } from "bignumber.js"; +import type { + Account, + Operation, + OperationType, + TransactionCommon, + TransactionCommonRaw, + TransactionStatusCommon, + TransactionStatusCommonRaw, +} from "@ledgerhq/types-live"; + +export type NetworkInfo = { + family: "stellar"; + fees: BigNumber; + baseFee: BigNumber; + baseReserve: BigNumber; + networkCongestionLevel?: NetworkCongestionLevel | undefined; +}; + +export type NetworkInfoRaw = { + family: "stellar"; + fees: string; + baseFee: string; + baseReserve: string; + networkCongestionLevel?: NetworkCongestionLevel | undefined; +}; + +export enum NetworkCongestionLevel { + LOW = "LOW", + MEDIUM = "MEDIUM", + HIGH = "HIGH", +} + +export const StellarMemoType = ["NO_MEMO", "MEMO_TEXT", "MEMO_ID", "MEMO_HASH", "MEMO_RETURN"]; + +export type StellarTransactionMode = "send" | "changeTrust"; + +export type Transaction = TransactionCommon & { + family: "stellar"; + networkInfo?: NetworkInfo | null | undefined; + fees?: BigNumber | null; + baseReserve?: BigNumber | null; + memoType?: string | null; + memoValue?: string | null; + mode: StellarTransactionMode; + assetCode?: string; + assetIssuer?: string; +}; + +export type TransactionRaw = TransactionCommonRaw & { + family: "stellar"; + networkInfo?: NetworkInfoRaw | null | undefined; + fees?: string | null; + baseReserve?: string | null; + memoType?: string | null; + memoValue?: string | null; + mode: StellarTransactionMode; + assetCode?: string; + assetIssuer?: string; +}; + +export type BalanceAsset = { + balance: string; + limit: string; + buying_liabilities: string; + selling_liabilities: string; + last_modified_ledger: number; + is_authorized: boolean; + is_authorized_to_maintain_liabilities: boolean; + asset_type: string; + asset_code: string; + asset_issuer: string; + liquidity_pool_id?: string; +}; + +export type RawOperation = Horizon.ServerApi.OperationRecord & { + asset_code?: string; + asset_issuer?: string; + from?: string; + to?: string; + to_muxed?: string; + funder?: string; + trustor?: string; + account?: string; + transaction_successful: boolean; +}; + +export type Signer = { + weight: number; + key: string; + type: string; +}; + +export type TransactionStatus = TransactionStatusCommon; + +export type TransactionStatusRaw = TransactionStatusCommonRaw; + +export type StellarOperation = Operation; + +export type StellarOperationExtra = { + pagingToken?: string; + assetCode?: string; + assetIssuer?: string; + assetAmount?: string | undefined; + ledgerOpType: OperationType; + memo?: string; +}; + +export type StellarAccount = Account; diff --git a/libs/coin-modules/coin-stellar/src/errors.ts b/libs/coin-modules/coin-stellar/src/types/errors.ts similarity index 100% rename from libs/coin-modules/coin-stellar/src/errors.ts rename to libs/coin-modules/coin-stellar/src/types/errors.ts diff --git a/libs/coin-modules/coin-stellar/src/types/index.ts b/libs/coin-modules/coin-stellar/src/types/index.ts index 3680ecb549f3..b2baff0e52ac 100644 --- a/libs/coin-modules/coin-stellar/src/types/index.ts +++ b/libs/coin-modules/coin-stellar/src/types/index.ts @@ -1,112 +1,3 @@ -import { Horizon } from "@stellar/stellar-sdk"; -import type { BigNumber } from "bignumber.js"; -import type { - Account, - Operation, - OperationType, - TransactionCommon, - TransactionCommonRaw, - TransactionStatusCommon, - TransactionStatusCommonRaw, -} from "@ledgerhq/types-live"; - -export type NetworkInfo = { - family: "stellar"; - fees: BigNumber; - baseFee: BigNumber; - baseReserve: BigNumber; - networkCongestionLevel?: NetworkCongestionLevel | undefined; -}; - -export type NetworkInfoRaw = { - family: "stellar"; - fees: string; - baseFee: string; - baseReserve: string; - networkCongestionLevel?: NetworkCongestionLevel | undefined; -}; - -export enum NetworkCongestionLevel { - LOW = "LOW", - MEDIUM = "MEDIUM", - HIGH = "HIGH", -} - -export const StellarMemoType = ["NO_MEMO", "MEMO_TEXT", "MEMO_ID", "MEMO_HASH", "MEMO_RETURN"]; - -export type StellarTransactionMode = "send" | "changeTrust"; - -export type Transaction = TransactionCommon & { - family: "stellar"; - networkInfo?: NetworkInfo | null | undefined; - fees?: BigNumber | null; - baseReserve?: BigNumber | null; - memoType?: string | null; - memoValue?: string | null; - mode: StellarTransactionMode; - assetCode?: string; - assetIssuer?: string; -}; - -export type TransactionRaw = TransactionCommonRaw & { - family: "stellar"; - networkInfo?: NetworkInfoRaw | null | undefined; - fees?: string | null; - baseReserve?: string | null; - memoType?: string | null; - memoValue?: string | null; - mode: StellarTransactionMode; - assetCode?: string; - assetIssuer?: string; -}; - -export type BalanceAsset = { - balance: string; - limit: string; - buying_liabilities: string; - selling_liabilities: string; - last_modified_ledger: number; - is_authorized: boolean; - is_authorized_to_maintain_liabilities: boolean; - asset_type: string; - asset_code: string; - asset_issuer: string; - liquidity_pool_id?: string; -}; - -export type RawOperation = Horizon.ServerApi.OperationRecord & { - asset_code?: string; - asset_issuer?: string; - from?: string; - to?: string; - to_muxed?: string; - funder?: string; - trustor?: string; - account?: string; - transaction_successful: boolean; -}; - -export type Signer = { - weight: number; - key: string; - type: string; -}; - -export type TransactionStatus = TransactionStatusCommon; - -export type TransactionStatusRaw = TransactionStatusCommonRaw; - -export type StellarOperation = Operation; - -export type StellarOperationExtra = { - pagingToken?: string; - assetCode?: string; - assetIssuer?: string; - assetAmount?: string | undefined; - ledgerOpType: OperationType; - memo?: string; -}; - -export type StellarAccount = Account; - +export * from "./bridge"; +export * from "./errors"; export * from "./signer"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94ae71637513..b68b5877e167 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2531,12 +2531,15 @@ importers: '@types/jest': specifier: ^29.5.10 version: 29.5.12 + '@types/node': + specifier: ^16.11.7 + version: 16.18.101 jest: specifier: ^29.7.0 - version: 29.7.0 + version: 29.7.0(@types/node@16.18.101) ts-jest: specifier: ^29.1.1 - version: 29.1.4(jest@29.7.0)(typescript@5.4.3) + version: 29.1.5(jest@29.7.0(@types/node@16.18.101))(typescript@5.4.3) libs/coin-modules/coin-tezos: dependencies: @@ -14259,6 +14262,9 @@ packages: '@types/node@13.13.52': resolution: {integrity: sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==} + '@types/node@16.18.101': + resolution: {integrity: sha512-AAsx9Rgz2IzG8KJ6tXd6ndNkVcu+GYB6U/SnFAaokSPNx2N7dcIIfnighYUNumvj6YS2q39Dejz5tT0NCV7CWA==} + '@types/node@18.15.13': resolution: {integrity: sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==} @@ -25069,6 +25075,9 @@ packages: q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qrcode-terminal@0.11.0: resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} @@ -27833,6 +27842,30 @@ packages: esbuild: optional: true + ts-jest@29.1.5: + resolution: {integrity: sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + ts-loader@9.5.1: resolution: {integrity: sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==} engines: {node: '>=12.0.0'} @@ -42329,6 +42362,8 @@ snapshots: '@types/node@13.13.52': {} + '@types/node@16.18.101': {} + '@types/node@18.15.13': {} '@types/node@18.19.26': @@ -46092,6 +46127,22 @@ snapshots: - supports-color - ts-node + create-jest@29.7.0(@types/node@16.18.101): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@16.18.101) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - metro + - supports-color + - ts-node + create-jest@29.7.0(@types/node@18.19.26)(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)): dependencies: '@jest/types': 29.6.3 @@ -51644,6 +51695,26 @@ snapshots: - supports-color - ts-node + jest-cli@29.7.0(@types/node@16.18.101): + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@16.18.101) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@16.18.101) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - metro + - supports-color + - ts-node + jest-cli@29.7.0(@types/node@18.19.26)(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)) @@ -51926,6 +51997,37 @@ snapshots: - metro - supports-color + jest-config@29.7.0(@types/node@16.18.101): + dependencies: + '@babel/core': 7.24.3 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.24.3) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 16.18.101 + transitivePeerDependencies: + - babel-plugin-macros + - metro + - supports-color + jest-config@29.7.0(@types/node@18.19.26)(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)): dependencies: '@babel/core': 7.24.3 @@ -53178,6 +53280,19 @@ snapshots: - supports-color - ts-node + jest@29.7.0(@types/node@16.18.101): + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@16.18.101) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - metro + - supports-color + - ts-node + jest@29.7.0(@types/node@18.19.26)(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)) @@ -57286,7 +57401,7 @@ snapshots: postcss-flexbugs-fixes: 5.0.2(postcss@8.4.38) postcss-normalize: 10.0.1(browserslist@4.23.0)(postcss@8.4.38) postcss-preset-env: 7.8.3(postcss@8.4.38) - semver: 7.5.4 + semver: 7.6.2 webpack: 5.89.0 transitivePeerDependencies: - browserslist @@ -61986,6 +62101,19 @@ snapshots: typescript: 5.4.3 yargs-parser: 21.1.1 + ts-jest@29.1.5(jest@29.7.0(@types/node@16.18.101))(typescript@5.4.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@16.18.101) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.2 + typescript: 5.4.3 + yargs-parser: 21.1.1 + ts-loader@9.5.1(typescript@5.1.3)(webpack@5.89.0): dependencies: chalk: 4.1.2 From afb8bbdd05b23ad2eb35d2264019abb717605bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Thu, 4 Jul 2024 19:28:03 +0200 Subject: [PATCH 02/16] feat: add getBalance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- libs/coin-modules/coin-stellar/package.json | 47 +++++----- .../coin-stellar/src/api/index.integ.test.ts | 87 +++++++++++++++++++ .../coin-stellar/src/api/index.ts | 26 ++++++ .../coin-stellar/src/bridge/index.ts | 6 +- libs/coin-modules/coin-stellar/src/config.ts | 9 +- .../coin-stellar/src/logic/getBalance.ts | 6 ++ .../coin-stellar/src/logic/index.ts | 6 ++ .../coin-stellar/src/network/horizon.ts | 35 ++++---- .../src/families/stellar/config.ts | 7 ++ pnpm-lock.yaml | 3 - 10 files changed, 186 insertions(+), 46 deletions(-) create mode 100644 libs/coin-modules/coin-stellar/src/api/index.integ.test.ts create mode 100644 libs/coin-modules/coin-stellar/src/logic/getBalance.ts diff --git a/libs/coin-modules/coin-stellar/package.json b/libs/coin-modules/coin-stellar/package.json index 45cd3df4395d..26ab340230a4 100644 --- a/libs/coin-modules/coin-stellar/package.json +++ b/libs/coin-modules/coin-stellar/package.json @@ -31,30 +31,6 @@ "typecheck": "tsc --noEmit", "unimported": "unimported" }, - "dependencies": { - "@ledgerhq/coin-framework": "workspace:^", - "@ledgerhq/cryptoassets": "workspace:^", - "@ledgerhq/devices": "workspace:^", - "@ledgerhq/errors": "workspace:^", - "@ledgerhq/live-env": "workspace:^", - "@ledgerhq/live-network": "workspace:^", - "@ledgerhq/logs": "workspace:^", - "@ledgerhq/types-cryptoassets": "workspace:^", - "@ledgerhq/types-live": "workspace:^", - "@stellar/stellar-sdk": "^11.3.0", - "bignumber.js": "^9.1.2", - "expect": "^27.4.6", - "invariant": "^2.2.2", - "rxjs": "^7.8.1" - }, - "devDependencies": { - "@faker-js/faker": "^8.4.1", - "@types/invariant": "^2.2.2", - "@types/jest": "^29.5.10", - "@types/node": "^16.11.7", - "jest": "^29.7.0", - "ts-jest": "^29.1.1" - }, "publishConfig": { "access": "public" }, @@ -79,5 +55,28 @@ "default": "./lib-es/*.js" }, "./package.json": "./package.json" + }, + "dependencies": { + "@ledgerhq/coin-framework": "workspace:^", + "@ledgerhq/cryptoassets": "workspace:^", + "@ledgerhq/devices": "workspace:^", + "@ledgerhq/errors": "workspace:^", + "@ledgerhq/live-network": "workspace:^", + "@ledgerhq/logs": "workspace:^", + "@ledgerhq/types-cryptoassets": "workspace:^", + "@ledgerhq/types-live": "workspace:^", + "@stellar/stellar-sdk": "^11.3.0", + "bignumber.js": "^9.1.2", + "expect": "^27.4.6", + "invariant": "^2.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@faker-js/faker": "^8.4.1", + "@types/invariant": "^2.2.2", + "@types/jest": "^29.5.10", + "@types/node": "^16.11.7", + "jest": "^29.7.0", + "ts-jest": "^29.1.1" } } diff --git a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts new file mode 100644 index 000000000000..5b916c0e3dea --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts @@ -0,0 +1,87 @@ +import type { Api } from "@ledgerhq/coin-framework/api/index"; +import { createApi } from "."; + +describe("Stellar Api", () => { + let module: Api; + const address = "GATRE74FSR4TJRCLVH4PODH4XUOPJ2PAXFSH5P7FL7J4ORTOJUVQB3UZ"; + + beforeAll(() => { + module = createApi({ + explorer: { + url: "https://horizon-testnet.stellar.org/", + fetchLmit: 200, + }, + useStaticFees: true, + enableNetworkLogs: false, + }); + }); + + // describe("estimateFees", () => { + // it("returns a default value", async () => { + // // Given + // const address = "rDCyjRD2TcSSGUQpEcEhJGmDWfjPJpuGxu"; + // const amount = BigInt(100); + + // // When + // const result = await module.estimateFees(address, amount); + + // // Then + // expect(result).toEqual(BigInt(10)); + // }); + // }); + + // describe("listOperations", () => { + // it("returns a list regarding address parameter", async () => { + // // When + // const result = await module.listOperations(address, 0); + + // // Then + // expect(result.length).toBeGreaterThanOrEqual(1); + // result.forEach(operation => { + // expect(operation.address).toEqual(address); + // const isSenderOrReceipt = + // operation.senders.includes(address) || operation.recipients.includes(address); + // expect(isSenderOrReceipt).toBeTruthy(); + // }); + // }); + // }); + + // describe("lastBlock", () => { + // it("returns last block info", async () => { + // // When + // const result = await module.lastBlock(); + + // // Then + // expect(result.hash).toBeDefined(); + // expect(result.height).toBeDefined(); + // expect(result.time).toBeInstanceOf(Date); + // }); + // }); + + describe("getBalance", () => { + it("returns a list regarding address parameter", async () => { + // When + const result = await module.getBalance(address); + + // Then + expect(result).toBeGreaterThan(0); + }); + }); + + // describe("craftTransaction", () => { + // it("returns a raw transaction", async () => { + // // When + // const result = await module.craftTransaction(address, { + // recipient: "rKRtUG15iBsCQRgrkeUEg5oX4Ae2zWZ89z", + // amount: BigInt(10), + // fee: BigInt(1), + // }); + + // // Then + // expect(result.slice(0, 34)).toEqual("120000228000000024001BCDA6201B001F"); + // expect(result.slice(38)).toEqual( + // "61400000000000000A6840000000000000018114CF30F590D7A9067B2604D80D46090FBF342EBE988314CA26FB6B0EF6859436C2037BA0A9913208A59B98", + // ); + // }); + // }); +}); diff --git a/libs/coin-modules/coin-stellar/src/api/index.ts b/libs/coin-modules/coin-stellar/src/api/index.ts index e69de29bb2d1..119172f35d0f 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.ts @@ -0,0 +1,26 @@ +import type { Api } from "@ledgerhq/coin-framework/api/index"; +import coinConfig, { type StellarConfig } from "../config"; +import { + // broadcast, + // combine, + // craftTransaction, + // estimateFees, + getBalance, + // listOperations, + // rawEncode, +} from "../logic"; + +export function createApi(config: StellarConfig): Api { + coinConfig.setCoinConfig(() => ({ ...config, status: { type: "active" } })); + + return { + broadcast: () => Promise.reject(new Error("Method not supported")), + combine: () => { + throw new Error("Method not supported"); + }, + craftTransaction: () => Promise.reject(new Error("Method not supported")), + estimateFees: () => Promise.reject(new Error("Method not supported")), + getBalance, + listOperations: () => Promise.reject(new Error("Method not supported")), + }; +} diff --git a/libs/coin-modules/coin-stellar/src/bridge/index.ts b/libs/coin-modules/coin-stellar/src/bridge/index.ts index 9b6aeb2d0b2a..ea9714cfeb9b 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/index.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/index.ts @@ -13,7 +13,7 @@ import { createTransaction } from "./createTransaction"; import { buildSignOperation } from "./signOperation"; import { broadcast } from "./broadcast"; import getAddressWrapper from "@ledgerhq/coin-framework/bridge/getAddressWrapper"; -import resolver from "../signer"; +import signerGetAddress from "../signer"; import { SignerContext } from "@ledgerhq/coin-framework/signer"; import { getAccountShape } from "./synchronization"; import { CoinConfig } from "@ledgerhq/coin-framework/config"; @@ -22,7 +22,7 @@ import stellarCoinConfig, { type StellarCoinConfig } from "../config"; const PRELOAD_MAX_AGE = 30 * 60 * 1000; // 30 minutes function buildCurrencyBridge(signerContext: SignerContext): CurrencyBridge { - const getAddress = resolver(signerContext); + const getAddress = signerGetAddress(signerContext); const scanAccounts = makeScanAccounts({ getAccountShape, @@ -44,7 +44,7 @@ function buildCurrencyBridge(signerContext: SignerContext): Curre } function buildAccountBridge(signerContext: SignerContext) { - const getAddress = resolver(signerContext); + const getAddress = signerGetAddress(signerContext); const receive = makeAccountBridgeReceive(getAddressWrapper(getAddress)); const signOperation = buildSignOperation(signerContext); const sync = makeSync({ getAccountShape }); diff --git a/libs/coin-modules/coin-stellar/src/config.ts b/libs/coin-modules/coin-stellar/src/config.ts index 57a17cd93368..f55aba1e81f4 100644 --- a/libs/coin-modules/coin-stellar/src/config.ts +++ b/libs/coin-modules/coin-stellar/src/config.ts @@ -1,6 +1,13 @@ import buildCoinConfig, { type CurrencyConfig } from "@ledgerhq/coin-framework/config"; -export type StellarConfig = Record; +export type StellarConfig = { + explorer: { + url: string; + fetchLmit: number; + }; + useStaticFees: boolean; + enableNetworkLogs: boolean; +}; export type StellarCoinConfig = CurrencyConfig & StellarConfig; diff --git a/libs/coin-modules/coin-stellar/src/logic/getBalance.ts b/libs/coin-modules/coin-stellar/src/logic/getBalance.ts new file mode 100644 index 000000000000..b6f8cfc91fc4 --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/logic/getBalance.ts @@ -0,0 +1,6 @@ +import { fetchAccount } from "../network"; + +export async function getBalance(addr: string): Promise { + const { balance } = await fetchAccount(addr); + return BigInt(balance.toString()); +} diff --git a/libs/coin-modules/coin-stellar/src/logic/index.ts b/libs/coin-modules/coin-stellar/src/logic/index.ts index e69de29bb2d1..ea0de80ffa8d 100644 --- a/libs/coin-modules/coin-stellar/src/logic/index.ts +++ b/libs/coin-modules/coin-stellar/src/logic/index.ts @@ -0,0 +1,6 @@ +// export { broadcast } from "./broadcast"; +// export { combine } from "./combine"; +// export { craftTransaction } from "./craftTransaction"; +// export { estimateFees } from "./estimateFees"; +export { getBalance } from "./getBalance"; +// export { listOperations } from "./listOperations"; diff --git a/libs/coin-modules/coin-stellar/src/network/horizon.ts b/libs/coin-modules/coin-stellar/src/network/horizon.ts index 25b99aa052e4..511a07977f83 100644 --- a/libs/coin-modules/coin-stellar/src/network/horizon.ts +++ b/libs/coin-modules/coin-stellar/src/network/horizon.ts @@ -16,7 +16,6 @@ import { Networks, } from "@stellar/stellar-sdk"; import { log } from "@ledgerhq/logs"; -import { getEnv } from "@ledgerhq/live-env"; import { type BalanceAsset, type NetworkInfo, @@ -31,13 +30,19 @@ import { rawOperationsToOperations, } from "../bridge/logic"; import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies"; +import coinConfig from "../config"; -const LIMIT = getEnv("API_STELLAR_HORIZON_FETCH_LIMIT"); const FALLBACK_BASE_FEE = 100; const TRESHOLD_LOW = 0.5; const TRESHOLD_MEDIUM = 0.75; const currency = getCryptoCurrencyById("stellar"); -const server = new Horizon.Server(getEnv("API_STELLAR_HORIZON")); +let server: Horizon.Server | undefined; +const getServer = () => { + if (!server) { + server = new Horizon.Server(coinConfig.getCoinConfig().explorer.url); + } + return server; +}; // Constants export const BASE_RESERVE = 0.5; @@ -48,7 +53,7 @@ export const MIN_BALANCE = 1; // and the version (0.26.1) used by `@ledgerhq/live-network/network`, it is not possible to use the interceptors // provided by `@ledgerhq/live-network/network`. Horizon.AxiosClient.interceptors.request.use(config => { - if (!getEnv("ENABLE_NETWORK_LOGS")) { + if (!coinConfig.getCoinConfig().enableNetworkLogs) { return config; } @@ -58,7 +63,7 @@ Horizon.AxiosClient.interceptors.request.use(config => { }); Horizon.AxiosClient.interceptors.response.use(response => { - if (getEnv("ENABLE_NETWORK_LOGS")) { + if (coinConfig.getCoinConfig().enableNetworkLogs) { const { url, method } = response.config; log("network-success", `${response.status} ${method} ${url}`, { data: response.data }); } @@ -70,7 +75,7 @@ Horizon.AxiosClient.interceptors.response.use(response => { if (next_href) { const next = new URL(next_href); - next.host = new URL(getEnv("API_STELLAR_HORIZON")).host; + next.host = new URL(coinConfig.getCoinConfig().explorer.url).host; response.data._links.next.href = next.toString(); } @@ -87,7 +92,7 @@ export async function fetchBaseFee(): Promise<{ networkCongestionLevel: NetworkCongestionLevel; }> { // For tests - if (getEnv("API_STELLAR_HORIZON_STATIC_FEE")) { + if (coinConfig.getCoinConfig().useStaticFees) { return { baseFee: 100, recommendedFee: 100, @@ -100,7 +105,7 @@ export async function fetchBaseFee(): Promise<{ let networkCongestionLevel = NetworkCongestionLevel.MEDIUM; try { - const feeStats = await server.feeStats(); + const feeStats = await getServer().feeStats(); const ledgerCapacityUsage = feeStats.ledger_capacity_usage; recommendedFee = new BigNumber(feeStats.fee_charged.mode).toNumber(); @@ -142,7 +147,7 @@ export async function fetchAccount(addr: string): Promise<{ let balance = "0"; try { - account = await server.accounts().accountId(addr).call(); + account = await getServer().accounts().accountId(addr).call(); balance = account.balances?.find(balance => { return balance.asset_type === "native"; @@ -195,10 +200,10 @@ export async function fetchOperations({ let operations: Operation[] = []; try { - let rawOperations = await server + let rawOperations = await getServer() .operations() .forAccount(addr) - .limit(LIMIT) + .limit(coinConfig.getCoinConfig().explorer.fetchLmit) .order(order) .cursor(cursor) .includeFailed(true) @@ -252,7 +257,7 @@ export async function fetchOperations({ export async function fetchAccountNetworkInfo(account: Account): Promise { try { - const extendedAccount = await server.accounts().accountId(account.freshAddress).call(); + const extendedAccount = await getServer().accounts().accountId(account.freshAddress).call(); const baseReserve = getReservedBalance(extendedAccount); const { recommendedFee, networkCongestionLevel, baseFee } = await fetchBaseFee(); @@ -280,7 +285,7 @@ export async function fetchSequence(account: Account): Promise { export async function fetchSigners(account: Account): Promise { try { - const extendedAccount = await server.accounts().accountId(account.freshAddress).call(); + const extendedAccount = await getServer().accounts().accountId(account.freshAddress).call(); return extendedAccount.signers; } catch (error) { return []; @@ -289,7 +294,7 @@ export async function fetchSigners(account: Account): Promise { export async function broadcastTransaction(signedTransaction: string): Promise { const transaction = new StellarSdkTransaction(signedTransaction, Networks.PUBLIC); - const res = await server.submitTransaction(transaction, { + const res = await getServer().submitTransaction(transaction, { skipMemoRequiredCheck: true, }); return res.hash; @@ -345,7 +350,7 @@ export async function loadAccount(addr: string): Promise { } try { - return await server.loadAccount(addr); + return await getServer().loadAccount(addr); } catch (e) { return null; } diff --git a/libs/ledger-live-common/src/families/stellar/config.ts b/libs/ledger-live-common/src/families/stellar/config.ts index 0186be0b9ae1..da9920c317a3 100644 --- a/libs/ledger-live-common/src/families/stellar/config.ts +++ b/libs/ledger-live-common/src/families/stellar/config.ts @@ -1,4 +1,5 @@ import { ConfigInfo } from "@ledgerhq/live-config/LiveConfig"; +import { getEnv } from "@ledgerhq/live-env"; export const stellarConfig: Record = { config_currency_stellar: { @@ -7,6 +8,12 @@ export const stellarConfig: Record = { status: { type: "active", }, + explorer: { + url: getEnv("API_STELLAR_HORIZON"), + fetchLimit: getEnv("API_STELLAR_HORIZON_FETCH_LIMIT"), + }, + useStaticFees: getEnv("API_STELLAR_HORIZON_STATIC_FEE"), + enableNetworkLogs: getEnv("ENABLE_NETWORK_LOGS"), }, }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b68b5877e167..29f88b9c8ff4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2491,9 +2491,6 @@ importers: '@ledgerhq/errors': specifier: workspace:^ version: link:../../ledgerjs/packages/errors - '@ledgerhq/live-env': - specifier: workspace:^ - version: link:../../env '@ledgerhq/live-network': specifier: workspace:^ version: link:../../live-network From 92f8250e86229ac0312814f8bf25ced7d723e899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Fri, 5 Jul 2024 14:29:57 +0200 Subject: [PATCH 03/16] fix: missing export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- libs/coin-modules/coin-stellar/package.json | 57 ++++++++++++++++++- .../coin-stellar/src/test/index.ts | 1 + .../stellar/bridge.integration.test.ts | 2 +- .../src/families/stellar/setup.ts | 2 +- 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 libs/coin-modules/coin-stellar/src/test/index.ts diff --git a/libs/coin-modules/coin-stellar/package.json b/libs/coin-modules/coin-stellar/package.json index 26ab340230a4..c7e2f958b778 100644 --- a/libs/coin-modules/coin-stellar/package.json +++ b/libs/coin-modules/coin-stellar/package.json @@ -42,18 +42,73 @@ "lib-es/*": [ "lib-es/*" ], + "api": [ + "lib/api/index" + ], + "deviceTransactionConfig": [ + "lib/bridge/deviceTransactionConfig" + ], + "logic": [ + "lib/logic/index" + ], + "specs": [ + "lib/test/bot-specs" + ], + "transaction": [ + "lib/bridge/transaction" + ], + "types": [ + "lib/types/index" + ], "*": [ - "lib/*" + "lib/*", + "lib/bridge/*", + "lib/logic/*", + "lib/signer/*", + "lib/test/*", + "lib/types/*" ] } }, "exports": { "./lib/*": "./lib/*.js", "./lib-es/*": "./lib-es/*.js", + "./api": { + "require": "./lib/api/index.js", + "default": "./lib-es/api/index.js" + }, + "./deviceTransactionConfig": { + "require": "./lib/bridge/deviceTransactionConfig.js", + "default": "./lib-es/bridge/deviceTransactionConfig.js" + }, + "./logic": { + "require": "./lib/logic/index.js", + "default": "./lib-es/logic/index.js" + }, + "./signer": { + "require": "./lib/signer/index.js", + "default": "./lib-es/signer/index.js" + }, + "./specs": { + "require": "./lib/test/bot-specs.js", + "default": "./lib-es/test/bot-specs.js" + }, + "./transaction": { + "require": "./lib/bridge/transaction.js", + "default": "./lib-es/bridge/transaction.js" + }, + "./types": { + "require": "./lib/types/index.js", + "default": "./lib-es/types/index.js" + }, "./*": { "require": "./lib/*.js", "default": "./lib-es/*.js" }, + ".": { + "require": "./lib/index.js", + "default": "./lib-es/index.js" + }, "./package.json": "./package.json" }, "dependencies": { diff --git a/libs/coin-modules/coin-stellar/src/test/index.ts b/libs/coin-modules/coin-stellar/src/test/index.ts new file mode 100644 index 000000000000..8e034a6ecf1f --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/test/index.ts @@ -0,0 +1 @@ +export * from "./bridgeDatasetTest"; diff --git a/libs/ledger-live-common/src/families/stellar/bridge.integration.test.ts b/libs/ledger-live-common/src/families/stellar/bridge.integration.test.ts index 4326a059092c..03eb5951de4a 100644 --- a/libs/ledger-live-common/src/families/stellar/bridge.integration.test.ts +++ b/libs/ledger-live-common/src/families/stellar/bridge.integration.test.ts @@ -1,5 +1,5 @@ import "../../__tests__/test-helpers/setup"; import { testBridge } from "../../__tests__/test-helpers/bridge"; -import { dataset } from "@ledgerhq/coin-stellar/bridge/bridge.integration.test"; +import { dataset } from "@ledgerhq/coin-stellar/test/index"; testBridge(dataset); diff --git a/libs/ledger-live-common/src/families/stellar/setup.ts b/libs/ledger-live-common/src/families/stellar/setup.ts index 1bbb728d355f..5a2307a0b6e1 100644 --- a/libs/ledger-live-common/src/families/stellar/setup.ts +++ b/libs/ledger-live-common/src/families/stellar/setup.ts @@ -7,7 +7,7 @@ import { StellarCoinConfig } from "@ledgerhq/coin-stellar/config"; import makeCliTools from "@ledgerhq/coin-stellar/cli"; import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; import { createBridges } from "@ledgerhq/coin-stellar/bridge/index"; -import stellarResolver from "@ledgerhq/coin-stellar/hw-getAddress"; +import stellarResolver from "@ledgerhq/coin-stellar/signer/index"; import { CreateSigner, createResolver, executeWithSigner } from "../../bridge/setup"; import { Resolver } from "../../hw/getAddress/types"; import { getCurrencyConfiguration } from "../../config"; From d5241b079fa053c7570081d8b16fc7c3836ebc18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Mon, 15 Jul 2024 14:28:33 +0200 Subject: [PATCH 04/16] feat(xlm): add listOperations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- .../coin-stellar/src/api/index.integ.test.ts | 32 +++++++------- .../coin-stellar/src/api/index.ts | 4 +- .../coin-stellar/src/logic/index.ts | 2 +- .../coin-stellar/src/logic/listOperations.ts | 42 +++++++++++++++++++ 4 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 libs/coin-modules/coin-stellar/src/logic/listOperations.ts diff --git a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts index 5b916c0e3dea..0ea3b6ef83cd 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts @@ -3,13 +3,13 @@ import { createApi } from "."; describe("Stellar Api", () => { let module: Api; - const address = "GATRE74FSR4TJRCLVH4PODH4XUOPJ2PAXFSH5P7FL7J4ORTOJUVQB3UZ"; + const address = "GD6QELUZPSKPRWVXOQ3F6GBF4OBRMCHO5PHREXH4ZRTPJAG7V5MD7JGX"; beforeAll(() => { module = createApi({ explorer: { url: "https://horizon-testnet.stellar.org/", - fetchLmit: 200, + fetchLmit: 100, }, useStaticFees: true, enableNetworkLogs: false, @@ -30,21 +30,21 @@ describe("Stellar Api", () => { // }); // }); - // describe("listOperations", () => { - // it("returns a list regarding address parameter", async () => { - // // When - // const result = await module.listOperations(address, 0); + describe("listOperations", () => { + it("returns a list regarding address parameter", async () => { + // When + const result = await module.listOperations(address, 0); - // // Then - // expect(result.length).toBeGreaterThanOrEqual(1); - // result.forEach(operation => { - // expect(operation.address).toEqual(address); - // const isSenderOrReceipt = - // operation.senders.includes(address) || operation.recipients.includes(address); - // expect(isSenderOrReceipt).toBeTruthy(); - // }); - // }); - // }); + // Then + expect(result.length).toBeGreaterThanOrEqual(1); + result.forEach(operation => { + expect(operation.address).toEqual(address); + const isSenderOrReceipt = + operation.senders.includes(address) || operation.recipients.includes(address); + expect(isSenderOrReceipt).toBeTruthy(); + }); + }); + }); // describe("lastBlock", () => { // it("returns last block info", async () => { diff --git a/libs/coin-modules/coin-stellar/src/api/index.ts b/libs/coin-modules/coin-stellar/src/api/index.ts index 119172f35d0f..897ee353b983 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.ts @@ -6,7 +6,7 @@ import { // craftTransaction, // estimateFees, getBalance, - // listOperations, + listOperations, // rawEncode, } from "../logic"; @@ -21,6 +21,6 @@ export function createApi(config: StellarConfig): Api { craftTransaction: () => Promise.reject(new Error("Method not supported")), estimateFees: () => Promise.reject(new Error("Method not supported")), getBalance, - listOperations: () => Promise.reject(new Error("Method not supported")), + listOperations, }; } diff --git a/libs/coin-modules/coin-stellar/src/logic/index.ts b/libs/coin-modules/coin-stellar/src/logic/index.ts index ea0de80ffa8d..bed096f67eb0 100644 --- a/libs/coin-modules/coin-stellar/src/logic/index.ts +++ b/libs/coin-modules/coin-stellar/src/logic/index.ts @@ -3,4 +3,4 @@ // export { craftTransaction } from "./craftTransaction"; // export { estimateFees } from "./estimateFees"; export { getBalance } from "./getBalance"; -// export { listOperations } from "./listOperations"; +export { listOperations } from "./listOperations"; diff --git a/libs/coin-modules/coin-stellar/src/logic/listOperations.ts b/libs/coin-modules/coin-stellar/src/logic/listOperations.ts new file mode 100644 index 000000000000..1799f453f142 --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/logic/listOperations.ts @@ -0,0 +1,42 @@ +import type { Operation as LiveOperation } from "@ledgerhq/types-live"; +import { fetchOperations } from "../network"; + +export type Operation = { + hash: string; + address: string; + type: string; + value: bigint; + fee: bigint; + blockHeight: number; + senders: string[]; + recipients: string[]; + date: Date; + transactionSequenceNumber: number; +}; + +export async function listOperations(address: string, _blockHeight: number): Promise { + // Fake accountId + const accountId = ""; + const operations = await fetchOperations({ + accountId, + addr: address, + order: "asc", + cursor: "0", + }); + return operations.map(convertToCoreOperation(address)); +} + +const convertToCoreOperation = (address: string) => (operation: LiveOperation) => { + return { + hash: operation.hash, + address, + type: operation.type, + value: BigInt(operation.value.toString()), + fee: BigInt(operation.fee.toString()), + blockHeight: operation.blockHeight!, + senders: operation.senders, + recipients: operation.recipients, + date: operation.date, + transactionSequenceNumber: operation.transactionSequenceNumber ?? 0, + }; +}; From 7df169395fcdaa29efcd59bde531abb0d29def3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Tue, 16 Jul 2024 15:57:23 +0200 Subject: [PATCH 05/16] feat: add craftTransaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- libs/coin-framework/src/api/types.ts | 17 +- .../coin-stellar/src/bridge/broadcast.ts | 2 +- .../src/bridge/buildTransaction.integ.test.ts | 177 ++++++++++++++++++ .../src/bridge/buildTransaction.ts | 99 ++-------- .../src/bridge/getTransactionStatus.ts | 5 +- .../coin-stellar/src/bridge/logic.ts | 84 +-------- .../src/bridge/synchronization.test.ts | 0 .../coin-stellar/src/logic/broadcast.ts | 5 + .../coin-stellar/src/logic/combine.ts | 3 + .../src/logic/craftTransaction.ts | 103 ++++++++++ .../coin-stellar/src/logic/index.ts | 4 +- .../coin-stellar/src/logic/sdkWrapper.ts | 60 ++++++ .../coin-stellar/src/network/horizon.ts | 145 ++++++++------ .../coin-stellar/src/network/index.ts | 7 +- 14 files changed, 470 insertions(+), 241 deletions(-) create mode 100644 libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts create mode 100644 libs/coin-modules/coin-stellar/src/bridge/synchronization.test.ts create mode 100644 libs/coin-modules/coin-stellar/src/logic/broadcast.ts create mode 100644 libs/coin-modules/coin-stellar/src/logic/combine.ts create mode 100644 libs/coin-modules/coin-stellar/src/logic/craftTransaction.ts create mode 100644 libs/coin-modules/coin-stellar/src/logic/sdkWrapper.ts diff --git a/libs/coin-framework/src/api/types.ts b/libs/coin-framework/src/api/types.ts index fd9a5159095d..0ac7942b21ce 100644 --- a/libs/coin-framework/src/api/types.ts +++ b/libs/coin-framework/src/api/types.ts @@ -17,16 +17,21 @@ export type Operation = { transactionSequenceNumber: number; }; +export type Transaction = { + mode: M; + recipient: string; + amount: bigint; + fee: bigint; + supplement: S; +}; + export type Api = { broadcast: (tx: string) => Promise; combine: (tx: string, signature: string, pubkey?: string) => string; - craftTransaction: ( + craftTransaction: ( address: string, - transaction: { - recipient: string; - amount: bigint; - fee: bigint; - }, + transaction: Transaction, + pubkey?: string, ) => Promise; estimateFees: (addr: string, amount: bigint) => Promise; getBalance: (address: string) => Promise; diff --git a/libs/coin-modules/coin-stellar/src/bridge/broadcast.ts b/libs/coin-modules/coin-stellar/src/bridge/broadcast.ts index 31c1f57a4af1..fc546df13cd1 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/broadcast.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/broadcast.ts @@ -1,6 +1,6 @@ import { patchOperationWithHash } from "@ledgerhq/coin-framework/operation"; import type { AccountBridge, Operation, SignedOperation } from "@ledgerhq/types-live"; -import { broadcastTransaction as apiBroadcast } from "../network"; +import { broadcast as apiBroadcast } from "../logic"; import { Transaction } from "../types"; /** diff --git a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts new file mode 100644 index 000000000000..eff4e2cb5f87 --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts @@ -0,0 +1,177 @@ +import BigNumber from "bignumber.js"; +import { buildTransaction } from "./buildTransaction"; +import { createFixtureAccount, createFixtureTransaction } from "../types/bridge.fixture"; +import { NetworkInfo } from "../types"; +import coinConfig, { type StellarCoinConfig } from "../config"; + +describe("buildTransaction", () => { + beforeAll(() => { + coinConfig.setCoinConfig( + (): StellarCoinConfig => ({ + status: { type: "active" }, + explorer: { + url: "https://stellar.coin.ledger.com", //"https://horizon-testnet.stellar.org/", + fetchLmit: 100, + }, + useStaticFees: true, + enableNetworkLogs: false, + }), + ); + }); + + it("throws an error when no fees are setted in the transaction", async () => { + // Given + const account = createFixtureAccount(); + const transaction = createFixtureTransaction(); + + // When + await expect(buildTransaction(account, transaction)).rejects.toThrow("FeeNotLoaded"); + }); + + it("throws an error if transaction has no NetworkInfo", async () => { + // Given + const account = createFixtureAccount({ + freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", + }); + const transaction = createFixtureTransaction({ fees: BigNumber(1) }); + + // When + await expect(buildTransaction(account, transaction)).rejects.toThrow("stellar family"); + }); + + it.skip("crash if transaction amount is 0", async () => { + // Given + const account = createFixtureAccount({ + freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", + }); + const transaction = createFixtureTransaction({ + amount: BigNumber(0), + fees: BigNumber(1), + networkInfo: { family: "stellar" } as NetworkInfo, + }); + + // When + const builtTransaction = await buildTransaction(account, transaction); + + // Then + expect(builtTransaction).toBeUndefined(); + }); + + it("throws an error when recipient is an invalid address", async () => { + // Given + const account = createFixtureAccount({ + freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", + }); + const transaction = createFixtureTransaction({ + amount: BigNumber(10), + fees: BigNumber(1), + networkInfo: { family: "stellar" } as NetworkInfo, + recipient: "NEW", + }); + + // When + await expect(buildTransaction(account, transaction)).rejects.toThrow("destination is invalid"); + }); + + it("returns a built transaction in Stellar format", async () => { + // Given + const account = createFixtureAccount({ + freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", + }); + const transaction = createFixtureTransaction({ + amount: BigNumber(10), + fees: BigNumber(1), + networkInfo: { family: "stellar" } as NetworkInfo, + }); + + // When + const builtTransaction = await buildTransaction(account, transaction); + + // Then + expect(builtTransaction.fee).toEqual("1"); + expect(builtTransaction.source).toEqual( + "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", + ); + expect(builtTransaction.operations).toHaveLength(1); + const operation = builtTransaction.operations[0]; + expect(operation.type).toEqual("payment"); + expect((operation as any).asset.code).toEqual("XLM"); + expect((operation as any).asset.issuer).toBeUndefined(); + expect(builtTransaction.toXDR().slice(0, 68)).toEqual( + "AAAAAgAAAACnLoGyX7Arx0zl7s6F7d8pSe2arRNq4dfpGSPkW5/d3AAAAAECbx/3AAHJ", + ); + expect(builtTransaction.toXDR().slice(70)).toEqual( + "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADw9kGYtpM1vsCgDoHjZOVO/sjTKLsmA51f8vdM9oaecgAAAAAAAAAAAAAACgAAAAAAAAAA", + ); + }); + + it("returns a built transaction in Stellar format when asset used is USDC", async () => { + // Given + const account = createFixtureAccount({ + freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", + }); + const transaction = createFixtureTransaction({ + amount: BigNumber(10), + fees: BigNumber(1), + networkInfo: { family: "stellar" } as NetworkInfo, + recipient: "GDRQAROTM7WYEHZ42SXUVUOHO36MLQKLFIZ5Y2JBWVQRPCJ3SQBBA3LH", + assetCode: "USDC", + assetIssuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", + }); + + // When + const builtTransaction = await buildTransaction(account, transaction); + + // Then + expect(builtTransaction.fee).toEqual("1"); + expect(builtTransaction.source).toEqual( + "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", + ); + expect(builtTransaction.operations).toHaveLength(1); + const operation = builtTransaction.operations[0]; + expect(operation.type).toEqual("payment"); + expect((operation as any).asset.code).toEqual("USDC"); + expect((operation as any).asset.issuer).toEqual( + "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", + ); + expect(builtTransaction.toXDR().slice(0, 68)).toEqual( + "AAAAAgAAAACnLoGyX7Arx0zl7s6F7d8pSe2arRNq4dfpGSPkW5/d3AAAAAECbx/3AAHJ", + ); + }); + + it("returns a built transaction in Stellar format", async () => { + // Given + const account = createFixtureAccount({ + freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", + }); + const transaction = createFixtureTransaction({ + amount: BigNumber(10), + fees: BigNumber(1), + networkInfo: { family: "stellar" } as NetworkInfo, + memoType: "MEMO_TEXT", + memoValue: "Hello", + }); + + // When + const builtTransaction = await buildTransaction(account, transaction); + + // Then + expect(builtTransaction.fee).toEqual("1"); + expect(builtTransaction.source).toEqual( + "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", + ); + expect(builtTransaction.operations).toHaveLength(1); + expect(builtTransaction.memo.type).toEqual("text"); + expect(builtTransaction.memo.value).toEqual("Hello"); + const operation = builtTransaction.operations[0]; + expect(operation.type).toEqual("payment"); + expect((operation as any).asset.code).toEqual("XLM"); + expect((operation as any).asset.issuer).toBeUndefined(); + expect(builtTransaction.toXDR().slice(0, 68)).toEqual( + "AAAAAgAAAACnLoGyX7Arx0zl7s6F7d8pSe2arRNq4dfpGSPkW5/d3AAAAAECbx/3AAHJ", + ); + expect(builtTransaction.toXDR().slice(70)).toEqual( + "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAVIZWxsbwAAAAAAAAEAAAAAAAAAAQAAAADw9kGYtpM1vsCgDoHjZOVO/sjTKLsmA51f8vdM9oaecgAAAAAAAAAAAAAACgAAAAAAAAAA", + ); + }); +}); diff --git a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts index a9f3c8f1ddb0..731b9fb6156e 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts @@ -1,16 +1,8 @@ import invariant from "invariant"; -import { Memo, Operation as StellarSdkOperation, xdr } from "@stellar/stellar-sdk"; -import { AmountRequired, FeeNotLoaded, NetworkDown } from "@ledgerhq/errors"; +import { FeeNotLoaded } from "@ledgerhq/errors"; import type { Account } from "@ledgerhq/types-live"; -import { StellarAssetRequired, StellarMuxedAccountNotExist, type Transaction } from "../types"; -import { - buildPaymentOperation, - buildCreateAccountOperation, - buildTransactionBuilder, - buildChangeTrustOperation, - loadAccount, -} from "../network"; -import { getRecipientAccount, getAmountValue } from "./logic"; +import type { Transaction } from "../types"; +import { craftTransaction } from "../logic"; /** * @param {Account} account @@ -24,81 +16,22 @@ export async function buildTransaction(account: Account, transaction: Transactio throw new FeeNotLoaded(); } - const source = await loadAccount(account.freshAddress); - - if (!source) { - throw new NetworkDown(); - } - invariant(networkInfo && networkInfo.family === "stellar", "stellar family"); - const transactionBuilder = buildTransactionBuilder(source, fees); - let operation: xdr.Operation | null = null; - - if (mode === "changeTrust") { - if (!assetCode || !assetIssuer) { - throw new StellarAssetRequired(""); - } - - operation = buildChangeTrustOperation(assetCode, assetIssuer); - } else { - // Payment - const amount = getAmountValue(account, transaction, fees); - - if (!amount) { - throw new AmountRequired(); - } - - const recipientAccount = await getRecipientAccount({ - account, - recipient: transaction.recipient, - }); - - if (recipientAccount?.id) { - operation = buildPaymentOperation({ - destination: recipient, - amount, - assetCode, - assetIssuer, - }); - } else { - if (recipientAccount?.isMuxedAccount) { - throw new StellarMuxedAccountNotExist(""); - } - - operation = buildCreateAccountOperation(recipient, amount); - } - } - - transactionBuilder.addOperation(operation); - - let memo: Memo | null = null; - - if (memoType && memoValue) { - switch (memoType) { - case "MEMO_TEXT": - memo = Memo.text(memoValue); - break; - - case "MEMO_ID": - memo = Memo.id(memoValue); - break; - - case "MEMO_HASH": - memo = Memo.hash(memoValue); - break; - - case "MEMO_RETURN": - memo = Memo.return(memoValue); - break; - } - } - - if (memo) { - transactionBuilder.addMemo(memo); - } + const { transaction: built } = await craftTransaction( + { address: account.freshAddress }, + { + mode, + recipient, + amount: BigInt(transaction.amount.toString()), + fee: BigInt(fees.toString()), + assetCode, + assetIssuer, + memoType, + memoValue, + }, + ); - const built = transactionBuilder.setTimeout(0).build(); return built; } diff --git a/libs/coin-modules/coin-stellar/src/bridge/getTransactionStatus.ts b/libs/coin-modules/coin-stellar/src/bridge/getTransactionStatus.ts index 554c699cedd8..0fbbbba3b8a0 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/getTransactionStatus.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/getTransactionStatus.ts @@ -13,8 +13,8 @@ import { BigNumber } from "bignumber.js"; import type { AccountBridge } from "@ledgerhq/types-live"; import { findSubAccountById } from "@ledgerhq/coin-framework/account/index"; import { formatCurrencyUnit } from "@ledgerhq/coin-framework/currencies/index"; -import { isAddressValid, isAccountMultiSign, isMemoValid, getRecipientAccount } from "./logic"; -import { BASE_RESERVE, MIN_BALANCE } from "../network"; +import { isAddressValid, isAccountMultiSign, isMemoValid } from "./logic"; +import { BASE_RESERVE, MIN_BALANCE, getRecipientAccount } from "../network"; import { StellarWrongMemoFormat, StellarAssetRequired, @@ -99,7 +99,6 @@ export const getTransactionStatus: AccountBridge["getTransactionSta } const recipientAccount = await getRecipientAccount({ - account: account, recipient: transaction.recipient, }); diff --git a/libs/coin-modules/coin-stellar/src/bridge/logic.ts b/libs/coin-modules/coin-stellar/src/bridge/logic.ts index 351c875cbf98..77ddccc0aef6 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/logic.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/logic.ts @@ -1,25 +1,11 @@ import { BigNumber } from "bignumber.js"; -import type { CacheRes } from "@ledgerhq/live-network/cache"; -import { makeLRUCache } from "@ledgerhq/live-network/cache"; import type { Account, OperationType, TokenAccount } from "@ledgerhq/types-live"; -import { - Horizon, - StrKey, - MuxedAccount, - // @ts-expect-error stellar-sdk ts definition missing? - AccountRecord, -} from "@stellar/stellar-sdk"; +import { Horizon, StrKey } from "@stellar/stellar-sdk"; import { findSubAccountById } from "@ledgerhq/coin-framework/account/helpers"; import { encodeOperationId } from "@ledgerhq/coin-framework/operation"; import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies/parseCurrencyUnit"; -import { - BASE_RESERVE, - BASE_RESERVE_MIN_COUNT, - fetchBaseFee, - fetchSigners, - loadAccount, -} from "../network"; +import { BASE_RESERVE, BASE_RESERVE_MIN_COUNT, fetchBaseFee, fetchSigners } from "../network"; import type { BalanceAsset, RawOperation, @@ -62,20 +48,6 @@ export function getAmountValue( : transaction.amount; } -export function getBalanceId(balance: BalanceAsset): string | null { - switch (balance.asset_type) { - case "native": - return "native"; - case "liquidity_pool_shares": - return balance.liquidity_pool_id || null; - case "credit_alphanum4": - case "credit_alphanum12": - return `${balance.asset_code}:${balance.asset_issuer}`; - default: - return null; - } -} - export function getReservedBalance(account: Horizon.ServerApi.AccountRecord): BigNumber { const numOfSponsoringEntries = Number(account.num_sponsoring); const numOfSponsoredEntries = Number(account.num_sponsored); @@ -288,58 +260,6 @@ export function isAddressValid(address: string): boolean { } } -export const getRecipientAccount: CacheRes< - Array<{ - account: Account; - recipient: string; - }>, - { - id: string | null; - isMuxedAccount: boolean; - assetIds: string[]; - } | null -> = makeLRUCache( - async ({ recipient }) => await recipientAccount(recipient), - extract => extract.recipient, - { - max: 300, - ttl: 5 * 60, - }, // 5 minutes -); - -export async function recipientAccount(address?: string): Promise<{ - id: string | null; - isMuxedAccount: boolean; - assetIds: string[]; -} | null> { - if (!address) { - return null; - } - - let accountAddress = address; - - const isMuxedAccount = StrKey.isValidMed25519PublicKey(address); - - if (isMuxedAccount) { - const muxedAccount = MuxedAccount.fromAddress(address, "0"); - accountAddress = muxedAccount.baseAccount().accountId(); - } - - const account: AccountRecord = await loadAccount(accountAddress); - - if (!account) { - return null; - } - - return { - id: account.id, - isMuxedAccount, - assetIds: account.balances.reduce((allAssets: any[], balance: any) => { - return [...allAssets, getBalanceId(balance)]; - }, []), - }; -} - export function rawOperationsToOperations( operations: RawOperation[], addr: string, diff --git a/libs/coin-modules/coin-stellar/src/bridge/synchronization.test.ts b/libs/coin-modules/coin-stellar/src/bridge/synchronization.test.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/libs/coin-modules/coin-stellar/src/logic/broadcast.ts b/libs/coin-modules/coin-stellar/src/logic/broadcast.ts new file mode 100644 index 000000000000..8d41982c71bd --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/logic/broadcast.ts @@ -0,0 +1,5 @@ +import { broadcastTransaction } from "../network"; + +export async function broadcast(signature: string): Promise { + return broadcastTransaction(signature); +} diff --git a/libs/coin-modules/coin-stellar/src/logic/combine.ts b/libs/coin-modules/coin-stellar/src/logic/combine.ts new file mode 100644 index 000000000000..40bd5104d2f0 --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/logic/combine.ts @@ -0,0 +1,3 @@ + +// export function combine(transaction: string, signature: string, publicKey?: string): string { +// } diff --git a/libs/coin-modules/coin-stellar/src/logic/craftTransaction.ts b/libs/coin-modules/coin-stellar/src/logic/craftTransaction.ts new file mode 100644 index 000000000000..6fdfbc0996ea --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/logic/craftTransaction.ts @@ -0,0 +1,103 @@ +import { + Memo, + Operation as StellarSdkOperation, + Transaction as StellarSdkTransaction, + xdr, +} from "@stellar/stellar-sdk"; +import { NetworkDown } from "@ledgerhq/errors"; +import { getRecipientAccount, loadAccount } from "../network"; +import { StellarAssetRequired, StellarMuxedAccountNotExist } from "../types"; +import { + buildChangeTrustOperation, + buildCreateAccountOperation, + buildPaymentOperation, + buildTransactionBuilder, +} from "./sdkWrapper"; + +export async function craftTransaction( + account: { + address: string; + }, + transaction: { + mode: "send" | "changeTrust"; + recipient: string; + amount: bigint; + fee: bigint; + assetCode?: string | undefined; + assetIssuer?: string | undefined; + memoType?: string | null | undefined; + memoValue?: string | null | undefined; + }, +): Promise<{ transaction: StellarSdkTransaction; xdr: string }> { + const { amount, recipient, fee, memoType, memoValue, mode, assetCode, assetIssuer } = transaction; + console.log("amount", amount); + + const source = await loadAccount(account.address); + + if (!source) { + throw new NetworkDown(); + } + + const transactionBuilder = buildTransactionBuilder(source, fee); + let operation: xdr.Operation | null = null; + + if (mode === "changeTrust") { + if (!assetCode || !assetIssuer) { + throw new StellarAssetRequired(""); + } + + operation = buildChangeTrustOperation(assetCode, assetIssuer); + } else { + // Payment + const recipientAccount = await getRecipientAccount({ + recipient, + }); + + if (recipientAccount?.id) { + operation = buildPaymentOperation({ + destination: recipient, + amount, + assetCode, + assetIssuer, + }); + } else { + if (recipientAccount?.isMuxedAccount) { + throw new StellarMuxedAccountNotExist(""); + } + + operation = buildCreateAccountOperation(recipient, amount); + } + } + + transactionBuilder.addOperation(operation); + + const memo = buildMemo(memoType, memoValue); + if (memo) { + transactionBuilder.addMemo(memo); + } + + const craftedTransaction = transactionBuilder.setTimeout(0).build(); + return { + transaction: craftedTransaction, + xdr: craftedTransaction.toXDR(), + }; +} + +function buildMemo( + memoType?: string | null | undefined, + memoValue?: string | null | undefined, +): Memo | null { + if (memoType && memoValue) { + switch (memoType) { + case "MEMO_TEXT": + return Memo.text(memoValue); + case "MEMO_ID": + return Memo.id(memoValue); + case "MEMO_HASH": + return Memo.hash(memoValue); + case "MEMO_RETURN": + return Memo.return(memoValue); + } + } + return null; +} diff --git a/libs/coin-modules/coin-stellar/src/logic/index.ts b/libs/coin-modules/coin-stellar/src/logic/index.ts index bed096f67eb0..9b5f5b1c1692 100644 --- a/libs/coin-modules/coin-stellar/src/logic/index.ts +++ b/libs/coin-modules/coin-stellar/src/logic/index.ts @@ -1,6 +1,6 @@ -// export { broadcast } from "./broadcast"; +export { broadcast } from "./broadcast"; // export { combine } from "./combine"; -// export { craftTransaction } from "./craftTransaction"; +export { craftTransaction } from "./craftTransaction"; // export { estimateFees } from "./estimateFees"; export { getBalance } from "./getBalance"; export { listOperations } from "./listOperations"; diff --git a/libs/coin-modules/coin-stellar/src/logic/sdkWrapper.ts b/libs/coin-modules/coin-stellar/src/logic/sdkWrapper.ts new file mode 100644 index 000000000000..992e89a2b811 --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/logic/sdkWrapper.ts @@ -0,0 +1,60 @@ +import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; +import { + Account as StellarSdkAccount, + Operation as StellarSdkOperation, + TransactionBuilder, + Networks, + Asset, +} from "@stellar/stellar-sdk"; + +const currency = getCryptoCurrencyById("stellar"); + +export function buildTransactionBuilder(source: StellarSdkAccount, fee: bigint) { + const formattedFee = fee.toString(); + return new TransactionBuilder(source, { + fee: formattedFee, + networkPassphrase: Networks.PUBLIC, + }); +} + +export function buildChangeTrustOperation(assetCode: string, assetIssuer: string) { + return StellarSdkOperation.changeTrust({ + asset: new Asset(assetCode, assetIssuer), + }); +} + +export function buildCreateAccountOperation(destination: string, amount: bigint) { + const formattedAmount = getFormattedAmount(amount); + return StellarSdkOperation.createAccount({ + destination: destination, + startingBalance: formattedAmount, + }); +} + +export function buildPaymentOperation({ + destination, + amount, + assetCode, + assetIssuer, +}: { + destination: string; + amount: bigint; + assetCode: string | undefined; + assetIssuer: string | undefined; +}) { + const formattedAmount = getFormattedAmount(amount); + // Non-native assets should always have asset code and asset issuer. If an + // asset doesn't have both, we assume it is native asset. + const asset = assetCode && assetIssuer ? new Asset(assetCode, assetIssuer) : Asset.native(); + return StellarSdkOperation.payment({ + destination: destination, + amount: formattedAmount, + asset, + }); +} + +function getFormattedAmount(amount: bigint) { + const div = 10 ** currency.units[0].magnitude; + // BigInt division is always an integer, never a float. We need to convert first to a Number. + return (Number(amount) / div).toString(); +} diff --git a/libs/coin-modules/coin-stellar/src/network/horizon.ts b/libs/coin-modules/coin-stellar/src/network/horizon.ts index 511a07977f83..d166c68eb67f 100644 --- a/libs/coin-modules/coin-stellar/src/network/horizon.ts +++ b/libs/coin-modules/coin-stellar/src/network/horizon.ts @@ -1,21 +1,35 @@ +import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies"; +import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; import { LedgerAPI4xx, LedgerAPI5xx, NetworkDown } from "@ledgerhq/errors"; +import type { CacheRes } from "@ledgerhq/live-network/cache"; +import { makeLRUCache } from "@ledgerhq/live-network/cache"; +import { log } from "@ledgerhq/logs"; import type { Account, Operation } from "@ledgerhq/types-live"; -import { BigNumber } from "bignumber.js"; import { // @ts-expect-error stellar-sdk ts definition missing? AccountRecord, + Asset, + BASE_FEE, + Horizon, NetworkError, + Networks, NotFoundError, - Horizon, - BASE_FEE, - Asset, - Operation as StellarSdkOperation, Account as StellarSdkAccount, + Operation as StellarSdkOperation, Transaction as StellarSdkTransaction, TransactionBuilder, - Networks, + StrKey, + MuxedAccount, + // @ts-expect-error stellar-sdk ts definition missing? + AccountRecord, } from "@stellar/stellar-sdk"; -import { log } from "@ledgerhq/logs"; +import { BigNumber } from "bignumber.js"; +import { + getAccountSpendableBalance, + getReservedBalance, + rawOperationsToOperations, +} from "../bridge/logic"; +import coinConfig from "../config"; import { type BalanceAsset, type NetworkInfo, @@ -23,14 +37,6 @@ import { type Signer, NetworkCongestionLevel, } from "../types"; -import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; -import { - getAccountSpendableBalance, - getReservedBalance, - rawOperationsToOperations, -} from "../bridge/logic"; -import { parseCurrencyUnit } from "@ledgerhq/coin-framework/currencies"; -import coinConfig from "../config"; const FALLBACK_BASE_FEE = 100; const TRESHOLD_LOW = 0.5; @@ -300,50 +306,6 @@ export async function broadcastTransaction(signedTransaction: string): Promise { if (!addr || !addr.length) { return null; @@ -355,3 +317,68 @@ export async function loadAccount(addr: string): Promise { return null; } } + +export const getRecipientAccount: CacheRes< + Array<{ + recipient: string; + }>, + { + id: string | null; + isMuxedAccount: boolean; + assetIds: string[]; + } | null +> = makeLRUCache( + async ({ recipient }) => await recipientAccount(recipient), + extract => extract.recipient, + { + max: 300, + ttl: 5 * 60, + }, // 5 minutes +); + +async function recipientAccount(address?: string): Promise<{ + id: string | null; + isMuxedAccount: boolean; + assetIds: string[]; +} | null> { + if (!address) { + return null; + } + + let accountAddress = address; + + const isMuxedAccount = StrKey.isValidMed25519PublicKey(address); + + if (isMuxedAccount) { + const muxedAccount = MuxedAccount.fromAddress(address, "0"); + accountAddress = muxedAccount.baseAccount().accountId(); + } + + const account: AccountRecord = await loadAccount(accountAddress); + + if (!account) { + return null; + } + + return { + id: account.id, + isMuxedAccount, + assetIds: account.balances.reduce((allAssets: any[], balance: any) => { + return [...allAssets, getBalanceId(balance)]; + }, []), + }; +} + +function getBalanceId(balance: BalanceAsset): string | null { + switch (balance.asset_type) { + case "native": + return "native"; + case "liquidity_pool_shares": + return balance.liquidity_pool_id || null; + case "credit_alphanum4": + case "credit_alphanum12": + return `${balance.asset_code}:${balance.asset_issuer}`; + default: + return null; + } +} diff --git a/libs/coin-modules/coin-stellar/src/network/index.ts b/libs/coin-modules/coin-stellar/src/network/index.ts index 9b4530f7eff7..3c8e8d7c1440 100644 --- a/libs/coin-modules/coin-stellar/src/network/index.ts +++ b/libs/coin-modules/coin-stellar/src/network/index.ts @@ -1,15 +1,12 @@ export { + broadcastTransaction, fetchAccount, fetchOperations, fetchBaseFee, fetchSequence, fetchSigners, fetchAccountNetworkInfo, - broadcastTransaction, - buildPaymentOperation, - buildCreateAccountOperation, - buildChangeTrustOperation, - buildTransactionBuilder, + getRecipientAccount, loadAccount, BASE_RESERVE, BASE_RESERVE_MIN_COUNT, From afa38fabe67e97f56cc666884f877ad23613b71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Tue, 16 Jul 2024 17:01:56 +0200 Subject: [PATCH 06/16] chore: remove generics as it will forbid the use of generic Api creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- libs/coin-framework/src/api/types.ts | 12 ++--- .../coin-polkadot/src/api/index.integ.test.ts | 1 + .../coin-polkadot/src/api/index.ts | 3 ++ .../coin-stellar/src/api/index.ts | 46 ++++++++++++++++++- .../src/logic/craftTransaction.ts | 3 +- .../coin-tezos/src/api/index.integ.test.ts | 1 + .../coin-xrp/src/api/index.integ.test.ts | 1 + libs/coin-modules/coin-xrp/src/api/index.ts | 1 + 8 files changed, 56 insertions(+), 12 deletions(-) diff --git a/libs/coin-framework/src/api/types.ts b/libs/coin-framework/src/api/types.ts index 0ac7942b21ce..58be941507b6 100644 --- a/libs/coin-framework/src/api/types.ts +++ b/libs/coin-framework/src/api/types.ts @@ -17,22 +17,18 @@ export type Operation = { transactionSequenceNumber: number; }; -export type Transaction = { - mode: M; +export type Transaction = { + mode: string; recipient: string; amount: bigint; fee: bigint; - supplement: S; + supplement?: unknown; }; export type Api = { broadcast: (tx: string) => Promise; combine: (tx: string, signature: string, pubkey?: string) => string; - craftTransaction: ( - address: string, - transaction: Transaction, - pubkey?: string, - ) => Promise; + craftTransaction: (address: string, transaction: Transaction, pubkey?: string) => Promise; estimateFees: (addr: string, amount: bigint) => Promise; getBalance: (address: string) => Promise; lastBlock: () => Promise; diff --git a/libs/coin-modules/coin-polkadot/src/api/index.integ.test.ts b/libs/coin-modules/coin-polkadot/src/api/index.integ.test.ts index 0cb00ca9967e..3f138de77076 100644 --- a/libs/coin-modules/coin-polkadot/src/api/index.integ.test.ts +++ b/libs/coin-modules/coin-polkadot/src/api/index.integ.test.ts @@ -81,6 +81,7 @@ describe("Polkadot Api", () => { it("returns a raw transaction", async () => { // When const result = await module.craftTransaction(address, { + mode: "send", recipient: "16YreVmGhM8mNMqnsvK7rn7b1e4SKYsTfFUn4UfCZ65BgDjh", amount: BigInt(10), fee: BigInt(1), diff --git a/libs/coin-modules/coin-polkadot/src/api/index.ts b/libs/coin-modules/coin-polkadot/src/api/index.ts index 22fed372c431..5a9ea2a87073 100644 --- a/libs/coin-modules/coin-polkadot/src/api/index.ts +++ b/libs/coin-modules/coin-polkadot/src/api/index.ts @@ -30,8 +30,11 @@ export function createApi(config: PolkadotConfig): Api { async function craft( address: string, transaction: { + mode: string; recipient: string; amount: bigint; + fee: bigint; + supplement?: unknown; }, ): Promise { const extrinsicArg = defaultExtrinsicArg(transaction.amount, transaction.recipient); diff --git a/libs/coin-modules/coin-stellar/src/api/index.ts b/libs/coin-modules/coin-stellar/src/api/index.ts index 897ee353b983..207974688afa 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.ts @@ -3,10 +3,11 @@ import coinConfig, { type StellarConfig } from "../config"; import { // broadcast, // combine, - // craftTransaction, + craftTransaction, // estimateFees, getBalance, listOperations, + // lastBlock, // rawEncode, } from "../logic"; @@ -18,9 +19,50 @@ export function createApi(config: StellarConfig): Api { combine: () => { throw new Error("Method not supported"); }, - craftTransaction: () => Promise.reject(new Error("Method not supported")), + craftTransaction: craft, estimateFees: () => Promise.reject(new Error("Method not supported")), getBalance, + lastBlock: () => Promise.reject(new Error("Method not supported")), listOperations, }; } + +type Supplement = { + assetCode?: string | undefined; + assetIssuer?: string | undefined; + memoType?: string | null | undefined; + memoValue?: string | null | undefined; +}; +function isSupplement(supplement: unknown): supplement is Supplement { + return typeof supplement === "object"; +} +async function craft( + address: string, + transaction: { + mode: string; + recipient: string; + amount: bigint; + fee: bigint; + supplement?: unknown; + }, +): Promise { + const supplement = isSupplement(transaction.supplement) + ? { + assetCode: transaction.supplement?.assetCode, + assetIssuer: transaction.supplement?.assetIssuer, + memoType: transaction.supplement?.memoType, + memoValue: transaction.supplement?.memoValue, + } + : {}; + const tx = await craftTransaction( + { address }, + { + ...transaction, + assetCode: supplement?.assetCode, + assetIssuer: supplement?.assetIssuer, + memoType: supplement?.memoType, + memoValue: supplement?.memoValue, + }, + ); + return tx.xdr; +} diff --git a/libs/coin-modules/coin-stellar/src/logic/craftTransaction.ts b/libs/coin-modules/coin-stellar/src/logic/craftTransaction.ts index 6fdfbc0996ea..c37785967f72 100644 --- a/libs/coin-modules/coin-stellar/src/logic/craftTransaction.ts +++ b/libs/coin-modules/coin-stellar/src/logic/craftTransaction.ts @@ -19,7 +19,7 @@ export async function craftTransaction( address: string; }, transaction: { - mode: "send" | "changeTrust"; + mode: string; recipient: string; amount: bigint; fee: bigint; @@ -30,7 +30,6 @@ export async function craftTransaction( }, ): Promise<{ transaction: StellarSdkTransaction; xdr: string }> { const { amount, recipient, fee, memoType, memoValue, mode, assetCode, assetIssuer } = transaction; - console.log("amount", amount); const source = await loadAccount(account.address); diff --git a/libs/coin-modules/coin-tezos/src/api/index.integ.test.ts b/libs/coin-modules/coin-tezos/src/api/index.integ.test.ts index 8e408281f604..e42bd57df02e 100644 --- a/libs/coin-modules/coin-tezos/src/api/index.integ.test.ts +++ b/libs/coin-modules/coin-tezos/src/api/index.integ.test.ts @@ -85,6 +85,7 @@ describe("Tezos Api", () => { it("returns a raw transaction", async () => { // When const result = await module.craftTransaction(address, { + mode: "send", recipient: "tz1aWXP237BLwNHJcCD4b3DutCevhqq2T1Z9", amount: BigInt(10), fee: BigInt(1), diff --git a/libs/coin-modules/coin-xrp/src/api/index.integ.test.ts b/libs/coin-modules/coin-xrp/src/api/index.integ.test.ts index c67ce5d4b726..b9ad99455218 100644 --- a/libs/coin-modules/coin-xrp/src/api/index.integ.test.ts +++ b/libs/coin-modules/coin-xrp/src/api/index.integ.test.ts @@ -69,6 +69,7 @@ describe("Xrp Api", () => { it("returns a raw transaction", async () => { // When const result = await module.craftTransaction(address, { + mode: "send", recipient: "rKRtUG15iBsCQRgrkeUEg5oX4Ae2zWZ89z", amount: BigInt(10), fee: BigInt(1), diff --git a/libs/coin-modules/coin-xrp/src/api/index.ts b/libs/coin-modules/coin-xrp/src/api/index.ts index 02771b24a1ea..6b70daf5e46c 100644 --- a/libs/coin-modules/coin-xrp/src/api/index.ts +++ b/libs/coin-modules/coin-xrp/src/api/index.ts @@ -28,6 +28,7 @@ export function createApi(config: XrpConfig): Api { async function craft( address: string, transaction: { + mode: string; recipient: string; amount: bigint; fee: bigint; From fd90b7b673ab9f0f6199f5e572092852eea977a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Wed, 17 Jul 2024 08:37:19 +0200 Subject: [PATCH 07/16] feat(xlm): add lastBlock function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- .../coin-stellar/src/api/index.ts | 8 +++---- .../coin-stellar/src/logic/index.ts | 1 + .../coin-stellar/src/logic/lastBlock.ts | 6 +++++ .../coin-stellar/src/network/horizon.ts | 23 +++++++++++-------- .../coin-stellar/src/network/index.ts | 1 + 5 files changed, 25 insertions(+), 14 deletions(-) create mode 100644 libs/coin-modules/coin-stellar/src/logic/lastBlock.ts diff --git a/libs/coin-modules/coin-stellar/src/api/index.ts b/libs/coin-modules/coin-stellar/src/api/index.ts index 207974688afa..3976b4a661d1 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.ts @@ -1,13 +1,13 @@ import type { Api } from "@ledgerhq/coin-framework/api/index"; import coinConfig, { type StellarConfig } from "../config"; import { - // broadcast, + broadcast, // combine, craftTransaction, // estimateFees, getBalance, listOperations, - // lastBlock, + lastBlock, // rawEncode, } from "../logic"; @@ -15,14 +15,14 @@ export function createApi(config: StellarConfig): Api { coinConfig.setCoinConfig(() => ({ ...config, status: { type: "active" } })); return { - broadcast: () => Promise.reject(new Error("Method not supported")), + broadcast, combine: () => { throw new Error("Method not supported"); }, craftTransaction: craft, estimateFees: () => Promise.reject(new Error("Method not supported")), getBalance, - lastBlock: () => Promise.reject(new Error("Method not supported")), + lastBlock, listOperations, }; } diff --git a/libs/coin-modules/coin-stellar/src/logic/index.ts b/libs/coin-modules/coin-stellar/src/logic/index.ts index 9b5f5b1c1692..d3954ac7a826 100644 --- a/libs/coin-modules/coin-stellar/src/logic/index.ts +++ b/libs/coin-modules/coin-stellar/src/logic/index.ts @@ -3,4 +3,5 @@ export { broadcast } from "./broadcast"; export { craftTransaction } from "./craftTransaction"; // export { estimateFees } from "./estimateFees"; export { getBalance } from "./getBalance"; +export { lastBlock } from "./lastBlock"; export { listOperations } from "./listOperations"; diff --git a/libs/coin-modules/coin-stellar/src/logic/lastBlock.ts b/libs/coin-modules/coin-stellar/src/logic/lastBlock.ts new file mode 100644 index 000000000000..9247f870dad2 --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/logic/lastBlock.ts @@ -0,0 +1,6 @@ +import type { BlockInfo } from "@ledgerhq/coin-framework/api/index"; +import { getLastBlock } from "../network"; + +export async function lastBlock(): Promise { + return await getLastBlock(); +} diff --git a/libs/coin-modules/coin-stellar/src/network/horizon.ts b/libs/coin-modules/coin-stellar/src/network/horizon.ts index d166c68eb67f..8dfd8ea93d7f 100644 --- a/libs/coin-modules/coin-stellar/src/network/horizon.ts +++ b/libs/coin-modules/coin-stellar/src/network/horizon.ts @@ -8,20 +8,14 @@ import type { Account, Operation } from "@ledgerhq/types-live"; import { // @ts-expect-error stellar-sdk ts definition missing? AccountRecord, - Asset, BASE_FEE, Horizon, NetworkError, Networks, NotFoundError, - Account as StellarSdkAccount, - Operation as StellarSdkOperation, Transaction as StellarSdkTransaction, - TransactionBuilder, StrKey, MuxedAccount, - // @ts-expect-error stellar-sdk ts definition missing? - AccountRecord, } from "@stellar/stellar-sdk"; import { BigNumber } from "bignumber.js"; import { @@ -88,10 +82,6 @@ Horizon.AxiosClient.interceptors.response.use(response => { return response; }); -function getFormattedAmount(amount: BigNumber) { - return amount.div(new BigNumber(10).pow(currency.units[0].magnitude)).toString(10); -} - export async function fetchBaseFee(): Promise<{ baseFee: number; recommendedFee: number; @@ -318,6 +308,19 @@ export async function loadAccount(addr: string): Promise { } } +export async function getLastBlock(): Promise<{ + height: number; + hash: string; + time: Date; +}> { + const ledger = await getServer().ledgers().order("desc").limit(1).call(); + return { + height: ledger.records[0].sequence, + hash: ledger.records[0].hash, + time: new Date(ledger.records[0].closed_at), + }; +} + export const getRecipientAccount: CacheRes< Array<{ recipient: string; diff --git a/libs/coin-modules/coin-stellar/src/network/index.ts b/libs/coin-modules/coin-stellar/src/network/index.ts index 3c8e8d7c1440..3ca613ddc285 100644 --- a/libs/coin-modules/coin-stellar/src/network/index.ts +++ b/libs/coin-modules/coin-stellar/src/network/index.ts @@ -6,6 +6,7 @@ export { fetchSequence, fetchSigners, fetchAccountNetworkInfo, + getLastBlock, getRecipientAccount, loadAccount, BASE_RESERVE, From ddaf5b5945f43b6b63d2ae27254003f137a8d546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Wed, 17 Jul 2024 08:49:13 +0200 Subject: [PATCH 08/16] feat(xlm): add combine function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- .../coin-stellar/src/api/index.integ.test.ts | 20 +++++++++---------- .../coin-stellar/src/api/index.ts | 14 ++++++++----- .../coin-stellar/src/logic/combine.ts | 14 +++++++++++-- .../coin-stellar/src/logic/index.ts | 2 +- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts index 0ea3b6ef83cd..214944310d97 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts @@ -46,17 +46,17 @@ describe("Stellar Api", () => { }); }); - // describe("lastBlock", () => { - // it("returns last block info", async () => { - // // When - // const result = await module.lastBlock(); + describe("lastBlock", () => { + it("returns last block info", async () => { + // When + const result = await module.lastBlock(); - // // Then - // expect(result.hash).toBeDefined(); - // expect(result.height).toBeDefined(); - // expect(result.time).toBeInstanceOf(Date); - // }); - // }); + // Then + expect(result.hash).toBeDefined(); + expect(result.height).toBeDefined(); + expect(result.time).toBeInstanceOf(Date); + }); + }); describe("getBalance", () => { it("returns a list regarding address parameter", async () => { diff --git a/libs/coin-modules/coin-stellar/src/api/index.ts b/libs/coin-modules/coin-stellar/src/api/index.ts index 3976b4a661d1..d1b00f4aa8b7 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.ts @@ -2,13 +2,12 @@ import type { Api } from "@ledgerhq/coin-framework/api/index"; import coinConfig, { type StellarConfig } from "../config"; import { broadcast, - // combine, + combine, craftTransaction, // estimateFees, getBalance, listOperations, lastBlock, - // rawEncode, } from "../logic"; export function createApi(config: StellarConfig): Api { @@ -16,9 +15,7 @@ export function createApi(config: StellarConfig): Api { return { broadcast, - combine: () => { - throw new Error("Method not supported"); - }, + combine: compose, craftTransaction: craft, estimateFees: () => Promise.reject(new Error("Method not supported")), getBalance, @@ -66,3 +63,10 @@ async function craft( ); return tx.xdr; } + +function compose(tx: string, signature: string, pubkey?: string): string { + if (!pubkey) { + throw new Error("Missing pubkey"); + } + return combine(tx, signature, pubkey); +} diff --git a/libs/coin-modules/coin-stellar/src/logic/combine.ts b/libs/coin-modules/coin-stellar/src/logic/combine.ts index 40bd5104d2f0..abbc9ac001bc 100644 --- a/libs/coin-modules/coin-stellar/src/logic/combine.ts +++ b/libs/coin-modules/coin-stellar/src/logic/combine.ts @@ -1,3 +1,13 @@ +import { Transaction as StellarSdkTransaction } from "@stellar/stellar-sdk"; -// export function combine(transaction: string, signature: string, publicKey?: string): string { -// } +// https://developers.stellar.org/docs/learn/encyclopedia/network-configuration/network-passphrases +// Mainnet +const NETWORK_PASSPHRASE = "Public Global Stellar Network ; September 2015"; +// Testnet +// const NETWORK_PASSPHRASE = "Test SDF Network ; September 2015"; + +export function combine(transaction: string, signature: string, publicKey: string): string { + const unsignedTx = new StellarSdkTransaction(transaction, NETWORK_PASSPHRASE); + unsignedTx.addSignature(publicKey, signature); + return unsignedTx.toXDR(); +} diff --git a/libs/coin-modules/coin-stellar/src/logic/index.ts b/libs/coin-modules/coin-stellar/src/logic/index.ts index d3954ac7a826..5605cbf5e923 100644 --- a/libs/coin-modules/coin-stellar/src/logic/index.ts +++ b/libs/coin-modules/coin-stellar/src/logic/index.ts @@ -1,5 +1,5 @@ export { broadcast } from "./broadcast"; -// export { combine } from "./combine"; +export { combine } from "./combine"; export { craftTransaction } from "./craftTransaction"; // export { estimateFees } from "./estimateFees"; export { getBalance } from "./getBalance"; From 0ebbda0b83ef35bb5968e8533e9408b729ee1f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Wed, 17 Jul 2024 09:29:24 +0200 Subject: [PATCH 09/16] feat(xlm): add estimation fees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- .../coin-stellar/src/api/index.integ.test.ts | 54 ++++++++++--------- .../coin-stellar/src/api/index.ts | 4 +- .../coin-stellar/src/logic/combine.ts | 10 +--- .../coin-stellar/src/logic/estimateFees.ts | 6 +++ .../coin-stellar/src/logic/index.ts | 2 +- 5 files changed, 39 insertions(+), 37 deletions(-) create mode 100644 libs/coin-modules/coin-stellar/src/logic/estimateFees.ts diff --git a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts index 214944310d97..1640376c3457 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts @@ -16,19 +16,18 @@ describe("Stellar Api", () => { }); }); - // describe("estimateFees", () => { - // it("returns a default value", async () => { - // // Given - // const address = "rDCyjRD2TcSSGUQpEcEhJGmDWfjPJpuGxu"; - // const amount = BigInt(100); + describe("estimateFees", () => { + it("returns a default value", async () => { + // Given + const amount = BigInt(100_000); - // // When - // const result = await module.estimateFees(address, amount); + // When + const result = await module.estimateFees(address, amount); - // // Then - // expect(result).toEqual(BigInt(10)); - // }); - // }); + // Then + expect(result).toEqual(BigInt(100)); + }); + }); describe("listOperations", () => { it("returns a list regarding address parameter", async () => { @@ -68,20 +67,23 @@ describe("Stellar Api", () => { }); }); - // describe("craftTransaction", () => { - // it("returns a raw transaction", async () => { - // // When - // const result = await module.craftTransaction(address, { - // recipient: "rKRtUG15iBsCQRgrkeUEg5oX4Ae2zWZ89z", - // amount: BigInt(10), - // fee: BigInt(1), - // }); + describe("craftTransaction", () => { + it("returns a raw transaction", async () => { + // When + const result = await module.craftTransaction(address, { + mode: "send", + recipient: "GD6QELUZPSKPRWVXOQ3F6GBF4OBRMCHO5PHREXH4ZRTPJAG7V5MD7JGX", + amount: BigInt(1_000_000), + fee: BigInt(100), + }); - // // Then - // expect(result.slice(0, 34)).toEqual("120000228000000024001BCDA6201B001F"); - // expect(result.slice(38)).toEqual( - // "61400000000000000A6840000000000000018114CF30F590D7A9067B2604D80D46090FBF342EBE988314CA26FB6B0EF6859436C2037BA0A9913208A59B98", - // ); - // }); - // }); + // Then + expect(result.slice(0, 68)).toEqual( + "AAAAAgAAAAD9Ai6ZfJT42rd0Nl8YJeODFgju688SXPzMZvSA369YPwAAAGQAAHloAAAI", + ); + expect(result.slice(70)).toEqual( + "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAD9Ai6ZfJT42rd0Nl8YJeODFgju688SXPzMZvSA369YPwAAAAAAAAAAAA9CQAAAAAAAAAAA", + ); + }); + }); }); diff --git a/libs/coin-modules/coin-stellar/src/api/index.ts b/libs/coin-modules/coin-stellar/src/api/index.ts index d1b00f4aa8b7..df3d896a8458 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.ts @@ -4,7 +4,7 @@ import { broadcast, combine, craftTransaction, - // estimateFees, + estimateFees, getBalance, listOperations, lastBlock, @@ -17,7 +17,7 @@ export function createApi(config: StellarConfig): Api { broadcast, combine: compose, craftTransaction: craft, - estimateFees: () => Promise.reject(new Error("Method not supported")), + estimateFees, getBalance, lastBlock, listOperations, diff --git a/libs/coin-modules/coin-stellar/src/logic/combine.ts b/libs/coin-modules/coin-stellar/src/logic/combine.ts index abbc9ac001bc..f909d7d16fb1 100644 --- a/libs/coin-modules/coin-stellar/src/logic/combine.ts +++ b/libs/coin-modules/coin-stellar/src/logic/combine.ts @@ -1,13 +1,7 @@ -import { Transaction as StellarSdkTransaction } from "@stellar/stellar-sdk"; - -// https://developers.stellar.org/docs/learn/encyclopedia/network-configuration/network-passphrases -// Mainnet -const NETWORK_PASSPHRASE = "Public Global Stellar Network ; September 2015"; -// Testnet -// const NETWORK_PASSPHRASE = "Test SDF Network ; September 2015"; +import { Networks, Transaction as StellarSdkTransaction } from "@stellar/stellar-sdk"; export function combine(transaction: string, signature: string, publicKey: string): string { - const unsignedTx = new StellarSdkTransaction(transaction, NETWORK_PASSPHRASE); + const unsignedTx = new StellarSdkTransaction(transaction, Networks.PUBLIC); unsignedTx.addSignature(publicKey, signature); return unsignedTx.toXDR(); } diff --git a/libs/coin-modules/coin-stellar/src/logic/estimateFees.ts b/libs/coin-modules/coin-stellar/src/logic/estimateFees.ts new file mode 100644 index 000000000000..b92d9143ea4e --- /dev/null +++ b/libs/coin-modules/coin-stellar/src/logic/estimateFees.ts @@ -0,0 +1,6 @@ +import { fetchBaseFee } from "../network"; + +export async function estimateFees(): Promise { + const fees = await fetchBaseFee(); + return BigInt(fees.recommendedFee); +} diff --git a/libs/coin-modules/coin-stellar/src/logic/index.ts b/libs/coin-modules/coin-stellar/src/logic/index.ts index 5605cbf5e923..5bbaeb2211c4 100644 --- a/libs/coin-modules/coin-stellar/src/logic/index.ts +++ b/libs/coin-modules/coin-stellar/src/logic/index.ts @@ -1,7 +1,7 @@ export { broadcast } from "./broadcast"; export { combine } from "./combine"; export { craftTransaction } from "./craftTransaction"; -// export { estimateFees } from "./estimateFees"; +export { estimateFees } from "./estimateFees"; export { getBalance } from "./getBalance"; export { lastBlock } from "./lastBlock"; export { listOperations } from "./listOperations"; From 24017407a584bbfa4cd53d3f370a07e482e598d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Wed, 17 Jul 2024 10:34:16 +0200 Subject: [PATCH 10/16] fix: missing export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- .../coin-stellar/.unimportedrc.json | 28 ++++--------------- libs/coin-modules/coin-stellar/package.json | 4 +++ .../src/bridge/synchronization.test.ts | 3 ++ .../coin-stellar/src/logic/estimateFees.ts | 4 +++ .../coin-stellar/src/test/index.ts | 5 ++++ .../src/families/stellar/setup.ts | 20 +++++++------ 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/libs/coin-modules/coin-stellar/.unimportedrc.json b/libs/coin-modules/coin-stellar/.unimportedrc.json index f9162d00a3a6..dad5b54f16c8 100644 --- a/libs/coin-modules/coin-stellar/.unimportedrc.json +++ b/libs/coin-modules/coin-stellar/.unimportedrc.json @@ -1,11 +1,11 @@ { "entry": [ - "src/deviceTransactionConfig.ts", - "src/errors.ts", - "src/hw-getAddress.ts", - "src/serialization.ts", - "src/specs.ts", - "src/transaction.ts" + "src/api/index.ts", + "src/bridge/index.ts", + "src/bridge/deviceTransactionConfig.ts", + "src/signer/index.ts", + "src/test/index.ts", + "src/index.ts" ], "ignorePatterns": [ "**/node_modules/**", @@ -13,22 +13,6 @@ "**/*.mock.ts", "**/*.test.{js,jsx,ts,tsx}" ], - "ignoreUnresolved": [], - "ignoreUnimported": [ - "src/bridge/index.ts", - "src/broadcast.ts", - "src/buildOptimisticOperation.ts", - "src/buildTransaction.ts", - "src/cli.ts", - "src/config.ts", - "src/createTransaction.ts", - "src/estimateMaxSpendable.ts", - "src/getTransactionStatus.ts", - "src/prepareTransaction.ts", - "src/signOperation.ts", - "src/synchronization.ts", - "src/tokens.ts" - ], "ignoreUnused": [ "rxjs" ] diff --git a/libs/coin-modules/coin-stellar/package.json b/libs/coin-modules/coin-stellar/package.json index c7e2f958b778..74a36ea0569d 100644 --- a/libs/coin-modules/coin-stellar/package.json +++ b/libs/coin-modules/coin-stellar/package.json @@ -81,6 +81,10 @@ "require": "./lib/bridge/deviceTransactionConfig.js", "default": "./lib-es/bridge/deviceTransactionConfig.js" }, + "./errors": { + "require": "./lib/types/errors.js", + "default": "./lib-es/types/errors.js" + }, "./logic": { "require": "./lib/logic/index.js", "default": "./lib-es/logic/index.js" diff --git a/libs/coin-modules/coin-stellar/src/bridge/synchronization.test.ts b/libs/coin-modules/coin-stellar/src/bridge/synchronization.test.ts index e69de29bb2d1..b983c24de357 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/synchronization.test.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/synchronization.test.ts @@ -0,0 +1,3 @@ +describe("getAccountShape", () => { + it.todo("returns an AccountShapeInfo"); +}); diff --git a/libs/coin-modules/coin-stellar/src/logic/estimateFees.ts b/libs/coin-modules/coin-stellar/src/logic/estimateFees.ts index b92d9143ea4e..4cf386aa8e44 100644 --- a/libs/coin-modules/coin-stellar/src/logic/estimateFees.ts +++ b/libs/coin-modules/coin-stellar/src/logic/estimateFees.ts @@ -1,5 +1,9 @@ import { fetchBaseFee } from "../network"; +/** + * Estimate the fees for one transaction + * @see {@link https://developers.stellar.org/docs/learn/fundamentals/fees-resource-limits-metering#inclusion-fee} + */ export async function estimateFees(): Promise { const fees = await fetchBaseFee(); return BigInt(fees.recommendedFee); diff --git a/libs/coin-modules/coin-stellar/src/test/index.ts b/libs/coin-modules/coin-stellar/src/test/index.ts index 8e034a6ecf1f..b4fce0ef353b 100644 --- a/libs/coin-modules/coin-stellar/src/test/index.ts +++ b/libs/coin-modules/coin-stellar/src/test/index.ts @@ -1 +1,6 @@ +import makeCliTools from "./cli"; + export * from "./bridgeDatasetTest"; +export { makeCliTools }; +export * from "./bot-specs"; +export * from "./bot-deviceActions"; diff --git a/libs/ledger-live-common/src/families/stellar/setup.ts b/libs/ledger-live-common/src/families/stellar/setup.ts index 5a2307a0b6e1..c98ad966dc74 100644 --- a/libs/ledger-live-common/src/families/stellar/setup.ts +++ b/libs/ledger-live-common/src/families/stellar/setup.ts @@ -1,16 +1,20 @@ // Goal of this file is to inject all necessary device/signer dependency to coin-modules -import { Transaction, StellarAccount, TransactionStatus } from "@ledgerhq/coin-stellar/types/index"; -import Transport from "@ledgerhq/hw-transport"; -import Stellar from "@ledgerhq/hw-app-str"; -import type { Bridge } from "@ledgerhq/types-live"; -import { StellarCoinConfig } from "@ledgerhq/coin-stellar/config"; -import makeCliTools from "@ledgerhq/coin-stellar/cli"; -import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; import { createBridges } from "@ledgerhq/coin-stellar/bridge/index"; +import makeCliTools from "@ledgerhq/coin-stellar/test/cli"; +import { StellarCoinConfig } from "@ledgerhq/coin-stellar/config"; import stellarResolver from "@ledgerhq/coin-stellar/signer/index"; +import type { + StellarAccount, + Transaction, + TransactionStatus, +} from "@ledgerhq/coin-stellar/types/index"; +import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; +import Stellar from "@ledgerhq/hw-app-str"; +import Transport from "@ledgerhq/hw-transport"; +import type { Bridge } from "@ledgerhq/types-live"; import { CreateSigner, createResolver, executeWithSigner } from "../../bridge/setup"; -import { Resolver } from "../../hw/getAddress/types"; import { getCurrencyConfiguration } from "../../config"; +import { Resolver } from "../../hw/getAddress/types"; const createSigner: CreateSigner = (transport: Transport) => { return new Stellar(transport); From fc5403a8ec0d5e98d9f81174b202d7d2ad517aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Wed, 17 Jul 2024 11:34:52 +0200 Subject: [PATCH 11/16] fix(xlm): config typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- libs/coin-modules/coin-stellar/src/api/index.integ.test.ts | 3 --- .../coin-stellar/src/bridge/buildTransaction.integ.test.ts | 4 +--- libs/coin-modules/coin-stellar/src/config.ts | 6 +++--- libs/coin-modules/coin-stellar/src/network/horizon.ts | 3 ++- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts index 1640376c3457..9d851826145b 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts @@ -9,10 +9,7 @@ describe("Stellar Api", () => { module = createApi({ explorer: { url: "https://horizon-testnet.stellar.org/", - fetchLmit: 100, }, - useStaticFees: true, - enableNetworkLogs: false, }); }); diff --git a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts index eff4e2cb5f87..62f683c7b28c 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts @@ -11,10 +11,8 @@ describe("buildTransaction", () => { status: { type: "active" }, explorer: { url: "https://stellar.coin.ledger.com", //"https://horizon-testnet.stellar.org/", - fetchLmit: 100, + fetchLimit: 100, }, - useStaticFees: true, - enableNetworkLogs: false, }), ); }); diff --git a/libs/coin-modules/coin-stellar/src/config.ts b/libs/coin-modules/coin-stellar/src/config.ts index f55aba1e81f4..a0acd4cbd7d0 100644 --- a/libs/coin-modules/coin-stellar/src/config.ts +++ b/libs/coin-modules/coin-stellar/src/config.ts @@ -3,10 +3,10 @@ import buildCoinConfig, { type CurrencyConfig } from "@ledgerhq/coin-framework/c export type StellarConfig = { explorer: { url: string; - fetchLmit: number; + fetchLimit?: number; }; - useStaticFees: boolean; - enableNetworkLogs: boolean; + useStaticFees?: boolean; + enableNetworkLogs?: boolean; }; export type StellarCoinConfig = CurrencyConfig & StellarConfig; diff --git a/libs/coin-modules/coin-stellar/src/network/horizon.ts b/libs/coin-modules/coin-stellar/src/network/horizon.ts index 8dfd8ea93d7f..c80cfd0e7417 100644 --- a/libs/coin-modules/coin-stellar/src/network/horizon.ts +++ b/libs/coin-modules/coin-stellar/src/network/horizon.ts @@ -35,6 +35,7 @@ import { const FALLBACK_BASE_FEE = 100; const TRESHOLD_LOW = 0.5; const TRESHOLD_MEDIUM = 0.75; +const FETCH_LIMIT = 100; const currency = getCryptoCurrencyById("stellar"); let server: Horizon.Server | undefined; const getServer = () => { @@ -199,7 +200,7 @@ export async function fetchOperations({ let rawOperations = await getServer() .operations() .forAccount(addr) - .limit(coinConfig.getCoinConfig().explorer.fetchLmit) + .limit(coinConfig.getCoinConfig().explorer.fetchLimit ?? FETCH_LIMIT) .order(order) .cursor(cursor) .includeFailed(true) From 4dfbfc85f1107df6301d2899ab0056eb9ab7d945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Wed, 17 Jul 2024 15:24:43 +0200 Subject: [PATCH 12/16] fix: activate useAllAmount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- .../src/bridge/buildTransaction.integ.test.ts | 66 +++++++++++++------ .../src/bridge/buildTransaction.ts | 17 +++-- 2 files changed, 58 insertions(+), 25 deletions(-) diff --git a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts index 62f683c7b28c..8bdffbfe3b4d 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts @@ -5,6 +5,8 @@ import { NetworkInfo } from "../types"; import coinConfig, { type StellarCoinConfig } from "../config"; describe("buildTransaction", () => { + const sender = "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ"; + beforeAll(() => { coinConfig.setCoinConfig( (): StellarCoinConfig => ({ @@ -28,9 +30,7 @@ describe("buildTransaction", () => { it("throws an error if transaction has no NetworkInfo", async () => { // Given - const account = createFixtureAccount({ - freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", - }); + const account = createFixtureAccount({ freshAddress: sender }); const transaction = createFixtureTransaction({ fees: BigNumber(1) }); // When @@ -39,9 +39,7 @@ describe("buildTransaction", () => { it.skip("crash if transaction amount is 0", async () => { // Given - const account = createFixtureAccount({ - freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", - }); + const account = createFixtureAccount({ freshAddress: sender }); const transaction = createFixtureTransaction({ amount: BigNumber(0), fees: BigNumber(1), @@ -57,9 +55,7 @@ describe("buildTransaction", () => { it("throws an error when recipient is an invalid address", async () => { // Given - const account = createFixtureAccount({ - freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", - }); + const account = createFixtureAccount({ freshAddress: sender }); const transaction = createFixtureTransaction({ amount: BigNumber(10), fees: BigNumber(1), @@ -73,9 +69,7 @@ describe("buildTransaction", () => { it("returns a built transaction in Stellar format", async () => { // Given - const account = createFixtureAccount({ - freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", - }); + const account = createFixtureAccount({ freshAddress: sender }); const transaction = createFixtureTransaction({ amount: BigNumber(10), fees: BigNumber(1), @@ -87,12 +81,12 @@ describe("buildTransaction", () => { // Then expect(builtTransaction.fee).toEqual("1"); - expect(builtTransaction.source).toEqual( - "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", - ); + expect(builtTransaction.source).toEqual(sender); expect(builtTransaction.operations).toHaveLength(1); const operation = builtTransaction.operations[0]; expect(operation.type).toEqual("payment"); + expect((operation as any).destination).toEqual(transaction.recipient); + expect((operation as any).amount).toEqual("0.0000010"); expect((operation as any).asset.code).toEqual("XLM"); expect((operation as any).asset.issuer).toBeUndefined(); expect(builtTransaction.toXDR().slice(0, 68)).toEqual( @@ -103,11 +97,45 @@ describe("buildTransaction", () => { ); }); - it("returns a built transaction in Stellar format when asset used is USDC", async () => { + it("returns a built transaction in Stellar format when useAllAmount", async () => { // Given + const sender = "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ"; const account = createFixtureAccount({ - freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", + freshAddress: sender, + balance: BigNumber(600239412), + spendableBalance: BigNumber(500239412), + }); + const transaction = createFixtureTransaction({ + amount: BigNumber(10), + fees: BigNumber(1), + networkInfo: { family: "stellar" } as NetworkInfo, + useAllAmount: true, }); + + // When + const builtTransaction = await buildTransaction(account, transaction); + + // Then + expect(builtTransaction.fee).toEqual("1"); + expect(builtTransaction.source).toEqual(sender); + expect(builtTransaction.operations).toHaveLength(1); + const operation = builtTransaction.operations[0]; + expect(operation.type).toEqual("payment"); + expect((operation as any).destination).toEqual(transaction.recipient); + expect((operation as any).amount).toEqual("50.0239411"); + expect((operation as any).asset.code).toEqual("XLM"); + expect((operation as any).asset.issuer).toBeUndefined(); + expect(builtTransaction.toXDR().slice(0, 68)).toEqual( + "AAAAAgAAAACnLoGyX7Arx0zl7s6F7d8pSe2arRNq4dfpGSPkW5/d3AAAAAECbx/3AAHJ", + ); + expect(builtTransaction.toXDR().slice(70)).toEqual( + "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADw9kGYtpM1vsCgDoHjZOVO/sjTKLsmA51f8vdM9oaecgAAAAAAAAAAHdEMMwAAAAAAAAAA", + ); + }); + + it("returns a built transaction in Stellar format when asset used is USDC", async () => { + // Given + const account = createFixtureAccount({ freshAddress: sender }); const transaction = createFixtureTransaction({ amount: BigNumber(10), fees: BigNumber(1), @@ -139,9 +167,7 @@ describe("buildTransaction", () => { it("returns a built transaction in Stellar format", async () => { // Given - const account = createFixtureAccount({ - freshAddress: "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", - }); + const account = createFixtureAccount({ freshAddress: sender }); const transaction = createFixtureTransaction({ amount: BigNumber(10), fees: BigNumber(1), diff --git a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts index 731b9fb6156e..b825430e97c5 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts @@ -1,8 +1,9 @@ -import invariant from "invariant"; -import { FeeNotLoaded } from "@ledgerhq/errors"; +import { AmountRequired, FeeNotLoaded } from "@ledgerhq/errors"; import type { Account } from "@ledgerhq/types-live"; -import type { Transaction } from "../types"; +import invariant from "invariant"; import { craftTransaction } from "../logic"; +import type { Transaction } from "../types"; +import { getAmountValue } from "./logic"; /** * @param {Account} account @@ -12,18 +13,24 @@ export async function buildTransaction(account: Account, transaction: Transactio const { recipient, networkInfo, fees, memoType, memoValue, mode, assetCode, assetIssuer } = transaction; + invariant(networkInfo && networkInfo.family === "stellar", "stellar family"); + if (!fees) { throw new FeeNotLoaded(); } - invariant(networkInfo && networkInfo.family === "stellar", "stellar family"); + const amount = getAmountValue(account, transaction, fees); + + if (!amount) { + throw new AmountRequired(); + } const { transaction: built } = await craftTransaction( { address: account.freshAddress }, { mode, recipient, - amount: BigInt(transaction.amount.toString()), + amount: BigInt(amount.toString()), fee: BigInt(fees.toString()), assetCode, assetIssuer, From a429de078fdfb2ae89a3dd07d6ea7c6f777eafd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Thu, 18 Jul 2024 11:40:02 +0200 Subject: [PATCH 13/16] chore: changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- .changeset/thick-boats-fix.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/thick-boats-fix.md diff --git a/.changeset/thick-boats-fix.md b/.changeset/thick-boats-fix.md new file mode 100644 index 000000000000..fc202495a06a --- /dev/null +++ b/.changeset/thick-boats-fix.md @@ -0,0 +1,10 @@ +--- +"@ledgerhq/coin-stellar": minor +"@ledgerhq/live-common": minor +"@ledgerhq/coin-framework": minor +"@ledgerhq/coin-polkadot": patch +"@ledgerhq/coin-tezos": patch +"@ledgerhq/coin-xrp": patch +--- + +Prepare CoinModule Stellar for Alpaca From 62c28b2a8503afe1024ec93b6ff5749fbf9fd72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Wed, 7 Aug 2024 10:19:29 +0200 Subject: [PATCH 14/16] chore: update test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- .../src/bridge/buildTransaction.integ.test.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts index 8bdffbfe3b4d..df05d6e346ad 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts @@ -99,7 +99,6 @@ describe("buildTransaction", () => { it("returns a built transaction in Stellar format when useAllAmount", async () => { // Given - const sender = "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ"; const account = createFixtureAccount({ freshAddress: sender, balance: BigNumber(600239412), @@ -150,9 +149,7 @@ describe("buildTransaction", () => { // Then expect(builtTransaction.fee).toEqual("1"); - expect(builtTransaction.source).toEqual( - "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", - ); + expect(builtTransaction.source).toEqual(sender); expect(builtTransaction.operations).toHaveLength(1); const operation = builtTransaction.operations[0]; expect(operation.type).toEqual("payment"); @@ -181,9 +178,7 @@ describe("buildTransaction", () => { // Then expect(builtTransaction.fee).toEqual("1"); - expect(builtTransaction.source).toEqual( - "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ", - ); + expect(builtTransaction.source).toEqual(sender); expect(builtTransaction.operations).toHaveLength(1); expect(builtTransaction.memo.type).toEqual("text"); expect(builtTransaction.memo.value).toEqual("Hello"); From a68c63210058b3beb5d20d616caa120ed2fce631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Wed, 7 Aug 2024 14:21:25 +0200 Subject: [PATCH 15/16] fix: rebase issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- libs/coin-modules/coin-stellar/package.json | 2 +- .../coin-stellar/src/api/index.integ.test.ts | 4 +- .../src/{ => bridge}/broadcast.test.ts | 4 +- .../src/bridge/buildTransaction.integ.test.ts | 38 ++-- .../src/{ => bridge}/signOperation.test.ts | 23 +- .../synchronization.integ.test.ts | 8 +- .../src/buildTransaction.integ.test.ts | 196 ------------------ pnpm-lock.yaml | 97 +-------- 8 files changed, 48 insertions(+), 324 deletions(-) rename libs/coin-modules/coin-stellar/src/{ => bridge}/broadcast.test.ts (91%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/signOperation.test.ts (91%) rename libs/coin-modules/coin-stellar/src/{ => bridge}/synchronization.integ.test.ts (87%) delete mode 100644 libs/coin-modules/coin-stellar/src/buildTransaction.integ.test.ts diff --git a/libs/coin-modules/coin-stellar/package.json b/libs/coin-modules/coin-stellar/package.json index 74a36ea0569d..e6f6fbc09d36 100644 --- a/libs/coin-modules/coin-stellar/package.json +++ b/libs/coin-modules/coin-stellar/package.json @@ -134,7 +134,7 @@ "@faker-js/faker": "^8.4.1", "@types/invariant": "^2.2.2", "@types/jest": "^29.5.10", - "@types/node": "^16.11.7", + "@types/node": "^20.8.10", "jest": "^29.7.0", "ts-jest": "^29.1.1" } diff --git a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts index 9d851826145b..865efffba888 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.integ.test.ts @@ -75,8 +75,8 @@ describe("Stellar Api", () => { }); // Then - expect(result.slice(0, 68)).toEqual( - "AAAAAgAAAAD9Ai6ZfJT42rd0Nl8YJeODFgju688SXPzMZvSA369YPwAAAGQAAHloAAAI", + expect(result.slice(0, 67)).toEqual( + "AAAAAgAAAAD9Ai6ZfJT42rd0Nl8YJeODFgju688SXPzMZvSA369YPwAAAGQAAHloAAA", ); expect(result.slice(70)).toEqual( "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAD9Ai6ZfJT42rd0Nl8YJeODFgju688SXPzMZvSA369YPwAAAAAAAAAAAA9CQAAAAAAAAAAA", diff --git a/libs/coin-modules/coin-stellar/src/broadcast.test.ts b/libs/coin-modules/coin-stellar/src/bridge/broadcast.test.ts similarity index 91% rename from libs/coin-modules/coin-stellar/src/broadcast.test.ts rename to libs/coin-modules/coin-stellar/src/bridge/broadcast.test.ts index c50fc435ebed..c5958e7d08ef 100644 --- a/libs/coin-modules/coin-stellar/src/broadcast.test.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/broadcast.test.ts @@ -1,8 +1,8 @@ import { broadcast } from "./broadcast"; -import { createFixtureAccount, createFixtureOperation } from "./types/bridge.fixture"; +import { createFixtureAccount, createFixtureOperation } from "../types/bridge.fixture"; const mockBroadcast = jest.fn(); -jest.mock("./network", () => ({ +jest.mock("../network", () => ({ broadcastTransaction: (sig: unknown) => mockBroadcast(sig), })); diff --git a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts index df05d6e346ad..ab0205d7297f 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.integ.test.ts @@ -1,11 +1,11 @@ import BigNumber from "bignumber.js"; import { buildTransaction } from "./buildTransaction"; import { createFixtureAccount, createFixtureTransaction } from "../types/bridge.fixture"; -import { NetworkInfo } from "../types"; import coinConfig, { type StellarCoinConfig } from "../config"; +import { NetworkInfo } from "../types"; describe("buildTransaction", () => { - const sender = "GCTS5ANSL6YCXR2M4XXM5BPN34UUT3M2VUJWVYOX5EMSHZC3T7O5Z6NZ"; + const sender = "GAT4LBXYJGJJJRSNK74NPFLO55CDDXSYVMQODSEAAH3M6EY4S7LPH5GV"; beforeAll(() => { coinConfig.setCoinConfig( @@ -19,22 +19,24 @@ describe("buildTransaction", () => { ); }); - it("throws an error when no fees are setted in the transaction", async () => { + it("throws an error if transaction has no NetworkInfo", async () => { // Given - const account = createFixtureAccount(); - const transaction = createFixtureTransaction(); + const account = createFixtureAccount({ freshAddress: sender }); + const transaction = createFixtureTransaction({ fees: BigNumber(1) }); // When - await expect(buildTransaction(account, transaction)).rejects.toThrow("FeeNotLoaded"); + await expect(buildTransaction(account, transaction)).rejects.toThrow("stellar family"); }); - it("throws an error if transaction has no NetworkInfo", async () => { + it("throws an error when no fees are setted in the transaction", async () => { // Given - const account = createFixtureAccount({ freshAddress: sender }); - const transaction = createFixtureTransaction({ fees: BigNumber(1) }); + const account = createFixtureAccount(); + const transaction = createFixtureTransaction({ + networkInfo: { family: "stellar" } as NetworkInfo, + }); // When - await expect(buildTransaction(account, transaction)).rejects.toThrow("stellar family"); + await expect(buildTransaction(account, transaction)).rejects.toThrow("FeeNotLoaded"); }); it.skip("crash if transaction amount is 0", async () => { @@ -89,8 +91,8 @@ describe("buildTransaction", () => { expect((operation as any).amount).toEqual("0.0000010"); expect((operation as any).asset.code).toEqual("XLM"); expect((operation as any).asset.issuer).toBeUndefined(); - expect(builtTransaction.toXDR().slice(0, 68)).toEqual( - "AAAAAgAAAACnLoGyX7Arx0zl7s6F7d8pSe2arRNq4dfpGSPkW5/d3AAAAAECbx/3AAHJ", + expect(builtTransaction.toXDR().slice(0, 67)).toEqual( + "AAAAAgAAAAAnxYb4SZKUxk1X+NeVbu9EMd5YqyDhyIAB9s8THJfW8wAAAAEBn2AnAAA", ); expect(builtTransaction.toXDR().slice(70)).toEqual( "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADw9kGYtpM1vsCgDoHjZOVO/sjTKLsmA51f8vdM9oaecgAAAAAAAAAAAAAACgAAAAAAAAAA", @@ -124,8 +126,8 @@ describe("buildTransaction", () => { expect((operation as any).amount).toEqual("50.0239411"); expect((operation as any).asset.code).toEqual("XLM"); expect((operation as any).asset.issuer).toBeUndefined(); - expect(builtTransaction.toXDR().slice(0, 68)).toEqual( - "AAAAAgAAAACnLoGyX7Arx0zl7s6F7d8pSe2arRNq4dfpGSPkW5/d3AAAAAECbx/3AAHJ", + expect(builtTransaction.toXDR().slice(0, 67)).toEqual( + "AAAAAgAAAAAnxYb4SZKUxk1X+NeVbu9EMd5YqyDhyIAB9s8THJfW8wAAAAEBn2AnAAA", ); expect(builtTransaction.toXDR().slice(70)).toEqual( "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADw9kGYtpM1vsCgDoHjZOVO/sjTKLsmA51f8vdM9oaecgAAAAAAAAAAHdEMMwAAAAAAAAAA", @@ -157,8 +159,8 @@ describe("buildTransaction", () => { expect((operation as any).asset.issuer).toEqual( "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", ); - expect(builtTransaction.toXDR().slice(0, 68)).toEqual( - "AAAAAgAAAACnLoGyX7Arx0zl7s6F7d8pSe2arRNq4dfpGSPkW5/d3AAAAAECbx/3AAHJ", + expect(builtTransaction.toXDR().slice(0, 67)).toEqual( + "AAAAAgAAAAAnxYb4SZKUxk1X+NeVbu9EMd5YqyDhyIAB9s8THJfW8wAAAAEBn2AnAAA", ); }); @@ -186,8 +188,8 @@ describe("buildTransaction", () => { expect(operation.type).toEqual("payment"); expect((operation as any).asset.code).toEqual("XLM"); expect((operation as any).asset.issuer).toBeUndefined(); - expect(builtTransaction.toXDR().slice(0, 68)).toEqual( - "AAAAAgAAAACnLoGyX7Arx0zl7s6F7d8pSe2arRNq4dfpGSPkW5/d3AAAAAECbx/3AAHJ", + expect(builtTransaction.toXDR().slice(0, 67)).toEqual( + "AAAAAgAAAAAnxYb4SZKUxk1X+NeVbu9EMd5YqyDhyIAB9s8THJfW8wAAAAEBn2AnAAA", ); expect(builtTransaction.toXDR().slice(70)).toEqual( "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAVIZWxsbwAAAAAAAAEAAAAAAAAAAQAAAADw9kGYtpM1vsCgDoHjZOVO/sjTKLsmA51f8vdM9oaecgAAAAAAAAAAAAAACgAAAAAAAAAA", diff --git a/libs/coin-modules/coin-stellar/src/signOperation.test.ts b/libs/coin-modules/coin-stellar/src/bridge/signOperation.test.ts similarity index 91% rename from libs/coin-modules/coin-stellar/src/signOperation.test.ts rename to libs/coin-modules/coin-stellar/src/bridge/signOperation.test.ts index 659b06eda549..f0058a138cf4 100644 --- a/libs/coin-modules/coin-stellar/src/signOperation.test.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/signOperation.test.ts @@ -1,13 +1,13 @@ -import BigNumber from "bignumber.js"; import { SignOperationEvent } from "@ledgerhq/types-live"; -import { buildSignOperation } from "./signOperation"; -import { setCoinConfig, type StellarCoinConfig } from "./config"; -import { StellarSigner } from "./types/signer"; -import { createFixtureAccount, createFixtureTransaction } from "./types/bridge.fixture"; -import { NetworkInfo } from "./types"; import { Keypair } from "@stellar/stellar-sdk"; -import buildTransaction from "./buildTransaction"; +import BigNumber from "bignumber.js"; import { subtle } from "crypto"; +import coinConfig, { type StellarCoinConfig } from "../config"; +import { NetworkInfo } from "../types"; +import { createFixtureAccount, createFixtureTransaction } from "../types/bridge.fixture"; +import { StellarSigner } from "../types/signer"; +import buildTransaction from "./buildTransaction"; +import { buildSignOperation } from "./signOperation"; const stellarKp = Keypair.random(); const mockLoadAccount = jest.fn().mockResolvedValue( @@ -28,8 +28,8 @@ const mockLoadAccount = jest.fn().mockResolvedValue( incrementSequenceNumber: () => "1", }, ); -jest.mock("./network", () => ({ - ...jest.requireActual("./network"), +jest.mock("../network", () => ({ + ...jest.requireActual("../network"), loadAccount: () => mockLoadAccount(), fetchSequence: jest.fn(), })); @@ -46,9 +46,12 @@ describe.skip("signOperation", () => { const deviceId = "dummyDeviceId"; beforeAll(() => { - setCoinConfig( + coinConfig.setCoinConfig( (): StellarCoinConfig => ({ status: { type: "active" }, + explorer: { + url: "https://localhost", + }, }), ); }); diff --git a/libs/coin-modules/coin-stellar/src/synchronization.integ.test.ts b/libs/coin-modules/coin-stellar/src/bridge/synchronization.integ.test.ts similarity index 87% rename from libs/coin-modules/coin-stellar/src/synchronization.integ.test.ts rename to libs/coin-modules/coin-stellar/src/bridge/synchronization.integ.test.ts index 6af36b0f5fba..6658fdf4114f 100644 --- a/libs/coin-modules/coin-stellar/src/synchronization.integ.test.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/synchronization.integ.test.ts @@ -1,9 +1,9 @@ import { firstValueFrom, reduce } from "rxjs"; import { Account, AccountBridge, SyncConfig, TransactionCommon } from "@ledgerhq/types-live"; -import type { StellarCoinConfig } from "./config"; -import { Transaction, StellarAccount } from "./types"; -import { createBridges } from "./bridge/index"; -import { createFixtureAccount } from "./types/bridge.fixture"; +import type { StellarCoinConfig } from "../config"; +import { Transaction, StellarAccount } from "../types"; +import { createBridges } from "../bridge/index"; +import { createFixtureAccount } from "../types/bridge.fixture"; const defaultSyncConfig = { paginationConfig: {}, diff --git a/libs/coin-modules/coin-stellar/src/buildTransaction.integ.test.ts b/libs/coin-modules/coin-stellar/src/buildTransaction.integ.test.ts deleted file mode 100644 index d3e6d247fc3f..000000000000 --- a/libs/coin-modules/coin-stellar/src/buildTransaction.integ.test.ts +++ /dev/null @@ -1,196 +0,0 @@ -import BigNumber from "bignumber.js"; -import { buildTransaction } from "./buildTransaction"; -import { createFixtureAccount, createFixtureTransaction } from "./types/bridge.fixture"; -import { setCoinConfig, type StellarCoinConfig } from "./config"; -import { NetworkInfo } from "./types"; - -describe("buildTransaction", () => { - const sender = "GAT4LBXYJGJJJRSNK74NPFLO55CDDXSYVMQODSEAAH3M6EY4S7LPH5GV"; - - beforeAll(() => { - setCoinConfig( - (): StellarCoinConfig => ({ - status: { type: "active" }, - explorer: { - url: "https://stellar.coin.ledger.com", //"https://horizon-testnet.stellar.org/", - fetchLimit: 100, - }, - }), - ); - }); - - it("throws an error when no fees are setted in the transaction", async () => { - // Given - const account = createFixtureAccount(); - const transaction = createFixtureTransaction(); - - // When - await expect(buildTransaction(account, transaction)).rejects.toThrow("FeeNotLoaded"); - }); - - it("throws an error if transaction has no NetworkInfo", async () => { - // Given - const account = createFixtureAccount({ freshAddress: sender }); - const transaction = createFixtureTransaction({ fees: BigNumber(1) }); - - // When - await expect(buildTransaction(account, transaction)).rejects.toThrow("stellar family"); - }); - - it.skip("crash if transaction amount is 0", async () => { - // Given - const account = createFixtureAccount({ freshAddress: sender }); - const transaction = createFixtureTransaction({ - amount: BigNumber(0), - fees: BigNumber(1), - networkInfo: { family: "stellar" } as NetworkInfo, - }); - - // When - const builtTransaction = await buildTransaction(account, transaction); - - // Then - expect(builtTransaction).toBeUndefined(); - }); - - it("throws an error when recipient is an invalid address", async () => { - // Given - const account = createFixtureAccount({ freshAddress: sender }); - const transaction = createFixtureTransaction({ - amount: BigNumber(10), - fees: BigNumber(1), - networkInfo: { family: "stellar" } as NetworkInfo, - recipient: "NEW", - }); - - // When - await expect(buildTransaction(account, transaction)).rejects.toThrow("destination is invalid"); - }); - - it("returns a built transaction in Stellar format", async () => { - // Given - const account = createFixtureAccount({ freshAddress: sender }); - const transaction = createFixtureTransaction({ - amount: BigNumber(10), - fees: BigNumber(1), - networkInfo: { family: "stellar" } as NetworkInfo, - }); - - // When - const builtTransaction = await buildTransaction(account, transaction); - - // Then - expect(builtTransaction.fee).toEqual("1"); - expect(builtTransaction.source).toEqual(sender); - expect(builtTransaction.operations).toHaveLength(1); - const operation = builtTransaction.operations[0]; - expect(operation.type).toEqual("payment"); - expect((operation as any).destination).toEqual(transaction.recipient); - expect((operation as any).amount).toEqual("0.0000010"); - expect((operation as any).asset.code).toEqual("XLM"); - expect((operation as any).asset.issuer).toBeUndefined(); - expect(builtTransaction.toXDR().slice(0, 67)).toEqual( - "AAAAAgAAAAAnxYb4SZKUxk1X+NeVbu9EMd5YqyDhyIAB9s8THJfW8wAAAAEBn2AnAAA", - ); - expect(builtTransaction.toXDR().slice(70)).toEqual( - "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADw9kGYtpM1vsCgDoHjZOVO/sjTKLsmA51f8vdM9oaecgAAAAAAAAAAAAAACgAAAAAAAAAA", - ); - }); - - it("returns a built transaction in Stellar format when useAllAmount", async () => { - // Given - const account = createFixtureAccount({ - freshAddress: sender, - balance: BigNumber(600239412), - spendableBalance: BigNumber(500239412), - }); - const transaction = createFixtureTransaction({ - amount: BigNumber(10), - fees: BigNumber(1), - networkInfo: { family: "stellar" } as NetworkInfo, - useAllAmount: true, - }); - - // When - const builtTransaction = await buildTransaction(account, transaction); - - // Then - expect(builtTransaction.fee).toEqual("1"); - expect(builtTransaction.source).toEqual(sender); - expect(builtTransaction.operations).toHaveLength(1); - const operation = builtTransaction.operations[0]; - expect(operation.type).toEqual("payment"); - expect((operation as any).destination).toEqual(transaction.recipient); - expect((operation as any).amount).toEqual("50.0239411"); - expect((operation as any).asset.code).toEqual("XLM"); - expect((operation as any).asset.issuer).toBeUndefined(); - expect(builtTransaction.toXDR().slice(0, 67)).toEqual( - "AAAAAgAAAAAnxYb4SZKUxk1X+NeVbu9EMd5YqyDhyIAB9s8THJfW8wAAAAEBn2AnAAA", - ); - expect(builtTransaction.toXDR().slice(70)).toEqual( - "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADw9kGYtpM1vsCgDoHjZOVO/sjTKLsmA51f8vdM9oaecgAAAAAAAAAAHdEMMwAAAAAAAAAA", - ); - }); - - it("returns a built transaction in Stellar format when asset used is USDC", async () => { - // Given - const account = createFixtureAccount({ freshAddress: sender }); - const transaction = createFixtureTransaction({ - amount: BigNumber(10), - fees: BigNumber(1), - networkInfo: { family: "stellar" } as NetworkInfo, - recipient: "GDRQAROTM7WYEHZ42SXUVUOHO36MLQKLFIZ5Y2JBWVQRPCJ3SQBBA3LH", - assetCode: "USDC", - assetIssuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", - }); - - // When - const builtTransaction = await buildTransaction(account, transaction); - - // Then - expect(builtTransaction.fee).toEqual("1"); - expect(builtTransaction.source).toEqual(sender); - expect(builtTransaction.operations).toHaveLength(1); - const operation = builtTransaction.operations[0]; - expect(operation.type).toEqual("payment"); - expect((operation as any).asset.code).toEqual("USDC"); - expect((operation as any).asset.issuer).toEqual( - "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", - ); - expect(builtTransaction.toXDR().slice(0, 67)).toEqual( - "AAAAAgAAAAAnxYb4SZKUxk1X+NeVbu9EMd5YqyDhyIAB9s8THJfW8wAAAAEBn2AnAAA", - ); - }); - - it("returns a built transaction in Stellar format", async () => { - // Given - const account = createFixtureAccount({ freshAddress: sender }); - const transaction = createFixtureTransaction({ - amount: BigNumber(10), - fees: BigNumber(1), - networkInfo: { family: "stellar" } as NetworkInfo, - memoType: "MEMO_TEXT", - memoValue: "Hello", - }); - - // When - const builtTransaction = await buildTransaction(account, transaction); - - // Then - expect(builtTransaction.fee).toEqual("1"); - expect(builtTransaction.source).toEqual(sender); - expect(builtTransaction.operations).toHaveLength(1); - expect(builtTransaction.memo.type).toEqual("text"); - expect(builtTransaction.memo.value).toEqual("Hello"); - const operation = builtTransaction.operations[0]; - expect(operation.type).toEqual("payment"); - expect((operation as any).asset.code).toEqual("XLM"); - expect((operation as any).asset.issuer).toBeUndefined(); - expect(builtTransaction.toXDR().slice(0, 67)).toEqual( - "AAAAAgAAAAAnxYb4SZKUxk1X+NeVbu9EMd5YqyDhyIAB9s8THJfW8wAAAAEBn2AnAAA", - ); - expect(builtTransaction.toXDR().slice(70)).toEqual( - "AAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAVIZWxsbwAAAAAAAAEAAAAAAAAAAQAAAADw9kGYtpM1vsCgDoHjZOVO/sjTKLsmA51f8vdM9oaecgAAAAAAAAAAAAAACgAAAAAAAAAA", - ); - }); -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29f88b9c8ff4..be6682cd204e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2529,14 +2529,14 @@ importers: specifier: ^29.5.10 version: 29.5.12 '@types/node': - specifier: ^16.11.7 - version: 16.18.101 + specifier: ^20.8.10 + version: 20.12.12 jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@16.18.101) + version: 29.7.0(@types/node@20.12.12) ts-jest: specifier: ^29.1.1 - version: 29.1.5(jest@29.7.0(@types/node@16.18.101))(typescript@5.4.3) + version: 29.1.5(jest@29.7.0(@types/node@20.12.12))(typescript@5.4.3) libs/coin-modules/coin-tezos: dependencies: @@ -14259,9 +14259,6 @@ packages: '@types/node@13.13.52': resolution: {integrity: sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==} - '@types/node@16.18.101': - resolution: {integrity: sha512-AAsx9Rgz2IzG8KJ6tXd6ndNkVcu+GYB6U/SnFAaokSPNx2N7dcIIfnighYUNumvj6YS2q39Dejz5tT0NCV7CWA==} - '@types/node@18.15.13': resolution: {integrity: sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==} @@ -42359,8 +42356,6 @@ snapshots: '@types/node@13.13.52': {} - '@types/node@16.18.101': {} - '@types/node@18.15.13': {} '@types/node@18.19.26': @@ -46124,22 +46119,6 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@16.18.101): - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@16.18.101) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - metro - - supports-color - - ts-node - create-jest@29.7.0(@types/node@18.19.26)(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)): dependencies: '@jest/types': 29.6.3 @@ -51692,26 +51671,6 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@16.18.101): - dependencies: - '@jest/core': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@16.18.101) - exit: 0.1.2 - import-local: 3.1.0 - jest-config: 29.7.0(@types/node@16.18.101) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - metro - - supports-color - - ts-node - jest-cli@29.7.0(@types/node@18.19.26)(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)) @@ -51994,37 +51953,6 @@ snapshots: - metro - supports-color - jest-config@29.7.0(@types/node@16.18.101): - dependencies: - '@babel/core': 7.24.3 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.24.3) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 16.18.101 - transitivePeerDependencies: - - babel-plugin-macros - - metro - - supports-color - jest-config@29.7.0(@types/node@18.19.26)(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)): dependencies: '@babel/core': 7.24.3 @@ -53277,19 +53205,6 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@16.18.101): - dependencies: - '@jest/core': 29.7.0 - '@jest/types': 29.6.3 - import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@16.18.101) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - metro - - supports-color - - ts-node - jest@29.7.0(@types/node@18.19.26)(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)): dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@18.19.26)(source-map-support@0.5.21)(typescript@4.9.5)) @@ -62098,11 +62013,11 @@ snapshots: typescript: 5.4.3 yargs-parser: 21.1.1 - ts-jest@29.1.5(jest@29.7.0(@types/node@16.18.101))(typescript@5.4.3): + ts-jest@29.1.5(jest@29.7.0(@types/node@20.12.12))(typescript@5.4.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@16.18.101) + jest: 29.7.0(@types/node@20.12.12) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 From 287ffa0b948ecf8a58e76ff2765a0021d8f5497e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Prohaszka?= Date: Fri, 9 Aug 2024 11:14:46 +0200 Subject: [PATCH 16/16] chore: update bot device to NanoSP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Prohaszka --- libs/coin-modules/coin-stellar/src/test/bot-specs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/coin-modules/coin-stellar/src/test/bot-specs.ts b/libs/coin-modules/coin-stellar/src/test/bot-specs.ts index 5adb12abc504..433cc37eaee5 100644 --- a/libs/coin-modules/coin-stellar/src/test/bot-specs.ts +++ b/libs/coin-modules/coin-stellar/src/test/bot-specs.ts @@ -29,7 +29,7 @@ const stellar: AppSpec = { name: "Stellar", currency, appQuery: { - model: DeviceModelId.nanoS, + model: DeviceModelId.nanoSP, appName: "Stellar", }, genericDeviceAction: acceptTransaction,