@ledgerhq/coin-module-framework 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js ADDED
@@ -0,0 +1,20 @@
1
+ module.exports = {
2
+ env: {
3
+ browser: true,
4
+ es6: true,
5
+ },
6
+ overrides: [
7
+ {
8
+ files: ["src/**/*.test.{ts,tsx}"],
9
+ env: {
10
+ "jest/globals": true,
11
+ },
12
+ plugins: ["jest"],
13
+ },
14
+ ],
15
+ rules: {
16
+ "no-console": ["error", { allow: ["warn", "error"] }],
17
+ "@typescript-eslint/no-empty-function": "off",
18
+ "@typescript-eslint/no-explicit-any": "warn",
19
+ },
20
+ };
@@ -0,0 +1,33 @@
1
+ {
2
+ "entry": [
3
+ "src/api/index.ts",
4
+ "src/currencies/index.ts",
5
+ "src/index.ts",
6
+ "src/utils.ts"
7
+ ],
8
+ "ignoreUnimported": [
9
+ "src/api/errors.ts",
10
+ "src/api/index.ts",
11
+ "src/api/types.ts",
12
+ "src/config.ts",
13
+ "src/currencies/formatCurrencyUnit.ts",
14
+ "src/currencies/index.ts",
15
+ "src/currencies/parseCurrencyUnit.ts",
16
+ "src/errors.ts",
17
+ "src/features/types.ts",
18
+ "src/setup.ts",
19
+ "src/test/utils.ts",
20
+ "src/utils.ts"
21
+ ],
22
+ "ignoreUnresolved": [
23
+ "src/features/types.ts",
24
+ "src/setup.ts",
25
+ "src/utils.ts"
26
+ ],
27
+ "ignoreUnused": [
28
+ "@ledgerhq/errors",
29
+ "@ledgerhq/live-env",
30
+ "@ledgerhq/types-cryptoassets",
31
+ "expect"
32
+ ]
33
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # @ledgerhq/coin-module-framework
@@ -0,0 +1,9 @@
1
+ module.exports = async () => {
2
+ /**
3
+ * Set the timezone to EST for all tests
4
+ * This allow us to run tests directly through jest without having to manually
5
+ * set the timezone as an env var beforehand, either manually or through a
6
+ * custom script in package.json
7
+ */
8
+ process.env.TZ = "America/New_York";
9
+ };
package/jest.config.js ADDED
@@ -0,0 +1,30 @@
1
+ module.exports = {
2
+ testEnvironment: "node",
3
+ transform: {
4
+ "^.+\\.(t|j)sx?$": [
5
+ "@swc/jest",
6
+ {
7
+ jsc: {
8
+ target: "esnext",
9
+ },
10
+ },
11
+ ],
12
+ },
13
+ testPathIgnorePatterns: ["lib/", "lib-es/"],
14
+ globalSetup: "<rootDir>/jest-global-setup.js",
15
+ passWithNoTests: true,
16
+ collectCoverageFrom: [
17
+ "src/**/*.{ts,js,tsx}",
18
+ "!src/**/*.test.{ts,tsx}",
19
+ "!src/**/*.spec.{ts,tsx}",
20
+ "!src/**/__integration__/**",
21
+ "!src/**/__integrations__/**",
22
+ "!src/**/__tests__/**",
23
+ ],
24
+ coverageReporters: ["json", ["lcov", { file: "lcov.info", projectRoot: "../../" }], "text"],
25
+ reporters: [
26
+ "default",
27
+ ["jest-sonar", { outputName: "sonar-executionTests-report.xml", reportedFilePath: "absolute" }],
28
+ ],
29
+ setupFilesAfterEnv: ["<rootDir>/src/setup.ts"],
30
+ };
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@ledgerhq/coin-module-framework",
3
+ "version": "0.1.9",
4
+ "description": "Ledger framework for Coin integration",
5
+ "keywords": [
6
+ "Ledger",
7
+ "LedgerWallet",
8
+ "Hardware Wallet"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/LedgerHQ/alpaca-coin-module"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/LedgerHQ/alpaca-coin-module/issues"
16
+ },
17
+ "homepage": "https://github.com/LedgerHQ/alpaca-coin-module/tree/develop/libs/coin-module-framework",
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "typesVersions": {
22
+ "*": {
23
+ "lib/*": [
24
+ "lib/*"
25
+ ],
26
+ "lib-es/*": [
27
+ "lib-es/*"
28
+ ],
29
+ "currencies": [
30
+ "lib/currencies/index"
31
+ ],
32
+ "*": [
33
+ "lib/*"
34
+ ]
35
+ }
36
+ },
37
+ "exports": {
38
+ "./lib/*": "./lib/*.js",
39
+ "./lib-es/*": "./lib-es/*.js",
40
+ "./currencies": {
41
+ "@ledgerhq/source": "./src/currencies/index.ts",
42
+ "require": "./lib/currencies/index.js",
43
+ "default": "./lib-es/currencies/index.js"
44
+ },
45
+ "./*": {
46
+ "@ledgerhq/source": "./src/*.ts",
47
+ "require": "./lib/*.js",
48
+ "default": "./lib-es/*.js"
49
+ },
50
+ "./package.json": "./package.json"
51
+ },
52
+ "license": "Apache-2.0",
53
+ "dependencies": {
54
+ "@ledgerhq/errors": "6.31.0-nightly.20260317030141",
55
+ "@ledgerhq/live-env": "2.30.0-nightly.20260317030141",
56
+ "@ledgerhq/live-currency-format": "0.6.0-nightly.20260317030141",
57
+ "@ledgerhq/types-cryptoassets": "7.35.0-nightly.20260317030141",
58
+ "bignumber.js": "^9.1.2"
59
+ },
60
+ "devDependencies": {
61
+ "@types/node": "^24.0.0",
62
+ "@types/jest": "^30.0.0",
63
+ "cross-env": "^7.0.3",
64
+ "jest": "^30.2.0",
65
+ "@swc/jest": "0.2.39",
66
+ "@swc/core": "1.15.11"
67
+ },
68
+ "scripts": {
69
+ "clean": "rm -rf lib lib-es",
70
+ "build": "tsc --project tsconfig.build.json && tsc --project tsconfig.build.json -m esnext --moduleResolution bundler --outDir lib-es",
71
+ "coverage": "jest --coverage",
72
+ "prewatch": "pnpm build",
73
+ "watch": "tsc --watch --project tsconfig.build.json",
74
+ "watch:es": "tsc --watch --project tsconfig.build.json -m esnext --moduleResolution bundler --outDir lib-es",
75
+ "lint": "eslint ./src --no-error-on-unmatched-pattern --ext .ts,.tsx --cache",
76
+ "lint:fix": "pnpm lint --fix",
77
+ "test": "cross-env TZ=America/Paris jest",
78
+ "test:debug": "cross-env TZ=America/Paris node --inspect-brk ./node_modules/jest/bin/jest.js --runInBand",
79
+ "unimported": "unimported",
80
+ "update:deps": "../../bin/update-coin-module-deps.sh"
81
+ }
82
+ }
@@ -0,0 +1,5 @@
1
+ export class IncorrectTypeError extends Error {
2
+ constructor(message?: string) {
3
+ super(`IncorrectType: ${message}`)
4
+ }
5
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Module related to all API exposition constraints and helper.
3
+ * One consumer of this API is Alpaca.
4
+ */
5
+
6
+ export * from './errors'
7
+ export * from './types'
@@ -0,0 +1,726 @@
1
+ // NOTE: from types-live
2
+ export type BroadcastConfig = {
3
+ mevProtected: boolean
4
+ sponsored?: boolean
5
+ source?: TransactionSource
6
+ }
7
+
8
+ /**
9
+ * TransactionSource identifies the origin of a transaction
10
+ */
11
+ export type TransactionSource = {
12
+ // Type of the transaction source
13
+ type: 'dApp' | 'live-app' | 'coin-module' | 'swap'
14
+ // Name/identifier of the source (e.g., manifestId, provider name)
15
+ name: string
16
+ }
17
+
18
+ export type BlockInfo = {
19
+ height: number
20
+ hash: string
21
+ // can be different from tx date
22
+ // transaction could be created at a particular moment, but depending on network conditions
23
+ // mining time, and block intervals, it might not get included in the blockchain until later
24
+ time: Date
25
+ parent?: ParentBlock
26
+ }
27
+
28
+ export type ParentBlock = Pick<BlockInfo, 'height' | 'hash'>
29
+
30
+ // NOTE: from crypto-asset
31
+ export type Unit = {
32
+ // display name of a given unit (example: satoshi)
33
+ name: string
34
+ // string to use when formatting the unit. like 'BTC' or 'USD'
35
+ code: string
36
+ // number of digits after the '.'
37
+ magnitude: number
38
+ // should it always print all digits even if they are 0 (usually: true for fiats, false for cryptos)
39
+ showAllDigits?: boolean
40
+ // true if the code should prefix amount when formatting
41
+ prefixCode?: boolean
42
+ }
43
+
44
+ export type AssetInfo =
45
+ | { type: 'native'; name?: string; unit?: Unit }
46
+ | {
47
+ type: string // token, coin, fungible_asset, trc10, trc20, erc20, erc721, erc1155, etc.
48
+ assetReference?: string // contract address (trc20), tokenId (trc10),, etc
49
+ assetOwner?: string // Address of the asset contract deployer/creator
50
+ name?: string // e.g., token name, or asset name
51
+ unit?: Unit
52
+ }
53
+
54
+ // NOTE: CoreOperation
55
+ export type Operation<MemoType extends Memo = MemoNotSupported> = {
56
+ id: string
57
+ type: string
58
+
59
+ senders: string[]
60
+ recipients: string[]
61
+
62
+ value: bigint
63
+ asset: AssetInfo
64
+
65
+ /**
66
+ * Optional memo associated with the operation.
67
+ * Use a `Memo` interface like `StringMemo<"text">`, `MapMemo<Kind, Value>`, or `MyMemo`.
68
+ * Defaults to `MemoNotSupported`.
69
+ */
70
+ memo?: MemoType
71
+
72
+ /**
73
+ * Arbitrary per-blockchain extra fields.
74
+ * This can include things like status, error messages, swap info, etc.
75
+ */
76
+ details?: Record<string, unknown>
77
+ tx: {
78
+ hash: string // transaction hash
79
+ block: BlockInfo // block metadata, empty string for block hash if not available directly and no reorg possible
80
+ fees: bigint // network fees paid
81
+ // address that paid for this transaction's fees.
82
+ // optional as it may be unknown (e.g. the information is not available in the context of the operation).
83
+ // if it is unknown, it's expected that none of the sender addresses paid for the fees
84
+ feesPayer?: string
85
+ date: Date // tx date (may differ from block time)
86
+
87
+ /** If the transaction has failed, fees have been paid but other balance changes are not effective.*/
88
+ failed: boolean
89
+ }
90
+ }
91
+
92
+ export type Transaction = {
93
+ type: string
94
+ recipient: string
95
+ amount: bigint
96
+ fee: bigint
97
+ } & Record<string, unknown> // Field containing dedicated value for each blockchain
98
+
99
+ /**
100
+ * A block along with its {@link BlockTransaction}, not specific to a particular account/address.
101
+ */
102
+ export type Block = {
103
+ /** The block metadata. */
104
+ info: BlockInfo
105
+
106
+ /**
107
+ * The block transactions.
108
+ *
109
+ * It should include at least all transactions where an EOA is involved, however it is OK to ignore other types of
110
+ * transactions that cannot cause balance changes (eg: validator vote transactions on Solana).
111
+ */
112
+ transactions: BlockTransaction[]
113
+ }
114
+
115
+ /**
116
+ * A transaction belonging to a {@link Block}, not specific to a particular account/address.
117
+ */
118
+ export type BlockTransaction = {
119
+ /** The transaction hash/digest (globally unique identifier). */
120
+ hash: string
121
+
122
+ /** If the transaction has been failed, fees have been paid but other balance changes are not effective.*/
123
+ failed: boolean
124
+
125
+ /**
126
+ * The operations/instructions included in this transaction.
127
+ *
128
+ * It should include at least all operations where an EOA is involved, however it is OK to ignore other types of
129
+ * operations that cannot cause balance changes (eg: validator vote instructions on Solana).
130
+ *
131
+ * Note that fees are accounted for separately, so operations must not represent fees.
132
+ */
133
+ operations: BlockOperation[]
134
+
135
+ /** Network specific details for this transaction. */
136
+ details?: Record<string, unknown>
137
+
138
+ /** The fee amount paid for this transaction, in base unit of the network native coin, always positive or zero. */
139
+ fees: bigint
140
+
141
+ /** The address that paid for this transaction's fees. */
142
+ feesPayer?: string
143
+ }
144
+
145
+ /** An operation belonging to a {@link BlockTransaction}. */
146
+ export type BlockOperation = TransferBlockOperation | OtherBlockOperation
147
+
148
+ /** An asset transfer that occurred in a {@link BlockTransaction}. */
149
+ export type TransferBlockOperation = {
150
+ /** Operation type discriminator. */
151
+ type: 'transfer'
152
+
153
+ /** The impacted address (can be sender or recipient based on signum of <code>amount</code>). */
154
+ address: string
155
+
156
+ /** The peer participant in the transfer (optional as it may be not known). */
157
+ peer?: string
158
+
159
+ /** The transferred asset. */
160
+ asset: AssetInfo
161
+
162
+ /**
163
+ * The signed amount of the transfer, i.e. impact of the transfer on <code>address</code> balance (positive for
164
+ * incoming, negative for outgoing).
165
+ */
166
+ amount: bigint
167
+ }
168
+
169
+ /**
170
+ * An unclassified type of operation that occurred in a {@link BlockTransaction}.
171
+ *
172
+ * Implementations are free to partially/completely omit this kind of operations.
173
+ */
174
+ export type OtherBlockOperation = {
175
+ type: 'other'
176
+ } & Record<string, unknown>
177
+
178
+ // Other coins take different parameters What do we want to do ?
179
+ export type Account = {
180
+ currencyName: string
181
+ address: string
182
+ balance: bigint
183
+ currencyUnit: Unit
184
+ spendableBalance: bigint // NOTE:: check if we can get rid of this one
185
+ }
186
+
187
+ /**
188
+ * A component of an account/address balance, for a single asset.
189
+ *
190
+ * @see AlpacaApi#getBalance
191
+ */
192
+ export type Balance = {
193
+ /** The balance value, in base unit of {@link asset} (always positive). */
194
+ value: bigint
195
+
196
+ /** The balance asset. */
197
+ asset: AssetInfo
198
+
199
+ /** The non-spendable part of {@link value} (eg: minimum balance requirement, or reserved for rent). */
200
+ locked?: bigint
201
+
202
+ /** The {@link Stake} this balance is part of, if any. */
203
+ stake?: Stake
204
+ }
205
+
206
+ /** The state of a {@link Stake}. */
207
+ export type StakeState =
208
+ | 'inactive' // stake has been created/funded, but not collecting any rewards for any reason
209
+ | 'activating' // stake has been created/funded, and will start collecting rewards on next "epoch" (protocol specific)
210
+ | 'active' // stake is initialized and collecting rewards
211
+ | 'deactivating' // stake has been deactivated, will be withdrawable/spendable yet on next "epoch" (protocol specific)
212
+
213
+ /**
214
+ * A staking position, for a single address/asset/state.
215
+ *
216
+ * Note that on blockchains that allow heterogeneous assets/states in a single account, a staking account is represented
217
+ * as several {@link Stake}.
218
+ *
219
+ * @see Reward
220
+ * @see AlpacaApi#getStakes
221
+ */
222
+ export type Stake = {
223
+ /** An immutable, globally unique id of the stake. Depending on the blockchain, it could simply be the account address,
224
+ * or a synthetic identifier. */
225
+ uid: string
226
+
227
+ /** The owning account address. Depending on the blockchain, it can be the staking account address or directly the
228
+ * main one. */
229
+ address: string
230
+
231
+ /** The validator/staking pool/delegate address. */
232
+ delegate?: string
233
+
234
+ /** The stake status, see {@link StakeState}. */
235
+ state: StakeState
236
+
237
+ /** UTC date of last state change. */
238
+ stateUpdatedAt?: Date
239
+
240
+ /** UTC date of initial stake creation. */
241
+ createdAt?: Date
242
+
243
+ /** The staked asset. */
244
+ asset: AssetInfo
245
+
246
+ /** The amount owned by the stake, in base unit of {@link asset} (deposits + rewards). */
247
+ amount: bigint
248
+
249
+ /** The part of {@link amount} that was deposited (<code>amount = amount_deposited + amount_rewarded</code>). */
250
+ amountDeposited?: bigint
251
+
252
+ /** The part of {@link amount} that was rewarded (<code>amount = amount_deposited + amount_rewarded</code>). */
253
+ amountRewarded?: bigint
254
+
255
+ /** A free form map of network specific fields. */
256
+ details?: Record<string, unknown>
257
+ }
258
+
259
+ /**
260
+ * A staking reward distribution event.
261
+ *
262
+ * @see Stake
263
+ * @see AlpacaApi#getRewards
264
+ */
265
+ export type Reward = {
266
+ /** {@link Stake#uid} via which this reward was obtained. */
267
+ stake: string
268
+
269
+ /** The reward asset. */
270
+ asset: AssetInfo
271
+
272
+ /** The reward amount. */
273
+ amount: bigint
274
+
275
+ /** UTC date at which reward was effectively credited to the account (not emitted). */
276
+ receivedAt: Date
277
+
278
+ /** If applicable, the transaction hash that distributed the reward. */
279
+ transactionHash?: string
280
+
281
+ /** A free form map of network specific fields. */
282
+ details?: Record<string, unknown>
283
+ }
284
+
285
+ /**
286
+ * Computational payload processed by Blockchains, such as
287
+ * calldata on EVM or instruction data on Solana
288
+ */
289
+ export interface TxData {
290
+ type: string
291
+ }
292
+
293
+ /**
294
+ * Default implementation when no computational payload is supported
295
+ * by the underlying Blockchain
296
+ */
297
+ export interface TxDataNotSupported extends TxData {
298
+ type: 'none'
299
+ }
300
+
301
+ /**
302
+ * Implementation with bufferized computational payload
303
+ */
304
+ export interface BufferTxData extends TxData {
305
+ type: 'buffer'
306
+ value: Buffer
307
+ }
308
+
309
+ export interface Memo {
310
+ type: string
311
+ }
312
+
313
+ // generic implementations that cover many coins (in coin-framework)
314
+ export interface MemoNotSupported extends Memo {
315
+ type: 'none'
316
+ }
317
+
318
+ // Specialized version, not extending the above
319
+ export interface StringMemo<Kind extends string = 'text'> extends Memo {
320
+ type: 'string'
321
+ kind: Kind
322
+ value: string
323
+ }
324
+
325
+ export interface MapMemo<Kind extends string, Value> extends Memo {
326
+ type: string
327
+ memos: Map<Kind, Value>
328
+ }
329
+
330
+ export interface TypedMapMemo<KindToValueMap extends Record<string, unknown>> extends Memo {
331
+ type: string
332
+ memos: Map<keyof KindToValueMap, KindToValueMap[keyof KindToValueMap]>
333
+ }
334
+
335
+ type MaybeMemo<MemoType extends Memo> = MemoType extends MemoNotSupported
336
+ ? object
337
+ : { memo: MemoType }
338
+
339
+ type MaybeTxData<TxDataType extends TxData> = TxDataType extends TxDataNotSupported
340
+ ? object
341
+ : { data: TxDataType }
342
+
343
+ export type FeesStrategy = 'slow' | 'medium' | 'fast' | 'custom'
344
+
345
+ export type StakingOperation = 'delegate' | 'undelegate' | 'redelegate'
346
+
347
+ export type TransactionIntent<
348
+ MemoType extends Memo = MemoNotSupported,
349
+ TxDataType extends TxData = TxDataNotSupported,
350
+ > = {
351
+ intentType: 'transaction' | 'staking'
352
+ type: string
353
+ sender: string
354
+ recipient: string
355
+ amount: bigint
356
+ asset: AssetInfo
357
+ useAllAmount?: boolean
358
+ feesStrategy?: FeesStrategy
359
+ senderPublicKey?: string
360
+ sequence?: bigint
361
+ expiration?: number
362
+ sponsored?: boolean
363
+ } & MaybeMemo<MemoType> &
364
+ MaybeTxData<TxDataType>
365
+
366
+ export type StakingTransactionIntent<
367
+ MemoType extends Memo = MemoNotSupported,
368
+ TxDataType extends TxData = TxDataNotSupported,
369
+ > = TransactionIntent & {
370
+ intentType: 'staking'
371
+ mode: StakingOperation
372
+ valAddress: string
373
+ dstValAddress?: string
374
+ } & MaybeMemo<MemoType> &
375
+ MaybeTxData<TxDataType>
376
+
377
+ export type SendTransactionIntent<
378
+ MemoType extends Memo = MemoNotSupported,
379
+ TxDataType extends TxData = TxDataNotSupported,
380
+ > = TransactionIntent & {
381
+ intentType: 'transaction'
382
+ } & MaybeMemo<MemoType> &
383
+ MaybeTxData<TxDataType>
384
+
385
+ export type TransactionValidation = {
386
+ errors: Record<string, Error>
387
+ warnings: Record<string, Error>
388
+ estimatedFees: bigint
389
+ totalFees?: bigint
390
+ amount: bigint
391
+ totalSpent: bigint
392
+ }
393
+
394
+ export type FeeEstimation = {
395
+ value: bigint
396
+ parameters?: Record<string, unknown>
397
+ }
398
+
399
+ /** Response of {@link AlpacaApi#craftTransaction}. */
400
+ export type CraftedTransaction = {
401
+ /** The serialized transaction (encoding is blockchain dependent). */
402
+ transaction: string
403
+ /** Blockchain specific details (eg: UTXOs referenced in the transaction). */
404
+ details?: Record<string, unknown>
405
+ }
406
+
407
+ /** A pagination cursor. */
408
+ export type Cursor = string
409
+
410
+ /** A paginated response. */
411
+ export type Page<T> = {
412
+ items: T[]
413
+ next?: Cursor | undefined
414
+ }
415
+
416
+ /** Options for {@link AlpacaApi#listOperations}. */
417
+ export type ListOperationsOptions = {
418
+ /**
419
+ * The minimum block height for which to fetch operations (inclusive).
420
+ *
421
+ * Implementation must raise a "not supported" error if `minHeight` is non-zero and not supported.
422
+ */
423
+ minHeight: number
424
+
425
+ /**
426
+ * A pagination cursor to resume listing.
427
+ *
428
+ * Implementation must guarantee the cursor is not volatile, i.e. it can be used long after the last request and still
429
+ * provide consistent results - for instance, a date or transaction hash.
430
+ */
431
+ cursor?: Cursor
432
+
433
+ /**
434
+ * The maximum number of operations to fetch (note this is a soft limit, the implementation may return less or more
435
+ * operations to not waste RPC calls).
436
+ *
437
+ * Implementation must raise a "not supported" error if limit is set and not supported.
438
+ */
439
+ limit?: number
440
+
441
+ /**
442
+ * The chronological order of the operations (within one page as well as globally when concatenating all pages).
443
+ *
444
+ * Implementation must raise a "not supported" error if order is set and not supported.
445
+ */
446
+ order?: 'asc' | 'desc'
447
+ }
448
+
449
+ /** A network validator */
450
+ export type Validator = {
451
+ /** Address of the validator. */
452
+ address: string
453
+
454
+ /** Human-readable name of the validator. */
455
+ name: string
456
+
457
+ /** Human-readable description of the validator. */
458
+ description?: string | undefined
459
+
460
+ /** URL of the entity running the validator. */
461
+ url?: string | undefined
462
+
463
+ /** URL of the logo for the validator. */
464
+ logo?: string | undefined
465
+
466
+ /** Amount of native asset in the pool (in base unit of chain native currency). */
467
+ balance?: bigint | undefined
468
+
469
+ /** Validator commission (a bigint serialized as a string). */
470
+ commissionRate?: string | undefined
471
+
472
+ /** Validator Annual Percentage Yield (floating point number between 0 and 1). */
473
+ apy?: number | undefined
474
+ }
475
+
476
+ export type AccountInfo = {
477
+ isNewAccount: boolean
478
+ balance: string
479
+ ownerCount: number
480
+ sequence: number
481
+ }
482
+
483
+ export type AddressValidationCurrencyParameters = {
484
+ currencyId: string
485
+ networkId: number
486
+ }
487
+
488
+ export type AlpacaApi<
489
+ MemoType extends Memo = MemoNotSupported,
490
+ TxDataType extends TxData = TxDataNotSupported,
491
+ > = {
492
+ // blockchain API
493
+
494
+ /**
495
+ * Get the latest block information from the network.
496
+ *
497
+ * This must return the same result as {@link getBlockInfo} for the latest block height.
498
+ *
499
+ * @returns the latest block metadata (height, hash, time)
500
+ * @see getBlockInfo
501
+ * @see getBlock
502
+ */
503
+ lastBlock: () => Promise<BlockInfo>
504
+
505
+ /**
506
+ * Get block information for a specific block height.
507
+ *
508
+ * This must return the same result as {@link lastBlock} for the latest block height.
509
+ *
510
+ * This API is optional and may not be supported on all networks: implementation should raise a "not supported" error
511
+ * in such case.
512
+ *
513
+ * @param height the block height to query
514
+ * @returns the block metadata (height, hash, time)
515
+ * @throws "not supported" if the blockchain does not support querying historical blocks
516
+ */
517
+ getBlockInfo: (height: number) => Promise<BlockInfo>
518
+
519
+ /**
520
+ * Get a full block with all its transactions.
521
+ *
522
+ * This returns the complete block data including all transactions and their operations.
523
+ *
524
+ * This must return the same block info as {@link getBlockInfo} for the same height.
525
+ *
526
+ * This API is optional and may not be supported on all networks: implementation should raise a "not supported" error
527
+ * in such case.
528
+ *
529
+ * @param height the block height to query
530
+ * @returns the complete block with transactions
531
+ * @throws "not supported" if the blockchain does not support querying full blocks
532
+ */
533
+ getBlock: (height: number) => Promise<Block>
534
+
535
+ /**
536
+ * Get the list of validators available on the network.
537
+ * @param cursor a pagination cursor to resume listing (the implementation must guarantee the cursor is not volatile,
538
+ * i.e. it can be used long after the last request and still provide consistent results - for instance,
539
+ * a date or transaction hash).
540
+ * The concrete implementation may return all validators in a single page when the underlying SDK
541
+ * does not provide cursor-based pagination.
542
+ */
543
+ getValidators: (cursor?: Cursor) => Promise<Page<Validator>>
544
+
545
+ // account read API
546
+
547
+ /**
548
+ * Get the balance(s) for an address/account.
549
+ *
550
+ * Returns all asset balances associated with the address, including the native asset and any tokens/sub-assets. Each
551
+ * balance includes the total value and optionally locked/staked amounts.
552
+ *
553
+ * If account is not found, implementation must return an empty balance.
554
+ *
555
+ * @param address the account address
556
+ * @returns an array of balances for all assets held by the address
557
+ * @see getStakes
558
+ * @see listOperations
559
+ */
560
+ getBalance: (address: string) => Promise<Balance[]>
561
+
562
+ /**
563
+ * List operations for an address/account.
564
+ *
565
+ * If account is not found, implementation must return an empty result.
566
+ *
567
+ * @param address the owner account address
568
+ * @param options see {@link ListOperationsOptions}
569
+ * @returns a page of operations
570
+ * @see getBalance
571
+ */
572
+ listOperations: (address: string, options: ListOperationsOptions) => Promise<Page<Operation>>
573
+
574
+ /**
575
+ * Get staking positions owned by an address/account.
576
+ *
577
+ * Results are returned in no particular order, in pages that can be of variable size. Page size is controlled by
578
+ * implementation and should minimize number of RPC calls (typically by aligning with SDK/RPC API pages).
579
+ *
580
+ * Results could include closed/deleted staking positions, this is implementation dependent.
581
+ *
582
+ * Since this API can make no sense/be complex to implement/require too many RPC calls on some blockchains, it is
583
+ * optional: implementation should raise a "not supported" error in such case.
584
+ *
585
+ * @param address the owner account address
586
+ * @param cursor a pagination cursor to resume listing (the implementation must guarantee the cursor is not volatile,
587
+ * i.e. it can be used long after the last request and still provide consistent results - for instance,
588
+ * a date or transaction hash)
589
+ * @see getBalance
590
+ * @see getRewards
591
+ */
592
+ getStakes: (address: string, cursor?: Cursor) => Promise<Page<Stake>>
593
+
594
+ /**
595
+ * Get staking reward distribution events since address/account inception.
596
+ *
597
+ * Results are returned in ascending chronological order, in pages that can be of variable size. Page size is
598
+ * controlled by implementation and should minimize number of RPC calls (typically by aligning with SDK/RPC API pages).
599
+ *
600
+ * Note that since staking implementations vary from one blockchain to another, some points are implementation dependent:
601
+ * - depending on the blockchain account model, the exact meaning of <code>address</code> can be:
602
+ * - a parent account => history will include all staking subaccounts
603
+ * - a single staking position/subaccount address
604
+ * - depending on the reward distribution mechanisms, reward events can be transactions, or blocks/epochs, or
605
+ * generated synthetically (eg: daily)
606
+ *
607
+ * Since this API can make no sense/be complex to implement/require too many RPC calls on some blockchains, it is
608
+ * optional: implementation should raise a "not supported" error in such case.
609
+ *
610
+ * @param address the account address (see doc, exact scope of the request is implementation dependent)
611
+ * @param cursor a pagination cursor to resume listing (the implementation must guarantee the cursor is not volatile,
612
+ * i.e. it can be used long after the last request and still provide consistent results - for instance,
613
+ * a date or transaction hash)
614
+ * @see getBalance
615
+ * @see getStakes
616
+ */
617
+ getRewards: (address: string, cursor?: Cursor) => Promise<Page<Reward>>
618
+
619
+ // transaction API
620
+
621
+ /**
622
+ * Craft an unsigned transaction from a transaction intent.
623
+ *
624
+ * The crafted transaction is ready to be signed by the hardware wallet and then combined
625
+ * with the signature using {@link combine}.
626
+ *
627
+ * @param transactionIntent the transaction intent describing what the user wants to do
628
+ * @param customFees optional custom fees to use instead of the default estimation
629
+ * @returns the crafted transaction with optional blockchain-specific details
630
+ * @see craftRawTransaction
631
+ */
632
+ craftTransaction: (
633
+ transactionIntent: TransactionIntent<MemoType, TxDataType>,
634
+ customFees?: FeeEstimation
635
+ ) => Promise<CraftedTransaction>
636
+
637
+ /**
638
+ * Craft an unsigned transaction from a raw/pre-built transaction.
639
+ *
640
+ * This is an alternative to {@link craftTransaction} for cases where the transaction
641
+ * is already built externally (e.g., by a dApp or smart contract interaction).
642
+ *
643
+ * @param transaction the raw transaction to wrap/prepare for signing
644
+ * @param sender the sender address
645
+ * @param publicKey the sender's public key
646
+ * @param sequence the account sequence/nonce (to prevent replay attacks)
647
+ * @returns the crafted transaction ready for signing
648
+ * @throws "not supported" if the blockchain does not support raw transaction crafting
649
+ * @see craftTransaction
650
+ */
651
+ craftRawTransaction: (
652
+ transaction: string,
653
+ sender: string,
654
+ publicKey: string,
655
+ sequence: bigint
656
+ ) => Promise<CraftedTransaction>
657
+
658
+ /**
659
+ * Estimate the fees for a transaction intent.
660
+ *
661
+ * The estimation should be based on current network conditions (e.g., gas price, fee rate).
662
+ *
663
+ * @param transactionIntent the transaction intent describing what the user wants to do
664
+ * @param customFeesParameters optional blockchain-specific parameters to customize the fee estimation
665
+ * (e.g., gas limit, priority fee)
666
+ * @returns the estimated fees and optional parameters that can be passed to {@link craftTransaction}
667
+ */
668
+ estimateFees: (
669
+ transactionIntent: TransactionIntent<MemoType, TxDataType>,
670
+ customFeesParameters?: FeeEstimation['parameters']
671
+ ) => Promise<FeeEstimation>
672
+
673
+ /**
674
+ * Combine a crafted transaction with a signature to produce a signed transaction ready for broadcast.
675
+ *
676
+ * @param tx the unsigned/crafted transaction (as returned by {@link craftTransaction})
677
+ * @param signature the signature produced by the hardware wallet
678
+ * @param pubkey the public key used to sign (required for some blockchains to verify/embed in the transaction)
679
+ * @returns the signed transaction ready for {@link broadcast}
680
+ */
681
+ combine: (tx: string, signature: string, pubkey?: string) => string | Promise<string>
682
+
683
+ /**
684
+ * Broadcast a signed transaction to the network.
685
+ *
686
+ * @param tx the signed transaction (encoding is blockchain dependent, typically hex-encoded)
687
+ * @param broadcastConfig optional configuration for the broadcast (e.g., retry settings)
688
+ * @returns the transaction hash/digest once successfully submitted to the network
689
+ * @throws if the transaction is rejected by the network (e.g., invalid signature, insufficient funds)
690
+ */
691
+ broadcast: (tx: string, broadcastConfig?: BroadcastConfig) => Promise<string>
692
+
693
+ /**
694
+ * Validate a transaction intent.
695
+ *
696
+ * @param transactionIntent the transaction intent describing what the user wants to do
697
+ * @param balances current balances of the intent sender
698
+ * @param customFees optional custom fees to use instead of the default estimation
699
+ * @returns additional values the intent has been validated with, and optional errors/warnings
700
+ */
701
+ validateIntent: (
702
+ transactionIntent: TransactionIntent<MemoType, TxDataType>,
703
+ balances: Balance[],
704
+ customFees?: FeeEstimation
705
+ ) => Promise<TransactionValidation>
706
+
707
+ /**
708
+ * Get the next sequence number (nonce) for an address
709
+ *
710
+ * @param address the account address
711
+ * @returns the next usable sequence number
712
+ */
713
+ getNextSequence: (address: string) => Promise<bigint>
714
+
715
+ /**
716
+ * Validate whether an address is well-formed for the blockchain.
717
+ *
718
+ * @param address the address to validate
719
+ * @param parameters currency-specific validation parameters
720
+ * @returns `true` if the address is valid, `false` otherwise
721
+ */
722
+ validateAddress: (
723
+ address: string,
724
+ parameters: Partial<AddressValidationCurrencyParameters>
725
+ ) => Promise<boolean>
726
+ }
package/src/config.ts ADDED
@@ -0,0 +1,81 @@
1
+ import { CryptoCurrency, CryptoCurrencyId } from '@ledgerhq/types-cryptoassets'
2
+ import { MissingCoinConfig } from './errors'
3
+ import type { FeatureConfig } from './features/types'
4
+
5
+ type ConfigStatus =
6
+ | {
7
+ type: 'active'
8
+ features?: FeatureConfig[]
9
+ }
10
+ | {
11
+ type: 'under_maintenance'
12
+ message?: string
13
+ }
14
+ | {
15
+ type: 'migration'
16
+ chain: CryptoCurrencyId
17
+ from: string
18
+ to: string
19
+ link: string
20
+ }
21
+ | {
22
+ type: 'feature_unavailable'
23
+ link: string
24
+ feature:
25
+ | 'history'
26
+ | 'swap'
27
+ | 'token_history'
28
+ | 'send_and_receive'
29
+ | 'send'
30
+ | 'receive'
31
+ | 'sending_tokens'
32
+ | 'receiving_tokens'
33
+ | 'staking'
34
+ | 'claiming_staking_rewards'
35
+ }
36
+ | {
37
+ type: 'will_be_deprecated'
38
+ deprecated_date: string
39
+ link: string
40
+ }
41
+ | {
42
+ type: 'deprecated'
43
+ }
44
+
45
+ type Banner = {
46
+ isDisplay: boolean
47
+ bannerText: string
48
+ bannerLink?: string
49
+ bannerLinkText?: string
50
+ }
51
+
52
+ export type CurrencyConfig = {
53
+ status: ConfigStatus
54
+ customBanner?: Banner
55
+ [key: string]: unknown
56
+ }
57
+
58
+ export type CoinConfig<T extends CurrencyConfig> = (currency?: CryptoCurrency) => T
59
+
60
+ function buildCoinConfig<T extends CurrencyConfig>() {
61
+ let coinConfig: CoinConfig<T> | undefined
62
+
63
+ const setCoinConfig = (config: CoinConfig<T>): void => {
64
+ coinConfig = config
65
+ }
66
+
67
+ const getCoinConfig = (currency?: CryptoCurrency): T => {
68
+ if (!coinConfig) {
69
+ throw new MissingCoinConfig()
70
+ }
71
+
72
+ return coinConfig(currency)
73
+ }
74
+
75
+ return {
76
+ setCoinConfig,
77
+ getCoinConfig,
78
+ }
79
+ }
80
+
81
+ export default buildCoinConfig
@@ -0,0 +1,6 @@
1
+ export {
2
+ formatCurrencyUnit,
3
+ formatCurrencyUnitFragment,
4
+ type formatCurrencyUnitOptions,
5
+ type FormatterValue,
6
+ } from '@ledgerhq/live-currency-format'
@@ -0,0 +1,7 @@
1
+ export { parseCurrencyUnit } from './parseCurrencyUnit'
2
+ export {
3
+ formatCurrencyUnit,
4
+ formatCurrencyUnitFragment,
5
+ type formatCurrencyUnitOptions,
6
+ type FormatterValue,
7
+ } from './formatCurrencyUnit'
@@ -0,0 +1 @@
1
+ export { parseCurrencyUnit } from '@ledgerhq/live-currency-format'
package/src/errors.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { createCustomErrorClass } from '@ledgerhq/errors'
2
+
3
+ /** When a coin-module has no CoinConfig setted */
4
+ export const MissingCoinConfig = createCustomErrorClass('MissingCoinConfig')
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Feature IDs as defined in https://ledgerhq.atlassian.net/wiki/spaces/CF/pages/6125551933/Coin+modules+-+ADR-003+-+Features+support
3
+ * These represent the capabilities/features a coin module can support
4
+ */
5
+ export type FeatureId =
6
+ // Blockchain transaction features
7
+ | 'blockchain_txs'
8
+ // Staking features
9
+ | 'staking_txs'
10
+
11
+ /**
12
+ * Intent types for blockchain_txs feature
13
+ */
14
+ export type BlockchainTxsIntent = 'send'
15
+
16
+ /**
17
+ * Intent types for staking_txs feature
18
+ */
19
+ export type StakingTxsIntent = 'delegate' | 'undelegate' | 'redelegate' | 'claimReward' | 'withdraw'
20
+
21
+ /**
22
+ * Mapping from feature ID to its supported intents
23
+ */
24
+ export type FeatureIntentMap = {
25
+ blockchain_txs: BlockchainTxsIntent
26
+ staking_txs: StakingTxsIntent
27
+ }
28
+
29
+ /**
30
+ * A supported feature declaration in a coin module
31
+ * Maps feature IDs to their supported intents
32
+ * Example: { "blockchain_txs": ["send"], "staking_txs": ["delegate", "claimReward"] }
33
+ */
34
+ export type SupportedFeatures = Partial<Record<FeatureId, string[]>>
35
+
36
+ /**
37
+ * Feature status in liveconfig
38
+ */
39
+ export type FeatureStatus = 'active' | 'inactive'
40
+
41
+ /**
42
+ * Feature configuration in liveconfig
43
+ */
44
+ export type FeatureConfig = {
45
+ id: FeatureId
46
+ status: FeatureStatus
47
+ }
48
+
49
+ /**
50
+ * Helper function to check if a feature is supported
51
+ */
52
+ export function hasFeature(supportedFeatures: SupportedFeatures, featureId: FeatureId): boolean {
53
+ return featureId in supportedFeatures
54
+ }
55
+
56
+ /**
57
+ * Helper function to check if an intent is supported for a feature
58
+ */
59
+ export function hasIntent(
60
+ supportedFeatures: SupportedFeatures,
61
+ featureId: FeatureId,
62
+ intent: string
63
+ ): boolean {
64
+ const intents = supportedFeatures[featureId]
65
+ return intents?.includes(intent) ?? false
66
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ import * as api from './api'
2
+
3
+ export default {
4
+ api,
5
+ }
package/src/setup.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { BigNumber } from 'bignumber.js'
2
+ import { getEnv } from '@ledgerhq/live-env'
3
+
4
+ BigNumber.set({ DECIMAL_PLACES: getEnv('BIG_NUMBER_DECIMAL_PLACES') })
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Recursively makes all properties optional.
3
+ * Arrays keep their element type (but each element is deeply partial).
4
+ * Primitives and built-in value types (Date, RegExp) are preserved as-is.
5
+ */
6
+ export type DeepPartial<T> = T extends (infer U)[]
7
+ ? DeepPartial<U>[]
8
+ : T extends Date | RegExp
9
+ ? T
10
+ : T extends object
11
+ ? { [K in keyof T]?: DeepPartial<T[K]> }
12
+ : T
13
+
14
+ /**
15
+ * Re-types a function so its return value is wrapped in `Promise<DeepPartial<…>>`,
16
+ * while keeping the original parameter types intact.
17
+ *
18
+ * Useful for typing Jest mock functions that return partial RPC/API data:
19
+ * ```ts
20
+ * const mock = jest.fn() as jest.MockedFunction<DeepPartialReturn<SomeApi["method"]>>;
21
+ * mock.mockResolvedValue({ onlyTheFieldsWeNeed: true });
22
+ * ```
23
+ */
24
+ export type DeepPartialReturn<F extends (...args: never[]) => unknown> = (
25
+ ...args: Parameters<F>
26
+ ) => Promise<DeepPartial<Awaited<ReturnType<F>>>>
@@ -0,0 +1,23 @@
1
+ import BigNumber from 'bignumber.js'
2
+ import { fromBigNumberToBigInt } from './utils'
3
+
4
+ describe('bigNumberToBigInt', () => {
5
+ it('should convert BigNumber to BigInt', () => {
6
+ const bigNumber = new BigNumber('42')
7
+ const result = fromBigNumberToBigInt(bigNumber)
8
+ expect(result).toEqual(BigInt('42'))
9
+ })
10
+
11
+ it('should return the default value if input is undefined', () => {
12
+ const value = undefined
13
+ const defaultValue = BigInt(12)
14
+ const result = fromBigNumberToBigInt(value, defaultValue)
15
+ expect(result).toEqual(defaultValue)
16
+ })
17
+ it('should convert to BigInt for very large numbers', () => {
18
+ const valueStr = '1234567890123456789012345678901234567890'
19
+ const valBignumber = new BigNumber(valueStr)
20
+ const valBigint = fromBigNumberToBigInt(valBignumber)
21
+ expect(valBigint).toEqual(BigInt(valueStr))
22
+ })
23
+ })
package/src/utils.ts ADDED
@@ -0,0 +1,20 @@
1
+ import BigNumber from 'bignumber.js'
2
+ import { SendTransactionIntent, StakingTransactionIntent, TransactionIntent } from './api'
3
+
4
+ export function fromBigNumberToBigInt<T>(
5
+ bigNumber: BigNumber | undefined,
6
+ defaultValue?: T
7
+ ): bigint | T {
8
+ if (bigNumber != null) {
9
+ return BigInt(bigNumber.toFixed())
10
+ }
11
+ return defaultValue as T
12
+ }
13
+
14
+ export function isSendTransactionIntent(tx: TransactionIntent): tx is SendTransactionIntent {
15
+ return tx.intentType === 'transaction'
16
+ }
17
+
18
+ export function isStakingTransactionIntent(tx: TransactionIntent): tx is StakingTransactionIntent {
19
+ return tx.intentType === 'staking'
20
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "customConditions": []
5
+ },
6
+ "exclude": [
7
+ "**/*.test.ts",
8
+ "**/*.test.tsx",
9
+ "**/*.spec.ts",
10
+ "**/*.spec.tsx",
11
+ "**/__tests__/**/*",
12
+ "**/tests/**/*",
13
+ "**/*.test.js",
14
+ "**/*.test.jsx",
15
+ "**/*.spec.js",
16
+ "**/*.spec.jsx"
17
+ ]
18
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "extends": "../tsconfig.base",
3
+ "compilerOptions": {
4
+ "declaration": true,
5
+ "declarationMap": true,
6
+ "downlevelIteration": true,
7
+ "lib": [
8
+ "es2020",
9
+ "dom"
10
+ ],
11
+ "types": [
12
+ "node",
13
+ "jest"
14
+ ],
15
+ "outDir": "lib",
16
+ "exactOptionalPropertyTypes": true,
17
+ "typeRoots": [
18
+ "./types",
19
+ "./node_modules/@types"
20
+ ]
21
+ },
22
+ "include": [
23
+ "src/**/*",
24
+ "types/*.d.ts"
25
+ ]
26
+ }