diff --git a/.changeset/slow-tips-camp.md b/.changeset/slow-tips-camp.md new file mode 100644 index 000000000000..81d985413475 --- /dev/null +++ b/.changeset/slow-tips-camp.md @@ -0,0 +1,9 @@ +--- +"@ledgerhq/coin-polkadot": minor +"@ledgerhq/coin-stellar": minor +"@ledgerhq/coin-tezos": minor +"@ledgerhq/coin-xrp": minor +"@ledgerhq/coin-framework": minor +--- + +Alpaca allows delegate and undelegate tx crafting diff --git a/libs/coin-framework/src/api/errors.ts b/libs/coin-framework/src/api/errors.ts new file mode 100644 index 000000000000..24912924e312 --- /dev/null +++ b/libs/coin-framework/src/api/errors.ts @@ -0,0 +1,5 @@ +export class IncorrectTypeError extends Error { + constructor(message?: string) { + super(`IncorrectType: ${message}`); + } +} diff --git a/libs/coin-framework/src/api/index.ts b/libs/coin-framework/src/api/index.ts index 78b6395f7e30..69fee69f8f96 100644 --- a/libs/coin-framework/src/api/index.ts +++ b/libs/coin-framework/src/api/index.ts @@ -3,4 +3,5 @@ * One consumer of this API is Alpaca. */ +export * from "./errors"; export * from "./types"; diff --git a/libs/coin-framework/src/api/types.ts b/libs/coin-framework/src/api/types.ts index 89586d554f14..cff36d366b16 100644 --- a/libs/coin-framework/src/api/types.ts +++ b/libs/coin-framework/src/api/types.ts @@ -20,7 +20,7 @@ export type Operation = { }; export type Transaction = { - mode: string; + type: string; recipient: string; amount: bigint; fee: bigint; 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 2872486b0143..e399c8955baf 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,7 +81,7 @@ describe("Polkadot Api", () => { it("returns a raw transaction", async () => { // When const result = await module.craftTransaction(address, { - mode: "send", + type: "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 3ed38a624353..fcdc3d4d6f0f 100644 --- a/libs/coin-modules/coin-polkadot/src/api/index.ts +++ b/libs/coin-modules/coin-polkadot/src/api/index.ts @@ -1,4 +1,4 @@ -import type { Api } from "@ledgerhq/coin-framework/api/index"; +import type { Api, Transaction as ApiTransaction } from "@ledgerhq/coin-framework/api/index"; import coinConfig, { type PolkadotConfig } from "../config"; import { broadcast, @@ -27,16 +27,7 @@ export function createApi(config: PolkadotConfig): Api { }; } -async function craft( - address: string, - transaction: { - mode: string; - recipient: string; - amount: bigint; - fee: bigint; - supplement?: unknown; - }, -): Promise { +async function craft(address: string, transaction: ApiTransaction): Promise { const extrinsicArg = defaultExtrinsicArg(transaction.amount, transaction.recipient); //TODO: Retrieve correctly the nonce via a call to the node `await api.rpc.system.accountNextIndex(address)` const nonce = 0; 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 865efffba888..87023840ae85 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 @@ -68,7 +68,7 @@ describe("Stellar Api", () => { it("returns a raw transaction", async () => { // When const result = await module.craftTransaction(address, { - mode: "send", + type: "send", recipient: "GD6QELUZPSKPRWVXOQ3F6GBF4OBRMCHO5PHREXH4ZRTPJAG7V5MD7JGX", amount: BigInt(1_000_000), fee: BigInt(100), diff --git a/libs/coin-modules/coin-stellar/src/api/index.ts b/libs/coin-modules/coin-stellar/src/api/index.ts index df3d896a8458..f13df4107a22 100644 --- a/libs/coin-modules/coin-stellar/src/api/index.ts +++ b/libs/coin-modules/coin-stellar/src/api/index.ts @@ -1,4 +1,4 @@ -import type { Api } from "@ledgerhq/coin-framework/api/index"; +import type { Api, Transaction as ApiTransaction } from "@ledgerhq/coin-framework/api/index"; import coinConfig, { type StellarConfig } from "../config"; import { broadcast, @@ -33,16 +33,7 @@ type Supplement = { 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 { +async function craft(address: string, transaction: ApiTransaction): Promise { const supplement = isSupplement(transaction.supplement) ? { assetCode: transaction.supplement?.assetCode, diff --git a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts index b825430e97c5..45f77a17c8d7 100644 --- a/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts +++ b/libs/coin-modules/coin-stellar/src/bridge/buildTransaction.ts @@ -28,7 +28,7 @@ export async function buildTransaction(account: Account, transaction: Transactio const { transaction: built } = await craftTransaction( { address: account.freshAddress }, { - mode, + type: mode, recipient, amount: BigInt(amount.toString()), fee: BigInt(fees.toString()), diff --git a/libs/coin-modules/coin-stellar/src/logic/craftTransaction.ts b/libs/coin-modules/coin-stellar/src/logic/craftTransaction.ts index c37785967f72..b8e5f25618ac 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: string; + type: string; recipient: string; amount: bigint; fee: bigint; @@ -29,7 +29,7 @@ export async function craftTransaction( memoValue?: string | null | undefined; }, ): Promise<{ transaction: StellarSdkTransaction; xdr: string }> { - const { amount, recipient, fee, memoType, memoValue, mode, assetCode, assetIssuer } = transaction; + const { amount, recipient, fee, memoType, memoValue, type, assetCode, assetIssuer } = transaction; const source = await loadAccount(account.address); @@ -40,7 +40,7 @@ export async function craftTransaction( const transactionBuilder = buildTransactionBuilder(source, fee); let operation: xdr.Operation | null = null; - if (mode === "changeTrust") { + if (type === "changeTrust") { if (!assetCode || !assetIssuer) { throw new StellarAssetRequired(""); } 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 e42bd57df02e..179cf21ebaf8 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 @@ -1,4 +1,4 @@ -import type { Api } from "@ledgerhq/coin-framework/api/index"; +import { IncorrectTypeError, type Api } from "@ledgerhq/coin-framework/api/index"; import { createApi } from "."; /** * https://teztnets.com/ghostnet-about @@ -82,19 +82,44 @@ describe("Tezos Api", () => { }); describe("craftTransaction", () => { - it("returns a raw transaction", async () => { + it.each([ + { + type: "send", + rawTx: + "6c0053ddb3b3a89ed5c8d8326066032beac6de225c9e010300000a0000a31e81ac3425310e3274a4698a793b2839dc0afa00", + }, + { + type: "delegate", + rawTx: + "6e0053ddb3b3a89ed5c8d8326066032beac6de225c9e01030000ff00a31e81ac3425310e3274a4698a793b2839dc0afa", + }, + { + type: "undelegate", + rawTx: "6e0053ddb3b3a89ed5c8d8326066032beac6de225c9e0103000000", + }, + ])("returns a raw transaction with $type", async ({ type, rawTx }) => { // When const result = await module.craftTransaction(address, { - mode: "send", + type, recipient: "tz1aWXP237BLwNHJcCD4b3DutCevhqq2T1Z9", amount: BigInt(10), fee: BigInt(1), }); // Then - expect(result.slice(64)).toEqual( - "6c0053ddb3b3a89ed5c8d8326066032beac6de225c9e010300000a0000a31e81ac3425310e3274a4698a793b2839dc0afa00", - ); + expect(result.slice(64)).toEqual(rawTx); + }); + + it("throws an error if type in 'send' or 'delegate' or 'undelegate'", async () => { + // When + await expect( + module.craftTransaction(address, { + type: "WHATEVERTYPE", + recipient: "tz1aWXP237BLwNHJcCD4b3DutCevhqq2T1Z9", + amount: BigInt(10), + fee: BigInt(1), + }), + ).rejects.toThrow(IncorrectTypeError); }); }); }); diff --git a/libs/coin-modules/coin-tezos/src/api/index.ts b/libs/coin-modules/coin-tezos/src/api/index.ts index df61edaf54c9..4d5a5805af14 100644 --- a/libs/coin-modules/coin-tezos/src/api/index.ts +++ b/libs/coin-modules/coin-tezos/src/api/index.ts @@ -1,4 +1,8 @@ -import type { Api } from "@ledgerhq/coin-framework/api/index"; +import { + IncorrectTypeError, + type Api, + type Transaction as ApiTransaction, +} from "@ledgerhq/coin-framework/api/index"; import coinConfig, { type TezosConfig } from "../config"; import { broadcast, @@ -26,17 +30,20 @@ export function createApi(config: TezosConfig): Api { }; } +function isTezosTransactionType(type: string): type is "send" | "delegate" | "undelegate" { + return ["send", "delegate", "undelegate"].includes(type); +} async function craft( address: string, - transaction: { - recipient: string; - amount: bigint; - fee: bigint; - }, + { type, recipient, amount, fee }: ApiTransaction, ): Promise { + if (!isTezosTransactionType(type)) { + throw new IncorrectTypeError(type); + } + const { contents } = await craftTransaction( { address }, - { ...transaction, type: "send", fee: { fees: transaction.fee.toString() } }, + { recipient, amount, type, fee: { fees: fee.toString() } }, ); return rawEncode(contents); } 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 eeb6853d6409..883f6d0e3b13 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 @@ -77,7 +77,7 @@ describe("Xrp Api", () => { it("returns a raw transaction", async () => { // When const result = await module.craftTransaction(address, { - mode: "send", + type: "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 b57ce2b187c9..37d0d58be6b5 100644 --- a/libs/coin-modules/coin-xrp/src/api/index.ts +++ b/libs/coin-modules/coin-xrp/src/api/index.ts @@ -1,4 +1,8 @@ -import type { Api, Operation } from "@ledgerhq/coin-framework/api/index"; +import type { + Api, + Operation, + Transaction as ApiTransaction, +} from "@ledgerhq/coin-framework/api/index"; import coinConfig, { type XrpConfig } from "../config"; import { broadcast, @@ -25,15 +29,7 @@ export function createApi(config: XrpConfig): Api { }; } -async function craft( - address: string, - transaction: { - mode: string; - recipient: string; - amount: bigint; - fee: bigint; - }, -): Promise { +async function craft(address: string, transaction: ApiTransaction): Promise { const nextSequenceNumber = await getNextValidSequence(address); const tx = await craftTransaction({ address, nextSequenceNumber }, transaction); return tx.serializedTransaction;