diff --git a/next.config.js b/next.config.js index 69707bfe..ab86032f 100644 --- a/next.config.js +++ b/next.config.js @@ -821,6 +821,11 @@ module.exports = { destination: '/docs/connectivity/ledgerJS/tutorials/tutorial-4-cosmos-webapp', permanent: true, }, + { + source: '/docs/clear-signing/eip7730', + destination: '/docs/clear-signing/erc7730', + permanent: true, + }, { source: '/docs/transport/clear-signing', destination: '/docs/clear-signing/eip712', @@ -1273,27 +1278,44 @@ module.exports = { }, { source: '/docs/token/faq', - destination: '/docs/tokens/integrating-tokens/faq', + destination: '/docs/tokens/faq', + permanent: true, + }, + + { + source: '/docs/tokens/integrating-tokens/faq', + destination: '/docs/tokens/faq', permanent: true, }, { source: '/docs/token/erc20-bep20', - destination: '/docs/tokens/integrating-tokens/evm-chains-tokens', + destination: '/docs/tokens/evm-chains-tokens', permanent: true, }, { source: '/docs/token/erc20', - destination: '/docs/tokens/integrating-tokens/evm-chains-tokens', + destination: '/docs/tokens/evm-chains-tokens', permanent: true, }, { source: '/docs/token/evm-chains-tokens', - destination: '/docs/tokens/integrating-tokens/evm-chains-tokens', + destination: '/docs/tokens/evm-chains-tokens', + permanent: true, + }, + + { + source: '/docs/tokens/integrating-tokens/evm-chains-tokens', + destination: '/docs/tokens/evm-chains-tokens', + permanent: true, + }, + { + source: '/docs/tokens/integrating-tokens/asa', + destination: '/docs/tokens/asa', permanent: true, }, { source: '/docs/token/asa', - destination: '/docs/tokens/integrating-tokens/asa', + destination: '/docs/tokens/asa', permanent: true, }, { @@ -1301,6 +1323,11 @@ module.exports = { destination: '/docs/tokens/integrating-tokens/trc', permanent: true, }, + { + source: '/docs/tokens/integrating-tokens/trcc', + destination: '/docs/tokens/trc', + permanent: true, + }, { source: '/docs/token/eip712-messages', destination: '/docs/clear-signing/nft', diff --git a/pages/docs/blockchain/coding.mdx b/pages/docs/blockchain/coding.mdx index 8a2fe799..22283a43 100644 --- a/pages/docs/blockchain/coding.mdx +++ b/pages/docs/blockchain/coding.mdx @@ -27,28 +27,35 @@ This time line shows the relative time of the steps (XS, S, M and L) you will go + <>![Coin Module](/blockchain/wallet-api.png) + + <>![Address derivation](/blockchain/address-derivation.png) <>![Add accounts: light sync](/blockchain/sync-light.png) <>![Add accounts: full sync](/blockchain/sync-full.png) <>![Send](/blockchain/send.png) diff --git a/pages/docs/blockchain/coding/_meta.json b/pages/docs/blockchain/coding/_meta.json index 3190c9ec..61a34bc1 100644 --- a/pages/docs/blockchain/coding/_meta.json +++ b/pages/docs/blockchain/coding/_meta.json @@ -7,5 +7,6 @@ "send": "6 - Send", "desktop-mobile": "7 - Ledger Live Desktop and Mobile", "wallet-api": "8 - Wallet API", + "create-module": "X - Create Coin Module", "bugs-troubleshooting": "Ledger Live common bugs" } diff --git a/pages/docs/blockchain/coding/address-derivation.mdx b/pages/docs/blockchain/coding/address-derivation.mdx index 1de0c1bd..3db9e619 100644 --- a/pages/docs/blockchain/coding/address-derivation.mdx +++ b/pages/docs/blockchain/coding/address-derivation.mdx @@ -4,13 +4,9 @@ title: Address derivation # 3 - Address derivation -From step 3 to step 6, you work will be implemented in the [Ledger Live repository](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/ledger-live-common). - ### Derive Address from device -Before starting this step, make sure [you have setup your environment](../setup-build) to work with `ledger-live`. - -First define the signer interface, which will reflect your [embedded app js binding](./js-bindings): +First define the signer interface in dedicated `libs/coin-module/coin-mycoin/src/types/signer.ts` file, which will reflect your [embedded app js binding](./js-bindings): ```ts copy export type MyCoinAddress = { @@ -30,7 +26,7 @@ export interface MyCoinSigner { Then get an address from the device for MyCoin, by creating the `hw-getAddress.ts` Resolver: -`libs/coin-mycoin/src/hw-getAddress.js`: +`libs/coin-module/coin-mycoin/src/signer/getAddress.ts`: ```ts copy import type { GetAddressFn } from "@ledgerhq/coin-framework/bridge/getAddressWrapper"; @@ -44,7 +40,7 @@ const resolver = ( return async (deviceId: string, { path, verify }: GetAddressOptions) => { const address = (await signerContext(deviceId, signer => signer.getAddress(path, verify), - )) as PolkadotAddress; + )) as MyCoinAddress; return { address: address.address, publicKey: address.pubKey, diff --git a/pages/docs/blockchain/coding/create-module.mdx b/pages/docs/blockchain/coding/create-module.mdx new file mode 100644 index 00000000..9c8e6b25 --- /dev/null +++ b/pages/docs/blockchain/coding/create-module.mdx @@ -0,0 +1,35 @@ +--- +title: "Add accounts: light sync" +description: An account represents a wallet where a user detains crypto assets for a given crypto currency. Ledger Live model is essentially an array of Account because many accounts can be created, even within a same crypto currency. +--- + +# 3 - Create Coin Module + +## Organization + +All CoinModules are located in [libs/coin-modules](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/coin-modules). + +A CoinModule has the following directories/modules: +- `api` (optional): interface for exposing the CoinModule to a backend service (cf. (TODO)) +- `bridge`: implementation of Bridges interface (cf. [Bridge](light-sync)) +- `logic`: contains all core logic of the blockchain. The code here is agnostic of all external interface (i.e. Bridge or Api) and relies only on external libs and `network` directory +- `network`: communication logic with explorer/index/node (cf. [How to wrap you api](light-sync#wrap-your-api)) +- `signer`: defines the interface definition to the Embedded App and the logic to retrieve [derive address](addrss-derivation) + +## Some architectural decision +For consistency among all the CoinModule, please consider the following principles: +- Always use an `index.ts` file in each directory. The goal is to "control" what is exposed from the module and allow to be used from outside. +- The `index.ts` in the root of the CoinModule should only expose: + - Bridge interface + - Bridge type + - bot test + - ... +- Inside CoinModule, the dependency between module is only one direction (avoid cyclic dependency or mutual ones). Example: From the `logic` module, it is forbidden to use some functions declared in `bridge`. + +Coin Module inner dependency + +## ... + +The following steps (3-A to 3-D), your work will have some impact on the [Ledger Live repository](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/ledger-live-common). + +Therefore, make sure [you have setup your environment](../setup-build) to work with `ledger-live`. diff --git a/pages/docs/blockchain/coding/desktop-mobile.mdx b/pages/docs/blockchain/coding/desktop-mobile.mdx index 1cafa35d..25b7790c 100755 --- a/pages/docs/blockchain/coding/desktop-mobile.mdx +++ b/pages/docs/blockchain/coding/desktop-mobile.mdx @@ -8,6 +8,88 @@ description: Adding your currency in Ledger Live Desktop and Mobile. 1. Fork [github.com/LedgerHQ/ledger-live](https://github.com/LedgerHQ/ledger-live) 2. Clone the repo +## Ledger Live Common + +Ledger Live Common is the repo where all the common logic and setup (between desktop and mobile) is done. + +Even if CoinModules are made with Ledger Live as first class citizen user, it is not the only Ledger product that relies on. +Therefore, some setup is required to allow LLD and LLM to use your CoinModule. + +For a dedicated CoinModule (i.e. MyCoin), all those setup files will be in `libs/ledger-live-common/src/families/mycoin` directory. + +### Setup + +The setup file (`libs/ledger-live-common/src/families/mycoin/setup.ts`) is responsible for the instanciation of the CoinModule. +It injects all required elements. + +Some helpers function exist in LLC (`libs/ledger-live-common/src/bridge/setup.ts`), to avoid too much boilerplate. + +```ts copy +import { + MyCoinAccount, + TransactionStatus, + createBridges, + type Transaction, +} from "@ledgerhq/coin-mycoin"; +import { getCryptoCurrencyById } from "@ledgerhq/cryptoassets/currencies"; +import Transport from "@ledgerhq/hw-transport"; +import MyCoin from "@ledgerhq/hw-app-mycoin"; +import type { Bridge } from "@ledgerhq/types-live"; +import { MyCoinCoinConfig } from "@ledgerhq/coin-mycoin/config"; +import myCoinResolver from "@ledgerhq/coin-mycoin/signer/index"; +import makeCliTools, { type CliTools } from "@ledgerhq/coin-mycoin/test/cli"; +import { createResolver, executeWithSigner, type CreateSigner } from "../../bridge/setup"; +import { Resolver } from "../../hw/getAddress/types"; +import { getCurrencyConfiguration } from "../../config"; + +const createSigner: CreateSigner = (transport: Transport) => { + return new MyCoin(transport); +}; + +const myCoin = getCryptoCurrencyById("myCoin"); +// Closure that will load dynamically the current config linked to this coin. The config may change during LLD and LLM lifecyle. +const getCurrencyConfig = (): MyCoinCoinConfig => getCurrencyConfiguration(myCoin); + +const bridge: Bridge = createBridges( + executeWithSigner(createSigner), + getCurrencyConfig, +); + +const resolver: Resolver = createResolver(createSigner, myCoinResolver); + +const cliTools: CliTools = makeCliTools(); + +export { bridge, cliTools, resolver }; + +``` + +### Config + +Ledger Live use a dynamic configuration for some parts of its logic. CoinModules use this tool to seamlessly update some key elements. + +The `status` element of the following object is mandatory. The other properties are depending on your needs in the CoinModule. +`libs/ledger-live-common/src/bridge/config.ts`: + +```ts copy +import { ConfigInfo } from "@ledgerhq/live-config/LiveConfig"; +import { getEnv } from "@ledgerhq/live-env"; + +export const myCoinConfig: Record = { + config_currency_mycoin: { + type: "object", + default: { + status: { + type: "active", + }, + node: { + url: getEnv("API_MYCOIN_NODE"), + }, + }, + }, +}; + +``` + ## Ledger Live Desktop ### Adding the crypto in LLD diff --git a/pages/docs/blockchain/coding/light-sync.mdx b/pages/docs/blockchain/coding/light-sync.mdx index 081afa4d..4e012c8b 100644 --- a/pages/docs/blockchain/coding/light-sync.mdx +++ b/pages/docs/blockchain/coding/light-sync.mdx @@ -21,11 +21,10 @@ More technically, an account is a view of the blockchain in the context of a spe Essentially what the user wants to see at the end is his balance, a historic graph, and a series of past operations that were performed. Moving from the blockchain to the concept of account is not necessarily trivial (in blockchains like Bitcoin, the concept of account does not exist – you don't create or destroy, this concept is a view, a lense, that we abstract for the user). -In Live Common, there are currently 3 types of accounts: +In Live Common, there are currently 2 types of accounts: - `Account` which is a top level account associated to a `CryptoCurrency` - the "main" account. - `TokenAccount` which is a nested level account, **inside** an Account and that is associated to a `TokenCurrency`. -- `ChildAccount` which is a variant of TokenAccount but more in context of a `CryptoCurrency` (typically used for Tezos) They are aggregated as a single `AccountLike` type, used across Ledger Live implementations. @@ -118,13 +117,13 @@ Although some fields are required, they can be emptied (recipent = "" and amount You should add any fields that would be required by MyCoin to be correctly broadcasted - respecting as much as possible its protocol's lexicon. -See existing implementations for inspiration: [Polkadot types](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/ledger-live-common/src/families/polkadot/types.ts), [Cosmos types](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/ledger-live-common/src/families/cosmos/types.ts) +See existing implementations for inspiration: [Polkadot types](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/coin-modules/coin-polkadot/src/types/bridge.ts), [Cosmos types](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/ledger-live-common/src/families/cosmos/types.ts) ### Family-specific types -You will be implementing typescript types that will be used in your integration, like the Transaction type or the additional data needed to be added to the Account shared type, but also any other types that you will need (remember to always type your functions with typescript). +You will be implementing types that will be used in your integration, like the Transaction type or the additional data needed to be added to the Account shared type, but also any other types that you will need (remember to always type your functions with typescript). -`libs/coin-modules/coin-mycoin/src/types.ts`: +`libs/coin-modules/coin-mycoin/src/types/bridge.ts`: ```ts copy import type { BigNumber } from "bignumber.js"; @@ -181,7 +180,7 @@ export type MyCoinPreloadData = { Since some of thoses types will be serialized when stored or cached, you may need to define serialize/deserialize functions for those: -`libs/coin-modules/coin-mycoin/src/serialization.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/serialization.ts`: ```ts copy import { BigNumber } from "bignumber.js"; @@ -204,7 +203,7 @@ export function fromMyCoinResourcesRaw(r: MyCoinResourcesRaw): MyCoinResources { } ``` -Because of Account being generic, you may need to add your specific resources to your `libs/coin-modules/coin-mycoin/src/types.ts` file, if you need to store specific information related to the blockchain (like staking, validators, or frozen balance, etc) that are not handle in Account. +Because of Account being generic, you may need to add your specific resources to your `libs/coin-modules/coin-mycoin/src/types/bridge.ts` file, if you need to store specific information related to the blockchain (like staking, validators, or frozen balance, etc) that are not handle in Account. ```ts // ... @@ -223,7 +222,7 @@ export type AccountRaw = { }; ``` -...and handle the associated serialization in `libs/coin-modules/coin-mycoin/src/serialization.ts` (if you use BigInt you will need to make it raw by changing it to string for example): +...and handle the associated serialization in `libs/coin-modules/coin-mycoin/src/bridge/serialization.ts` (if you use BigInt you will need to make it raw by changing it to string for example): ```ts copy import { Account, AccountRaw } from "@ledgerhq/types-live"; @@ -271,7 +270,7 @@ We also would like the `Operation` and `Account` to be displayed in CLI with the In this code sample, all references to operations are not needed at light sync step. They are currently necessary but will soon be removed from this section. -`libs/coin-modules/coin-mycoin/src/account.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/formatters.ts`: ```ts copy import type { Unit } from "@ledgerhq/types-cryptoassets"; @@ -376,7 +375,7 @@ The same idea applies also to the `Transaction` type which needs to be serialize This part may not be required if you don't need transactions for light sync. -`libs/coin-modules/coin-mycoin/src/transaction.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/transaction.ts`: ```ts copy import type { Account } from "@ledgerhq/types-live"; @@ -429,7 +428,7 @@ export const toTransactionRaw = (t: Transaction): TransactionRaw => { export default { formatTransaction, fromTransactionRaw, toTransactionRaw }; ``` -## Wrap your API +## Wrap your Node or Explorer In the code samples below, all references to operations are not needed at light sync step. They are currently necessary but will soon be removed from this section. @@ -443,19 +442,19 @@ The best way to implement your API in Live Common is to create a dedicated `api` ```plaintext ./libs/coin-modules/coin-mycoin/src -└── api +└── network ├── index.ts ├── sdk.ts - └── sdk.types.ts + └── types.ts ``` - Try to separate as much as possible your different APIs (if you use multiple providers) and use typings to ensure you map correctly API responses to Ledger Live types. + Try to separate as much as possible your different network calls (if you use multiple providers) and use typings to ensure you map correctly API responses to Ledger Live types. You will likely need to export thoses functions, but implemention is up-to-developer: -`libs/coin-modules/coin-mycoin/src/api/index.ts`: +`libs/coin-modules/coin-mycoin/src/network/index.ts`: ```ts copy export { @@ -470,7 +469,7 @@ export { Basically, in the next sections, `getAccount` will be called to create an `Account` with balances and any additional resources, and `getOperations` will be called to fill the `operations[]` of this account, with the whole history of operations that can be requested incrementally. Then `getFees` before sending a transaction to let the user know of the network cost (estimated or effective), and `submit` to broadcast its transaction after signing. -See [Polkadot Coin Integration's api](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/coin-polkadot/src/api) for good inspiration. +See [Polkadot Coin Integration's api](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/coin-modules/coin-polkadot/src/network) for good inspiration. ### API Example @@ -642,7 +641,7 @@ export const getOperations = async ( }); ``` -If you need to disconnect from your API after using it, update `src/api/index.ts` to add your api disconnect in the `disconnectAll` function, it will avoid tests and CLI to hang. +If you need to disconnect from your API after using it, update `src/network/index.ts` to add your api disconnect in the `disconnectAll` function, it will avoid tests and CLI to hang. ## Account Bridge @@ -655,24 +654,20 @@ It is designed for the end user frontend interface and is agnostic of the way it ### Receive The `receive` method allows to derivatives address of an account with a Ledger device but also display it on the device if verify is passed in. -As you may see in `coin-mycoin/src/bridge/js.ts`, Live Common provides a helper to implement it easily with `makeAccountBridgeReceive()`, and there are very few reasons to implement your own. +As you may see in `coin-mycoin/src/bridge/index.ts`, CoinFramework provides a helper to implement it easily with `makeAccountBridgeReceive()`, and there are very few reasons to implement your own. ### Synchronization -We usually group the `scanAccounts` and `sync` into the same file `js-synchronisation.ts` as they both use similar logic as a `getAccountShape` function passed to helpers. +We usually group the `scanAccounts` and `sync` into the same file `src/bridge/synchronisation.ts` as they both use similar logic as a `getAccountShape` function passed to helpers. -`libs/coin-modules/coin-mycoin/src/js-synchronisation.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/synchronisation.ts`: ```ts copy import type { Account } from "@ledgerhq/types-live"; -import type { GetAccountShape } from "@ledgerhq/coin-framework/bridge/jsHelpers"; -import { makeSync, makeScanAccounts, mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers"; +import { encodeAccountId } from "@ledgerhq/coin-framework/account/index"; +import { makeSync, makeScanAccounts, mergeOps, type GetAccountShape } from "@ledgerhq/coin-framework/bridge/jsHelpers"; -import { - encodeAccountId -} from "../../account"; - -import { getAccount, getOperations } from "./api"; +import { getAccount, getOperations } from "../network"; const getAccountShape: GetAccountShape = async (info) => { const { id, address, initialAccount } = info; @@ -736,17 +731,17 @@ Under the hood of the `makeSync` helper, the returned value is an Observable of - the updater is called in a reducer, and allows to produce an immutable state by applying the update to the latest account instance (with reconciliation on Ledger Live Desktop) - it's an observable, so we can interrupt it when/if multiple updates occurs -In some cases, you might need to do a `postSync` patch to add some update logic after sync (before the reconciliation that occurs on Ledger Live Desktop). If this `postSync` function is complex, you should split this function in a `src/families/mycoin/js-postSyncPatch.js` file. +In some cases, you might need to do a `postSync` patch to add some update logic after sync (before the reconciliation that occurs on Ledger Live Desktop). If this `postSync` function is complex, you should split this function in a `libs/coin-modules/coin-mycoin/src/bridge/postSyncPatch.js` file. ## Currency Bridge ### Scanning accounts -As we have seen [Synchronization](#synchronization), the `scanAccounts`, which is part of the CurrencyBridge, share common logic with the sync function, that's why we preferably put them in a `js-synchronisation.ts` file. +The `scanAccounts` function can be easily done directly in the `buildCurrencyBridge` function, by using `makeScanAccounts` helper. -The `makeScanAccounts` helper will automatically execute the default address derivation logic, but for some reason if you need to have a completely new way to scan account, you could then implement your own strategy. +This `makeScanAccounts` helper will automatically execute the default address derivation logic, but for some reason if you need to have a completely new way to scan account, you could then implement your own strategy. -## Icon +## Icon --> TO MOVE TO DESKTOP/MOBILE Icons are usually maintained by Ledger's design team, so you must first check that MyCoin icon is not already added in ledger-live-common, in [src/data/icons/svg](https://github.com/LedgerHQ/ledger-live-common/tree/master/src/data/icons/svg). It contains cleaned-up versions of Cryptocurrency Icons from [cryptoicons.co](http://cryptoicons.co/), organized by ticker. @@ -877,71 +872,92 @@ export default { currencyBridge, accountBridge }; You can now start to implement the JS bridge for MyCoin. It may need some changes back and forth between the types, your api wrapper, and the different files. -The skeleton of `libs/ledger-live-common/src/families/mycoin/bridge/js.ts` should look something like this: +The skeleton of `libs/coin-modules/coin-mycoin/src/bridge/index.ts` should look something like this: ```ts copy +import getAddressWrapper from "@ledgerhq/coin-framework/bridge/getAddressWrapper"; +import { + defaultUpdateTransaction, + makeAccountBridgeReceive, + makeScanAccounts, +} from "@ledgerhq/coin-framework/bridge/jsHelpers"; +import { CoinConfig } from "@ledgerhq/coin-framework/config"; +import { SignerContext } from "@ledgerhq/coin-framework/signer"; import type { AccountBridge, CurrencyBridge } from "@ledgerhq/types-live"; -import type { Transaction } from "../types"; -import { makeAccountBridgeReceive } from "@ledgerhq/coin-framework/bridge/jsHelpers"; - -import { getPreloadStrategy, preload, hydrate } from "../preload"; - -import { sync, scanAccounts } from "../js-synchronisation"; - -const receive = makeAccountBridgeReceive(); - -const currencyBridge: CurrencyBridge = { - getPreloadStrategy, - preload, - hydrate, - scanAccounts, -}; - -const createTransaction = () => { - throw new Error("createTransaction not implemented"); -}; +import myCoinConfig, { type MyCoinConfig } from "../config"; +import signerGetAddress from "../signer"; +import { MyCoinAccount, MyCoinSigner, TransactionStatus, type Transaction } from "../types"; +import { broadcast } from "./broadcast"; +import { createTransaction } from "./createTransaction"; +import { estimateMaxSpendable } from "./estimateMaxSpendable"; +import { getTransactionStatus } from "./getTransactionStatus"; +import { getPreloadStrategy, hydrate, preload } from "./preload"; +import { prepareTransaction } from "./prepareTransaction"; +import { + assignFromAccountRaw, + assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, +} from "./serialization"; +import { buildSignOperation } from "./signOperation"; +import { getAccountShape, sync } from "./synchronization"; -const prepareTransaction = () => { - throw new Error("prepareTransaction not implemented"); -}; +function buildCurrencyBridge(signerContext: SignerContext): CurrencyBridge { + const getAddress = signerGetAddress(signerContext); -const updateTransaction = () => { - throw new Error("updateTransaction not implemented"); -}; + const scanAccounts = makeScanAccounts({ + getAccountShape, + getAddressFn: getAddressWrapper(getAddress), + }); -const getTransactionStatus = () => { - throw new Error("getTransactionStatus not implemented"); -}; + return { + getPreloadStrategy, + preload, + hydrate, + scanAccounts, + }; +} -const estimateMaxSpendable = () => { - throw new Error("estimateMaxSpendable not implemented"); -}; +function buildAccountBridge( + signerContext: SignerContext, +): AccountBridge { + const getAddress = signerGetAddress(signerContext); -const signOperation = () => { - throw new Error("signOperation not implemented"); -}; + const receive = makeAccountBridgeReceive(getAddressWrapper(getAddress)); + const signOperation = buildSignOperation(signerContext); -const broadcast = () => { - throw new Error("broadcast not implemented"); -}; + return { + estimateMaxSpendable, + createTransaction, + updateTransaction: defaultUpdateTransaction, + getTransactionStatus, + prepareTransaction, + sync, + receive, + signOperation, + broadcast, + assignFromAccountRaw, + assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, + }; +} -const accountBridge: AccountBridge = { - estimateMaxSpendable, - createTransaction, - updateTransaction, - getTransactionStatus, - prepareTransaction, - sync, - receive, - signOperation, - broadcast, -}; +export function createBridges( + signerContext: SignerContext, + coinConfig: CoinConfig, +) { + myCoinConfig.setCoinConfig(coinConfig); -export default { currencyBridge, accountBridge }; + return { + currencyBridge: buildCurrencyBridge(signerContext), + accountBridge: buildAccountBridge(signerContext), + }; +} ``` - You could implement all the methods in a single file, but for better readability and maintainability, you should split your code into multiple files. + For better readability and maintainability, split your code into multiple files. @@ -951,7 +967,7 @@ It is important to keep in mind that all currencies work independently and that Hence, the more cryptocurrencies Ledger Live is using, the more requests and calculations are executed, which can take time. -To avoid making the same requests several times, we recommend using a local cache in your implementation (e.g. fees estimations, some currency data to preload, etc in a `libs/coin-modules/coin-mycoin/src/api/cache.ts` file. +To avoid making the same requests several times, we recommend using a local cache in your implementation (e.g. fees estimations, some currency data to preload, etc) in a `libs/coin-modules/coin-mycoin/src/network/cache.ts` file. We have a [`makeLRUCache`](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/coin-framework/src/cache.ts) function for creating Least-Recently-Used caches anywhere if needed. @@ -959,6 +975,10 @@ See for example the [Polkadot's cache usage](https://github.com/LedgerHQ/ledger- ## React Hooks + + CoinModules don't rely on any UI framework. They are UI-agnostic. Therefore, if you need to add React Hooks (or any UI specific behaviour), please add them in `libs/ledger-live-common/src/families/myCoin` directory. + + If you are adding specific features to Ledger Live (like staking), you may need to access data through React hooks, that could provide common logic reusable for React components. You are then free to add them in a `libs/ledger-live-common/src/families/mycoin/react.ts` file. diff --git a/pages/docs/blockchain/coding/send.mdx b/pages/docs/blockchain/coding/send.mdx index 7ffa1cb4..ac07317c 100644 --- a/pages/docs/blockchain/coding/send.mdx +++ b/pages/docs/blockchain/coding/send.mdx @@ -128,22 +128,16 @@ It is designed for the end user frontend interface and is agnostic of the way it ### Transactions -`Transaction` objects are created from a default state (`createTransaction`), that will then be updated according to the flow and inputs of the user. - -Everytime the transaction is updated through a patch (`updateTransaction`), its parameters will need to be validated to check if transaction can be signed and broadcasted (see [Validating Transactions](#validating-transactions)). +#### Create -In some cases, this transaction will need to be prepared to correctly check status (`prepareTransaction`), like fetching the network fees, transforming some parameters, or setting default values, ... +`Transaction` objects are created from a default state (`createTransaction`), that will then be updated according to the flow and inputs of the user. -`libs/coin-modules/coin-mycoin/src/js-transaction.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/createTransaction.ts`: ```ts copy import { BigNumber } from "bignumber.js"; import type { Account } from "@ledgerhq/types-live"; -import type { Transaction } from "./types"; - -import getEstimatedFees from "./js-getFeesForTransaction"; - -const sameFees = (a, b) => (!a || !b ? a === b : a.eq(b)); +import type { Transaction } from "../types"; /** * Create an empty transaction @@ -158,7 +152,16 @@ export const createTransaction = (): Transaction => ({ useAllAmount: false, fees: null, }); +``` + + +#### Update + +Everytime the transaction is updated through a patch (`updateTransaction`), its parameters will need to be validated to check if transaction can be signed and broadcasted (see [Validating Transactions](#validating-transactions)). + +`libs/coin-modules/coin-mycoin/src/bridge/updateTransaction.ts`: +```ts copy /** * Apply patch to transaction * @@ -169,6 +172,25 @@ export const updateTransaction = ( t: Transaction, patch: $Shape ) => ({ ...t, ...patch }); +``` + +if your update logic is similar as the above example, use directly the `defaultUpdateTransaction` from CoinFramework. + + +#### Prepare + +In some cases, this transaction will need to be prepared to correctly check status (`prepareTransaction`), like fetching the network fees, transforming some parameters, or setting default values, ... + +`libs/coin-modules/coin-mycoin/src/bridge/prepareTransaction.ts`: + +```ts copy +import { BigNumber } from "bignumber.js"; +import type { Account } from "@ledgerhq/types-live"; +import type { Transaction } from "../types"; + +import { estimateFees } from "./estimateFees"; + +const sameFees = (a, b) => (!a || !b ? a === b : a.eq(b)); /** * Prepare transaction before checking status @@ -179,7 +201,7 @@ export const updateTransaction = ( export const prepareTransaction = async (a: Account, t: Transaction) => { let fees = t.fees; - fees = await getEstimatedFees({ a, t }); + fees = await estimateFees({ a, t }); if (!sameFees(t.fees, fees)) { return { ...t, fees }; @@ -217,7 +239,7 @@ This validation is done everytime the user updates the transaction (any input ch `errors` and `warnings` are key - value (error) objects that would have for each input (in the user perspective) the eventual error or notice that has been detected. To each key then it is expected that an input in Ledger Live Desktop and Mobile will exists to display the error. -`libs/coin-modules/coin-mycoin/src/js-getTransactionStatus.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/getTransactionStatus.ts`: ```ts copy import { BigNumber } from "bignumber.js"; @@ -228,9 +250,8 @@ import { FeeNotLoaded, } from "@ledgerhq/errors"; import type { Account, TransactionStatus } from "@ledgerhq/types-live"; -import type { Transaction } from "./types"; - -import { isValidAddress, specificCheck } from "./logic"; +import type { Transaction } from "../types"; +import { isValidAddress, specificCheck } from "../logic"; import { MyCoinSpecificError } from "./errors"; const getTransactionStatus = async ( @@ -289,7 +310,7 @@ Those are user-dependent errors handled in the UI for each input displayed, and But some errors can occur in a different context, and not be caused by user. Try as much as possible to handle all failing cases and throw coin-specific errors (if not generic error already exist), that you may define in an `errors.ts` file (for reusablity). -`libs/coin-modules/coin-mycoin/src/errors.ts`: +`libs/coin-modules/coin-mycoin/src/types/errors.ts`: ```ts copy import { createCustomErrorClass } from "@ledgerhq/errors"; @@ -311,7 +332,7 @@ export * from "./families/mycoin/errors"; Also, to avoid repeating code and facilitate usage of checks and constants, gather all your coin-specific logic functions in a single file (calculations, getters, boolean checks...). This will also ease maintenance, for instance when the blockchain's logic changes (constants or additional checks added). -`libs/coin-mycoin/src/logic.ts`: +`libs/coin-modules/coin-mycoin/src/logic.ts`: ```ts copy import { BigNumber } from "bignumber.js"; @@ -362,12 +383,12 @@ export const getNonce = (a: Account): number => { The `Transaction` object is not exactly the transaction in the shape of the blockchain's protocol (which is generally serialized into a blob of bytes). So for convenience, you may implement a `buildTransaction` method to serialized it using MyCoin SDK, that could be reused for instance for estimating fees through the API. -`libs/coin-modules/coin-mycoin/src/js-buildTransaction.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/buildTransaction.ts`: ```ts copy -import type { Transaction } from "./types"; import type { Account } from "@ledgerhq/types-live"; +import type { Transaction } from "./types"; import { getNonce } from "./logic"; const getTransactionParams = (a: Account, t: Transaction) => { @@ -413,74 +434,34 @@ export const buildTransaction = async (a: Account, t: Transaction) => { }; ``` -This `buildTransaction` function would return an unsigned transaction blob that would be signed with the MyCoin App on device: +This `buildTransaction` function would return an unsigned transaction blob that would be signed with the MyCoin App on device. + +However, the signOperation function (example below), in charge to this signature process, will not have a direct access to this MyCoin. +Instead, you will need to use a `SignerContext` (more details in [setup chapter TODO](./TBD)). -`libs/coin-modules/coin-mycoin/src/js-signOperation.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/signOperation.ts`: ```ts copy import { BigNumber } from "bignumber.js"; import { Observable } from "rxjs"; +import { encodeOperationId } from "@ledgerhq/coin-framework/operation"; import { FeeNotLoaded } from "@ledgerhq/errors"; - -import type { Transaction } from "./types"; import type { Account, Operation, SignOperationEvent } from "@ledgerhq/types-live"; -import { withDevice } from "../../hw/deviceAccess"; -import { encodeOperationId } from "@ledgerhq/coin-framework/operation"; -import MyCoin from "./hw-app-mycoin/MyCoin"; +import type { Transaction } from "../types"; -import { buildTransaction } from "./js-buildTransaction"; +import { buildTransaction } from "./buildTransaction"; import { getNonce } from "./logic"; -const buildOptimisticOperation = ( - account: Account, - transaction: Transaction, - fee: BigNumber -): Operation => { - const type = "OUT"; - - const value = BigNumber(transaction.amount).plus(fee); - - const operation: $Exact = { - id: encodeOperationId(account.id, "", type), - hash: "", - type, - value, - fee, - blockHash: null, - blockHeight: null, - senders: [account.freshAddress], - recipients: [transaction.recipient].filter(Boolean), - accountId: account.id, - transactionSequenceNumber: getNonce(account), - date: new Date(), - extra: { additionalField: transaction.amount }, - }; - - return operation; -}; - -/** - * Adds signature to unsigned transaction. Will likely be a call to MyCoin SDK - */ -const signTx = (unsigned: string, signature: any) => { - return `${unsigned}:${signature}`; -}; - /** * Sign Transaction with Ledger hardware */ -const signOperation = ({ - account, - deviceId, - transaction, -}: { - account: Account, - deviceId: *, - transaction: Transaction, -}): Observable => - withDevice(deviceId)((transport) => - Observable.create((o) => { +export const buildSignOperation = + ( + signerContext: SignerContext, + ): AccountBridge["signOperation"] => + ({ account, deviceId, transaction }): Observable => + new Observable(o => { async function main() { o.next({ type: "device-signature-requested", @@ -493,10 +474,11 @@ const signOperation = ({ const unsigned = await buildTransaction(account, transaction); // Sign by device - const myCoin = new MyCoin(transport); - const r = await myCoin.signTransaction( - account.freshAddressPath, - unsigned + const r = await signerContext(deviceId, signer => + signer.signTransaction( + account.freshAddressPath, + unsigned + ); ); const signed = signTx(unsigned, r.signature); @@ -519,14 +501,11 @@ const signOperation = ({ }); } - main().then( - () => o.complete(), - (e) => o.error(e) - ); - }) -); - -export default signOperation; + main().then( + () => o.complete(), + (e) => o.error(e) + ); + }); ``` The `signOperation` function returns an Observable that will notify its subscriber when the user grated the signature. @@ -544,7 +523,7 @@ When signing a transaction, the user is shown on his device all the parameters o The list of all displayed fields on device are provided by the `getDeviceTransactionConfig` function, which must return all transaction fields for a given transaction. -`libs/coin-modules/coin-mycoin/src/deviceTransactionConfig.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/deviceTransactionConfig.ts`: ```ts copy import type { AccountLike, Account, TransactionStatus } from "@ledgerhq/types-live"; import type { Transaction } from "./types"; @@ -604,13 +583,13 @@ export default getDeviceTransactionConfig; Once the transaction is signed, it must be broadcasted to MyCoin network. This is pretty easy if you correctly wrapped your API. -`libs/coin-modules/coin-mycoin/src/js-broadcast.ts` +`libs/coin-modules/coin-mycoin/src/bridge/broadcast.ts` ```ts copy import type { Operation, SignedOperation } from "@ledgerhq/types-live"; import { patchOperationWithHash } from "../../operation"; -import { submit } from "./api"; +import { submit } from "../network"; /** * Broadcast the signed transaction @@ -642,25 +621,25 @@ The maximum spendable amount is the total balance in an account that is availabl See this [support article about Maximum Spendable Amount](https://support.ledger.com/hc/en-us/articles/360012960679-Maximum-spendable-amount). -`libs/coin-modules/coin-mycoin/src/js-estimateMaxSpendable.ts` +`libs/coin-modules/coin-mycoin/src/bridge/estimateMaxSpendable.ts` ```ts copy import { BigNumber } from "bignumber.js"; -import type { AccountLike, Account } from ".@ledgerhq/types-live"; import { getMainAccount } from "@ledgerhq/coin-framework/account/index"; +import type { AccountLike, Account } from "@ledgerhq/types-live"; -import type { Transaction } from "./types"; +import type { Transaction } from "../types"; -import { createTransaction } from "./js-transaction"; -import getEstimatedFees from "./js-getFeesForTransaction"; +import { createTransaction } from "./transaction"; +import getEstimatedFees from "./getFeesForTransaction"; /** * Returns the maximum possible amount for transaction * * @param {Object} param - the account, parentAccount and transaction */ -const estimateMaxSpendable = async ({ +export const estimateMaxSpendable = async ({ account, parentAccount, transaction, @@ -680,26 +659,25 @@ const estimateMaxSpendable = async ({ return a.spendableBalance.minus(fees); }; - -export default estimateMaxSpendable; ``` If it takes a long time for transactions to be confirmed on-chain and included in a sync, you might need to include the pending operations in this calculation. -Here, we only return the spendableBalance, but without the fees. Since Fee estimation can be used elsewhere (like in the `prepareTransaction`), you can put it's logic in a dedicated `js-getFeesForTransaction.ts` file. Here is an example for a fee fetched from network from an unsigned transaction (a bit like Polkadot), but you can also have specific calculation, with fee-per-byte value provided by the blockchain. +Here, we only return the spendableBalance, but without the fees. Since Fee estimation can be used elsewhere (like in the `prepareTransaction`), you can put it's logic in a dedicated `getFeesForTransaction.ts` file. +Here is an example for a fee fetched from network from an unsigned transaction (a bit like Polkadot), but you can also have specific calculation, with fee-per-byte value provided by the blockchain. -`libs/coin-modules/coin-mycoin/src/js-getFeesForTransaction.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/getFeesForTransaction.ts`: ```ts copy import { BigNumber } from "bignumber.js"; import type { Account } from "@ledgerhq/types-live"; -import type { Transaction } from "./types"; +import type { Transaction } from "../types"; +import { getFees } from "../network"; -import { getFees } from "./api"; -import { buildTransaction } from "./js-buildTransaction"; +import { buildTransaction } from "./buildTransaction"; /** * Fetch the transaction fees for a transaction @@ -707,7 +685,7 @@ import { buildTransaction } from "./js-buildTransaction"; * @param {Account} a * @param {Transaction} t */ -const getEstimatedFees = async ({ +export const getEstimatedFees = async ({ a, t, }: { @@ -719,15 +697,14 @@ const getEstimatedFees = async ({ return fees; }; - -export default getEstimatedFees; ``` ### Testing send with CLI -Before being able to test a `send` operation with CLI you will need to bind arguments and infer a transaction from it. Since we defined a "mode" field in the transaction, this will be the only argument that will be necessary to test a send. +Before being able to test a `send` operation with CLI you will need to bind arguments and infer a transaction from it. +Since we defined a "mode" field in the transaction, this will be the only argument that will be necessary to test a send. -`libs/coin-modules/coin-mycoin/src/cli-transaction.ts`: +`libs/coin-modules/coin-mycoin/src/test/cli.ts`: ```ts copy import flatMap from "lodash/flatMap"; @@ -768,7 +745,8 @@ export default { }; ``` -Of course if MyCoin has more complex transactions, you can add many arguments to CLI. You can also define you own cli commands for any specific data you would like to fetch. See [Polkadot CLI commands](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/coin-polkadot/src/cli-transaction.tss). +Of course if MyCoin has more complex transactions, you can add many arguments to CLI. You can also define you own cli commands for any specific data you would like to fetch. +See [Polkadot CLI commands](https://github.com/LedgerHQ/ledger-live/blob/develop/libs/coin-polkadot/src/cli-transaction.ts). Now you can try a `getTransactionStatus` or a `send`: diff --git a/pages/docs/blockchain/coding/sync.mdx b/pages/docs/blockchain/coding/sync.mdx index 634e5c4d..390bae85 100644 --- a/pages/docs/blockchain/coding/sync.mdx +++ b/pages/docs/blockchain/coding/sync.mdx @@ -23,24 +23,20 @@ It is designed for the end user frontend interface and is agnostic of the way it ### Receive The `receive` method allows to derivatives address of an account with a Ledger device but also display it on the device if verify is passed in. -As you may see in `libs/coin-modules/coin-mycoin/src/bridge/js.ts`, Live Common provides a helper to implement it easily with `makeAccountBridgeReceive()`, and there are a very few reasons to implement your own. +As you may see in `libs/coin-modules/coin-mycoin/src/bridge/index.ts`, CoinFramework provides a helper to implement it easily with `makeAccountBridgeReceive()`, and there are a very few reasons to implement your own. ### Synchronization -We usually group the `scanAccounts` and `sync` into the same file `js-synchronisation.ts` as they both use similar logic as a `getAccountShape` function passed to helpers. +We usually group the `scanAccounts` and `sync` into the same file `synchronisation.ts` as they both use similar logic as a `getAccountShape` function passed to helpers. -`libs/coin-modules/coin-mycoin/src/js-synchronisation.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/synchronisation.ts`: ```ts copy +import { encodeAccountId } from "@ledgerhq/coin-framework/account/index"; +import { makeSync, makeScanAccounts, mergeOps, type GetAccountShape } from "@ledgerhq/coin-framework/bridge/jsHelpers"; import type { Account } from "@ledgerhq/types-live"; -import type { GetAccountShape } from "@ledgerhq/coin-framework/bridge/jsHelpers"; -import { makeSync, makeScanAccounts, mergeOps } from "@ledgerhq/coin-framework/bridge/jsHelpers"; -import { - encodeAccountId -} from "@ledgerhq/coin-framework/account/index"; - -import { getAccount, getOperations } from "./api"; +import { getAccount, getOperations } from "../network"; const getAccountShape: GetAccountShape = async (info) => { const { id, address, initialAccount } = info; @@ -104,13 +100,14 @@ Under the hood of the `makeSync` helper, the returned value is an Observable of - the updater is called in a reducer, and allows to produce an immutable state by applying the update to the latest account instance (with reconciliation on Ledger Live Desktop) - it's an observable, so we can interrupt it when/if multiple updates occurs -In some cases, you might need to write a `postSync` patch to add update logic after sync (before the reconciliation that occurs on Ledger Live Desktop). If this `postSync` function is complex (eg, more than 200 lines of code), you should split it in a `libs/coin-modules/coin-mycoin/src/js-postSyncPatch.js` file. +In some cases, you might need to write a `postSync` patch to add update logic after sync (before the reconciliation that occurs on Ledger Live Desktop). +If this `postSync` function is complex (eg, more than 200 lines of code), you should split it in a `libs/coin-modules/coin-mycoin/src/bridge/postSyncPatch.ts` file. ## Currency Bridge ### Scanning accounts -As we have seen [Synchronization](#synchronization), the `scanAccounts`, which is part of the CurrencyBridge, share common logic with the sync function, that's why we preferably put them in a `js-synchronisation.ts` file. +As we have seen [Synchronization](#synchronization), the `scanAccounts`, which is part of the CurrencyBridge, share common logic with the sync function, that's why we preferably put them in a `synchronisation.ts` file. The `makeScanAccounts` helper will automatically execute the default address derivation logic, but for some reason if you need to have a completely new way to scan account, you could then implement your own strategy. @@ -125,14 +122,14 @@ This cache contains the JSON serialized response from `preload` which is then hy Live-Common features will then be able to reuse those data anywhere (e.g. validating transactions) with `getCurrentMyCoinPreloadData`, or by subscribing to `getMyCoinPreloadDataUpdates` observable. -`libs/coin-modules/coin-mycoin/src/preload.ts`: +`libs/coin-modules/coin-mycoin/src/bridge/preload.ts`: ```ts copy import { Observable, Subject } from "rxjs"; import { log } from "@ledgerhq/logs"; -import type { MyCoinPreloadData } from "./types"; -import { getPreloadedData } from "./api"; +import type { MyCoinPreloadData } from "../types"; +import { getPreloadedData } from "../network"; const PRELOAD_MAX_AGE = 30 * 60 * 1000; // 30 minutes @@ -307,72 +304,93 @@ export default { currencyBridge, accountBridge }; ## Split your code -You can now start to implement the JS bridge for MyCoin. It may need some changes back and forth between the types, your api wrapper, and the different files. +You can now start to implement the bridge for MyCoin. It may need some changes back and forth between the types, your api wrapper, and the different files. -The skeleton of `libs/coin-modules/coin-mycoin/src/bridge/js.ts` should look something like this: +The skeleton of `libs/coin-modules/coin-mycoin/src/bridge/index.ts` should look something like this: ```ts copy +import getAddressWrapper from "@ledgerhq/coin-framework/bridge/getAddressWrapper"; +import { + defaultUpdateTransaction, + makeAccountBridgeReceive, + makeScanAccounts, +} from "@ledgerhq/coin-framework/bridge/jsHelpers"; +import { CoinConfig } from "@ledgerhq/coin-framework/config"; +import { SignerContext } from "@ledgerhq/coin-framework/signer"; import type { AccountBridge, CurrencyBridge } from "@ledgerhq/types-live"; -import type { Transaction } from "../types"; -import { makeAccountBridgeReceive } from "@ledgerhq/coin-framework/bridge/jsHelpers"; - -import { getPreloadStrategy, preload, hydrate } from "../preload"; - -import { sync, scanAccounts } from "../js-synchronisation"; - -const receive = makeAccountBridgeReceive(); - -const currencyBridge: CurrencyBridge = { - getPreloadStrategy, - preload, - hydrate, - scanAccounts, -}; - -const createTransaction = () => { - throw new Error("createTransaction not implemented"); -}; - -const prepareTransaction = () => { - throw new Error("prepareTransaction not implemented"); -}; - -const updateTransaction = () => { - throw new Error("updateTransaction not implemented"); -}; +import myCoinConfig, { type MyCoinConfig } from "../config"; +import signerGetAddress from "../signer"; +import { MyCoinAccount, MyCoinSigner, TransactionStatus, type Transaction } from "../types"; +import { broadcast } from "./broadcast"; +import { createTransaction } from "./createTransaction"; +import { estimateMaxSpendable } from "./estimateMaxSpendable"; +import { getTransactionStatus } from "./getTransactionStatus"; +import { getPreloadStrategy, hydrate, preload } from "./preload"; +import { prepareTransaction } from "./prepareTransaction"; +import { + assignFromAccountRaw, + assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, +} from "./serialization"; +import { buildSignOperation } from "./signOperation"; +import { getAccountShape, sync } from "./synchronization"; + +function buildCurrencyBridge(signerContext: SignerContext): CurrencyBridge { + const getAddress = signerGetAddress(signerContext); + + const scanAccounts = makeScanAccounts({ + getAccountShape, + getAddressFn: getAddressWrapper(getAddress), + }); -const getTransactionStatus = () => { - throw new Error("getTransactionStatus not implemented"); -}; + return { + getPreloadStrategy, + preload, + hydrate, + scanAccounts, + }; +} -const estimateMaxSpendable = () => { - throw new Error("estimateMaxSpendable not implemented"); -}; +function buildAccountBridge( + signerContext: SignerContext, +): AccountBridge { + const getAddress = signerGetAddress(signerContext); -const signOperation = () => { - throw new Error("signOperation not implemented"); -}; + const receive = makeAccountBridgeReceive(getAddressWrapper(getAddress)); + const signOperation = buildSignOperation(signerContext); -const broadcast = () => { - throw new Error("broadcast not implemented"); -}; + return { + estimateMaxSpendable, + createTransaction, + updateTransaction: defaultUpdateTransaction, + getTransactionStatus, + prepareTransaction, + sync, + receive, + signOperation, + broadcast, + assignFromAccountRaw, + assignToAccountRaw, + fromOperationExtraRaw, + toOperationExtraRaw, + }; +} -const accountBridge: AccountBridge = { - estimateMaxSpendable, - createTransaction, - updateTransaction, - getTransactionStatus, - prepareTransaction, - sync, - receive, - signOperation, - broadcast, -}; +export function createBridges( + signerContext: SignerContext, + coinConfig: CoinConfig, +) { + myCoinConfig.setCoinConfig(coinConfig); -export default { currencyBridge, accountBridge }; + return { + currencyBridge: buildCurrencyBridge(signerContext), + accountBridge: buildAccountBridge(signerContext), + }; +} ``` - You could implement all the methods in a single file, but for better readability and maintainability, you should split your code into multiple files. + For better readability and maintainability, split your code into multiple files. diff --git a/pages/docs/blockchain/getting-started/general-architecture.mdx b/pages/docs/blockchain/getting-started/general-architecture.mdx index debb44bb..55c6c3bc 100755 --- a/pages/docs/blockchain/getting-started/general-architecture.mdx +++ b/pages/docs/blockchain/getting-started/general-architecture.mdx @@ -43,6 +43,10 @@ Its architecture is unconventional and may take some time to understand. Please The [Coin Framework](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/coin-framework) library is dedicated to coin integration. It provides utilities to help create Coin Modules with generic functions and a common interface signature. +#### Coin Modules + +The [Coin Modules] is a set of libraries. Each Coin Module is dedicated to one blockchain and implements the required interface (see [TBD]). + #### Live Common The Common library of Ledger Live is called [Ledger-live-common](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/ledger-live-common). It provides a single library compatible with both desktop and mobile version of Ledger Live. diff --git a/pages/docs/blockchain/setup-build.mdx b/pages/docs/blockchain/setup-build.mdx index 317a7530..25812fe3 100644 --- a/pages/docs/blockchain/setup-build.mdx +++ b/pages/docs/blockchain/setup-build.mdx @@ -48,48 +48,68 @@ All integrated coins are implemented in their own package in the `libs` director ## Structure -Your whole implementation of MyCoin must reside in a `mycoin` folder in `libs/ledger-live-common/src/families/` with the exception of some changes to apply in shared code. +Your whole implementation of MyCoin must reside in a `mycoin` folder in `libs/coin-modules/coin-mycoin`. +You will also need some code in `libs/ledger-live-common/src/families/mycoin`, for injection, setup and UI specific requirements. Here is a typical family folder structure (TS integration): ```plaintext copy ./libs -├── coin-mycoin/src +├── coin-modules/coin-mycoin/src │ ├── api -│ │ ├── cache.ts │ │ └── index.ts │ ├── bridge -│ │ └── js.ts -│ ├── account.ts -│ ├── bridge.integration.test.ts -│ ├── buildTransaction.ts -│ ├── cli-transaction.ts -│ ├── deviceTransactionConfig.ts -│ ├── errors.ts -│ ├── hw-getAddress.ts -│ ├── js-broadcast.ts -│ ├── js-createTransaction.ts -│ ├── js-estimateMaxSpendable.ts -│ ├── js-getFeesForTransaction.ts -│ ├── js-getTransactionStatus.ts -│ ├── js-prepareTransaction.ts -│ ├── js-signOperation.ts -│ ├── js-synchronization.ts -│ ├── logic.ts -│ ├── serialization.ts -│ ├── signer.ts -│ ├── specs.ts -│ ├── speculos-deviceActions.ts -│ ├── transaction.ts -│ └── types.ts +│ │ ├── broadcast.ts +│ │ ├── buildTransaction.ts +│ │ ├── createTransaction.ts +│ │ ├── estimateMaxSpendable.ts +│ │ ├── formatters.ts +│ │ ├── getFeesForTransaction.ts +│ │ ├── getTransactionStatus.ts +│ │ ├── prepareTransaction.ts +│ │ ├── signOperation.ts +│ │ ├── synchronization.ts +│ │ └── index.ts +│ ├── logic +│ │ ├── broadcast.ts +│ │ ├── craftTransaction.ts +│ │ ├── estimateFees.ts +│ │ ├── getBalance.ts +│ │ ├── lastBlock.ts +│ │ ├── listOperations.ts +│ │ ├── signTransaction.ts +│ │ └── index.ts +│ ├── network +│ │ ├── explorer.ts +│ │ └── index.ts +│ ├── signer +│ │ ├── getAddress.ts +│ │ └── index.ts +│ ├── test +│ │ ├── bot-deviceActions.ts +│ │ ├── bot-specs.ts +│ │ ├── bridgeDatasetTest.ts +│ │ ├── cli.ts +│ │ └── index.ts +│ ├── types +│ │ ├── bridge.ts +│ │ ├── errors.ts +│ │ ├── model.ts +│ │ ├── signer.ts +│ │ └── index.ts +│ ├── config.ts +│ └── index.ts ├── ledger-live-common/src/families/mycoin │ ├── bridge.integration.test.ts +│ ├── config.ts │ ├── logic.ts │ ├── react.ts │ ├── setup.ts -│ └── types.ts +│ ├── types.ts +│ └── walletApiAdapter.ts └── ledgerjs/packages/hw-app-mycoin/src - └── MyCoin.ts +│ ├── MyCoin.ts +│ └── index.ts ``` @@ -111,11 +131,11 @@ In the coin module, there is lockchain specific code, and how the signer needs t In live-common, there is the signer implementation, the logic shared between Ledger Live Desktop and mobile, and device app specific code. - * `src/logic.ts`: Lists coin specific business logic. * `src/react.ts`: Defines and lists coin specfic react hooks for UI. * `src/setup.ts`: Declares the signer in LLC. * `src/types.ts`: Lists coin specific types. + * `src/walletApiAdapter.ts`: TBD. #### LedgerJS All files related to communication with the device-app. diff --git a/pages/docs/clear-signing/_meta.json b/pages/docs/clear-signing/_meta.json index a4ca8161..b757eaba 100644 --- a/pages/docs/clear-signing/_meta.json +++ b/pages/docs/clear-signing/_meta.json @@ -1,5 +1,5 @@ { - "eip7730": "EIP-7730: the new Clear Signing initiative", + "erc7730": "ERC7730: the new Clear Signing initiative", "eip712": "EIP-712 messages", "nft": "ERC721, ERC1155 and ERC20" } \ No newline at end of file diff --git a/pages/docs/clear-signing/eip7730.mdx b/pages/docs/clear-signing/erc7730.mdx similarity index 65% rename from pages/docs/clear-signing/eip7730.mdx rename to pages/docs/clear-signing/erc7730.mdx index 7523052f..def83574 100644 --- a/pages/docs/clear-signing/eip7730.mdx +++ b/pages/docs/clear-signing/erc7730.mdx @@ -1,4 +1,6 @@ -# EIP-7730: the new Clear Signing initiative +import { Callout } from 'nextra/components' + +# ERC7730: the new Clear Signing initiative ## Prerequisite @@ -10,16 +12,24 @@ This implementation is currently for EVM chains only. Stay tuned for other proto The clear signing standard that we have developped relies on the whitelisting of smart contracts methods and can be done easily and quickly. -**The whitelisting of a smart contract method has two steps:** +#### Whitelisting process + +1. Submit a Json file to map your smart contract data into human-readable information to be displayed on Ledger devices. + - The specifications for this Json file are on [the official GitHub repository](https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/specs/erc-7730.md) + - The PR requirements are [on the Repository ReadMe](https://github.com/LedgerHQ/clear-signing-erc7730-registry). +2. Ledger reviews your PR + + + It is possible to submit your PRs but since the Generic Parser is not released yet, the transactions will not be clear signed. The Generic Parser serves as the middle-layer deployed on the Ledger infrastructure to read and parse the JSON files. + We recommend you submit the PR nonetheless so everything is ready when the generic parser is released. + -- A Json file to map your smart contract data into human-readable information to be displayed on Ledger devices. The specifications for this Json file are on [the official GitHub repository](https://github.com/LedgerHQ/clear-signing-erc7730-registry/blob/master/specs/erc-7730.md), -- A peer review of the files on the public Ledger repository. -Other items to take into account to ensure a full clear signing experience: +#### Special cases - For EIP-712 messages follow [this documentation](./eip712) - For for ERC721, ERC1155 and ERC20 follow [this documentation](./nft) -- [Add your token](..tokens/integrating-tokens) in Ledger Live. +- If applicable, [add your token](..tokens/integrating-tokens) in Ledger Live. - Some special cases may require the development of an [Ethereum plugin](../device-app/develop/code/plugin). The Ledger team will guide you through this process if required. @@ -27,7 +37,7 @@ Other items to take into account to ensure a full clear signing experience: All you have to do is use the latest version of the [LedgerJS Ethereum transport library](https://github.com/LedgerHQ/ledger-live/tree/b2163a6eaafd46390d4a14bad8e62fcc10454c05/libs/ledgerjs/packages/hw-app-eth). It has been updated for the new standard. For more information on how to use the LedgerJS libraries read [this documentation](./connectivity/ledgerJS). -### Watch the "Request for Comment Session" +## Watch the "Request for Comment Session" To get an insight about our process and plan for the future of clear signing, we recommend you watching the "Request for Comment Session". You can read [the slides](https://www.keepandshare.com/doc13/view.php?id=32202&da=y). @@ -45,5 +55,5 @@ To get an insight about our process and plan for the future of clear signing, we Stay tuned for further updates and evolutions on this topic as we continue to improve and expand our security measures, ensuring the highest protection for our users. -Want to know more? Join the conversation on [the Ethereum Magicians Forum](https://ethereum-magicians.org/t/eip-7730-proposal-for-a-clear-signing-standard-format-for-wallets/20403) and join [our Telegram group](https://t.me/+Saw4M8DRA_5mZTk0 ), dedicated to this subject. +Want to know more? Join the conversation on [the Ethereum Magicians Forum](https://ethereum-magicians.org/t/eip-7730-proposal-for-a-clear-signing-standard-format-for-wallets/20403). diff --git a/pages/docs/device-app/deliver/deliverables/icons.mdx b/pages/docs/device-app/deliver/deliverables/icons.mdx index 9ff1db75..7ac668b5 100644 --- a/pages/docs/device-app/deliver/deliverables/icons.mdx +++ b/pages/docs/device-app/deliver/deliverables/icons.mdx @@ -42,10 +42,10 @@ The requirement is to provide 2 mandatory icons that should be provided for Ledg *Ledger Nano S and X icon template (click to access Illustrator file)* -### Stax template +### Stax and Flex template -![Ledger Stax icon template](/device-app/stax-icons-template.png) -*Ledger Stax icon template (click right and save the file)* +![Ledger Stax and Flex icon template](/device-app/stax-flex-icons-template.png) +*Ledger Stax and Flex icon template (click right and save the file)* ## Design Warranty diff --git a/pages/docs/device-app/deliver/deliverables/security-audit.mdx b/pages/docs/device-app/deliver/deliverables/security-audit.mdx index 88f846b2..911084be 100644 --- a/pages/docs/device-app/deliver/deliverables/security-audit.mdx +++ b/pages/docs/device-app/deliver/deliverables/security-audit.mdx @@ -1,6 +1,6 @@ --- title: Security Audit -description: +description: --- import { Callout } from 'nextra/components' @@ -28,12 +28,12 @@ Provided your project fulfills the [conditions](#conditions), this is how an ext To go through an external security audit, ensure your project fulfills the following conditions: -- Your Device App works with all our devices (Ledger Nano S, S Plus, X and Stax) +- Your Device App works with all our devices (Ledger Nano S, S Plus, X, Stax and Flex) - Your Device App has been functionally validated by Ledger team
    -
  • - Do not start a security audit process if your Device App is not ready for all Ledger devices (Ledger Nano S, S Plus, X and Stax).
  • +
  • - Do not start a security audit process if your Device App is not ready for all Ledger devices (Ledger Nano S, S Plus, X, Stax and Flex).
  • - Your Device App must still be functional after the security audit
@@ -42,14 +42,14 @@ To go through an external security audit, ensure your project fulfills the follo Ledger has established and made public a detailed specification of what needs to be checked during a security audit following Ledger's standards. -When the word **must** is used, it indicates a requirement. -When the word **should** is used, it indicates a recommendation. It means that there are reasons not to follow the recommendation and it can be justified. +When the word **must** is used, it indicates a requirement. +When the word **should** is used, it indicates a recommendation. It means that there are reasons not to follow the recommendation and it can be justified. Do not hesistate to use the following specification as a checklist. ### Application privileges -- The application flags must abide by the principle of least privilege. +- The application flags must abide by the principle of least privilege. - When required, privileges must be justified. - The application derivation paths must be restricted to coin-specific BIP32 prefixes. - The application derivation paths must be declared in the Ledger app database. @@ -193,4 +193,4 @@ Do not hesistate to use the following specification as a checklist. - [ ] User supplied messages use a prefix and are validated before signature. - [ ] No cryptographic primitives are re-implemented if they already exist in BOLOS or the SDK. - [ ] The client is not able to freely manipulate the device keypairs. -- [ ] An authenticated encryption scheme is used for data being cached on the client. \ No newline at end of file +- [ ] An authenticated encryption scheme is used for data being cached on the client. diff --git a/pages/docs/device-app/deliver/submission-form.mdx b/pages/docs/device-app/deliver/submission-form.mdx index 18f0740d..d7cf9e13 100644 --- a/pages/docs/device-app/deliver/submission-form.mdx +++ b/pages/docs/device-app/deliver/submission-form.mdx @@ -14,9 +14,7 @@ Please make sure you followed this [process](./) and the coding requirements, an ### Ready to submit your App or Plugin for review? -Once you have finished your work and you have collected all the App or Plugin items you must enter them in: -- The Device App [form](https://ledger.typeform.com/app-submission) or -- The Plugin [form](https://ledger.typeform.com/to/YZZCHEB8) +Once you have finished your work and you have collected all the App or Plugin items you must enter them in [this form](https://tally.so/r/w8QKbr). Make sure you thoroughly complete the form to ensure a speedy release process. @@ -27,92 +25,55 @@ Make sure you thoroughly complete the form to ensure a speedy release process. ## App Form Checklist -This section is a summary outline of what is needed in the process od developing a device app. The list below is organised by category and some items are quite varied. +This section is a summary outline of what is needed in the process od developing a device app or a plugin. The list below is organised by category and some items are quite varied. Please make sure you have understood what is expected. You may use this list as a checklist. -#### 1 - General information +#### General information -- Device app name -- The name of your token as displayed in [CoinMarketCap](https://coinmarketcap.com/) +- App or Plugin Name +- The name of your token (if any) as displayed in [CoinMarketCap](https://coinmarketcap.com/) - Legal Entity name - Postal address - Email address +- The app compatibility with our devices (for device apps only, not for plugins) +- The service the DApp provide and the smart contract addresses (for plugins only) +#### Technical specifications for clones -#### 2 - Security +- The type of clone (Bitcoin, Ethereum) +- Link to the pull request -- I have met the [security requirements](../develop/requirements/security) -- The application went [through a security audit](./deliverables/security-audit) -- All vulnerabilities have been fixed +#### Technical specifications for other device apps +- If your app is a fork: the link to the repository's branch you have forked +- Link to your pull request (for updates) or repository (for new apps) +- If it is an update: explanation about the purpose of the pull request +- If it is a new app: explanation about the app features +- If your blockchain is not in Ledger Live yet and you modified the `Transfer` or `Verify address` flow: explanation about your modifications -#### 3 - Device app -- I have read the [coding tutorials](../develop/tutorials) -- Device app source code (GitHub repository) -- My app repository has a test folder that contains the Functional tests and Unit tests -- The App has been fully tested with a companion wallet (CLI or GUI) -- Two icons for the Ledger Stax, Nano and for "My Ledger" in Ledger Live, in PNG or GIF - (see [Icons](./deliverables/icons/)) -- [Video](./deliverables/ui-flow-video/#video) of your application running on the Ledger device (for Ledger Nano only) -- The App has a [Blind Signing](../develop/requirements/security#signing-settings) setting (if applicable) -- The [Guidelines_enforcer and Build_and_functional_tests workflow](../develop/requirements/development#workflows) succeeds - - -#### 4 - Companion Wallet - -- A link to the Companion Wallet The wallet must give an option to verify the receiving address on a Ledger device. It should also have an affiliate link next to the "Connect with Ledger" option. You must provide either: - - a link to the CLI repository, or - - a link to the GUI running on Windows/MacOS/Linux (mandatory for Public release) - -#### 5- Documentation +#### Security and requirements -- Document in the App's repository the list of the app's APDUs and status words -- Link to a Google doc tutorial about how to install and use your app (see [Third Party Applications Support](./deliverables/support)) - - The doc must include the link to the published tutorial hosted on third party website +- I certify that I have read the [coding guidelines](../develop/tutorials) and the [requirements](../develop/requirements) +- I certify that my app repository has a test folder that contains Unit Tests list and functional Tests (for device apps only) +- I certify that the App has been fully tested with a [companion wallet](./deliverables/companion-wallet) (for device apps only) +- All vulnerabilities have been fixed (for device apps only) +#### Deliverables -#### 7 - Marketing (Mandatory for Public release) - -- I have read the [marketing page](./deliverables/marketing) -- Marketing plan - - -#### 8 - Warranty and liability - -- I have read and agree with information laid out the [warranty and liability disclaimer](./deliverables/legal/) - -## Plugin Form Checklist - -#### 1 - General information - -- Device app name -- What service does the dApp provide -- The smart contracts addresses -- Legal Entity name -- Postal address -- Email address - -#### 3 - Security - -- I have met the [security requirements](../develop/requirements/security) -- The application went [through a security audit](./deliverables/security-audit) -- All vulnerabilities have been fixed - -#### 4 - Plugin - -- Plugin source code (GitHub repository) +- The [Guidelines_enforcer and Build_and_functional_tests workflow](../develop/requirements/development#workflows) succeeds - Two icons for the Ledger Stax, Nano and for "My Ledger" in Ledger Live, in PNG or GIF (see [Icons](./deliverables/icons/)) -- Link of the dApp working with the plugin - -#### 5 - Support +- [Video](./deliverables/ui-flow-video/#video) of your application running on the Ledger device (for device apps only, not for plugins) +- Link to the companion wallet (for device apps only, not for plugins) +- The Dapp working with the plugin (for plugins only) +- Document in the App's repository the list of the app's APDUs and status words (for device apps only, not for plugins) +- Link to a Google doc tutorial about how to install and use your app (see [Third Party Applications Support](./deliverables/support)). The doc must include the link to the published tutorial hosted on third party website. (for device apps only, not for plugins) +- Links to your ser application support (email address, Slack/Reddit/Telegram/Discord communities) +- Marketing plan (optional) (for device apps only, not for plugins) -- I have read the [support page](./deliverables/support) -- Main support contact (mail address, Slack/Reddit/Telegram/Discord communities) -#### 6 - Warranty and liability +#### Warranty and liability - I have read and agree with information laid out the [warranty and liability disclaimer](./deliverables/legal/) - diff --git a/pages/docs/device-app/develop/code/plugin.mdx b/pages/docs/device-app/develop/code/plugin.mdx index b555ca52..dd390075 100644 --- a/pages/docs/device-app/develop/code/plugin.mdx +++ b/pages/docs/device-app/develop/code/plugin.mdx @@ -3,91 +3,4 @@ import { Callout } from 'nextra/components' # Ethereum plugins - -## Overview - -Let’s start with a high-level overview of what a plugin is, how it interacts with the Ethereum app, and the steps required to write your plugin. - - - Even though this guide is relatively beginner-friendly, you need to have prior experience of C and Solidity development. - - - -### Why Plugins? - -If you’ve already interacted with any smart contract using a Ledger Device, then you’ve already seen this screen: - -![Blind Signing](/plugin/blind-signing.png) - -This is a UX disaster. The user has no guarantee of interacting with the right smart contract, nor of signing the correct data. The only user action is literally to blind-sign the transaction. - -Display information is specific to each smart contract: so when swapping on a decentralised exchange, you probably want to see information such as “Swapping X ETH for Y DAI”. When depositing DAI on Aave, you need to see the amount in DAI. So, the information is specific to the smart contract. - -![Plugin](/plugin/contract-data.png) - -Modifying the Ethereum App would not do because its size would quickly go out of control. Instead, the solution lies in a small and versatile parser of smart contract data, which works hand-in-hand with the Ethereum App and decides what to display for the best user experience. - -This is precisely what a plugin is: a small app that the user installs on their device to show just what needs to be signed. Since plugins are very small, users can install many without worrying about memory. - -Ledger designed and implemented Paraswap, the first Ethereum plugin. - - - - - The second mandatory requirement to obtain official support by Ledger for your dApp is using a plugin to verify transaction details on the Ledger. - - -### Example - -Plugins work hand-in-hand with the Ethereum App, and implementation is straightforward. The Ethereum App handles parsing, signing, screen display etc. The only thing your plugin needs to do is: - -1. Extract the relevant information from the data. -2. Send the string to be displayed back to the Ethereum App. - -Here is the detailed sequence diagram of the interaction between the Ethereum application and the plugin: - -```mermaid -sequenceDiagram - Note left of Eth App: Receive transaction - Eth App -> Plugin: ETH_PLUGIN_CHECK_PRESENCE - Plugin --> Eth App : - Note left of Eth App: Plugin exists
so initialize it - Eth App -> Plugin: ETH_PLUGIN_INITIALIZE - Note right of Plugin: Initialize context - Plugin --> Eth App: - Note left of Eth App: Split contract data
in chunks - Note left of Eth App: Send chunks
to plugin - Eth App -> Plugin: ETH_PLUGIN_PROVIDE_PARAMETER - Note right of Plugin: Parse the chunk - Plugin --> Eth App: - Note left of Eth App: Repeat call
for every chunk - Eth App -> Plugin: - Plugin --> Eth App: - Note left of Eth App: Finish parsing Tx - Eth App -> Plugin: ETH_PLUGIN_FINALIZE - Note right of Plugin: Note which ERC20
are needed - Plugin --> Eth App: Ask for info on ERC20 - Note left of Eth App: Get info on ERC20 - Note left of Eth App: Send it back
to plugin - Eth App -> Plugin: ETH_PLUGIN_PROVIDE_TOKEN - Note right of Plugin: Store ERC20 info - Plugin --> Eth App: - Eth App -> Plugin: ETH_PLUGIN_QUERY_CONTRACT_ID - Note right of Plugin: Prepare screen - Plugin --> Eth App: - Note left of Eth App: Display
contract ID screen - Eth App -> Plugin: ETH_PLUGIN_QUERY_CONTRACT_UI - Note right of Plugin: Prepare screen - Plugin --> Eth App: - Note left of Eth App: Repeat call
for every screen - Eth App -> Plugin: - Plugin --> Eth App: - Note left of Eth App: Sign/Reject
the transaction -``` - - -## This tutorial - -We will walk through the plugin Boilerplate example, which is a dApp Plugin for Ethereum. This will help you code your plugin. - -Remember, the plugin gets called repeatedly by the Ethereum Application and answers with the appropriate message status. +Follow [this documentation](https://ethereum-plugin-sdk.ledger.com/) to integrate your Ethereum Plugin. diff --git a/pages/docs/device-app/develop/code/plugin/_meta.json b/pages/docs/device-app/develop/code/plugin/_meta.json deleted file mode 100644 index 2f5f0d9d..00000000 --- a/pages/docs/device-app/develop/code/plugin/_meta.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "boilerplate-plugin": "Boilerplate Plugin", - "selectors": "Selector Setup", - "selectors-detailed": "Selector detailed", - "testing": "Testing" -} diff --git a/pages/docs/device-app/develop/code/plugin/boilerplate-plugin.mdx b/pages/docs/device-app/develop/code/plugin/boilerplate-plugin.mdx deleted file mode 100644 index d9ef72ef..00000000 --- a/pages/docs/device-app/develop/code/plugin/boilerplate-plugin.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Boilerplate plugin -description: A boilerplate plugin to help you write your own. This guide walks you through the plugin. ---- - -import { Callout } from 'nextra/components' - -# Setup - -This step is getting the plugin to compile. But first, we need to set up the working environment linked to the Ethereum app. - -## Clone the Ethereum App - -```sh -git clone --recurse-submodules https://github.com/LedgerHQ/app-ethereum -cd app-ethereum -``` - -## Build the Ethereum App - -You can easily build the Ethereum app by following these [instructions](../../quickstart). - -Once you've successfully compiled the Ethereum app you are ready to walk through the Boilerplate plugin example. - -# Boilerplate plugin - -Here is a boilerplate plugin to help you write your own. This guide walks you through the plugin. - -You can follow it best by forking the repository [app-plugin-boilerplate](https://github.com/LedgerHQ/app-plugin-boilerplate). - -```sh -cd .. -git clone --recurse-submodules https://github.com/LedgerHQ/app-plugin-boilerplate -``` - -## What's what - -There is no need to build it right now, only after adding tests. For now, in `app-plugin-boilerplate/` there are these folders: -- `ethereum-plugin-sdk`: This repository contains information shared by the Ethereum app and your plugin, such as structure definitions, utility functions, etc. -- `icons` and `glyphs` : plugin icons as displayed on the device. We will get to it later, but you can read about it [here](../../../deliver/deliverables/icons/#device-icon). -- `src`: the actual source code (in C). -- `tests`: the test folder (using the Ragger testing framework). -- `.github/workflows` : the CI workflows which are a requirement to submit your plugin. To have more details you can refer to this [documentation](https://github.com/LedgerHQ/ledger-app-workflows/). - - -## Change the plugin info - -There is the `Makefile` directly in the repository root. You need to open `Makefile` and change the plugin infos including `APPNAME` to suit your plugin name (e.g., Paraswap, 1inch, Lido), `APPVERSION` for plugin version and other various infos. \ No newline at end of file diff --git a/pages/docs/device-app/develop/code/plugin/selectors-detailed.mdx b/pages/docs/device-app/develop/code/plugin/selectors-detailed.mdx deleted file mode 100644 index e438419a..00000000 --- a/pages/docs/device-app/develop/code/plugin/selectors-detailed.mdx +++ /dev/null @@ -1,28 +0,0 @@ -import { Cards, Card } from 'nextra/components' - - - - - - - - - \ No newline at end of file diff --git a/pages/docs/device-app/develop/code/plugin/selectors-detailed/_meta.json b/pages/docs/device-app/develop/code/plugin/selectors-detailed/_meta.json deleted file mode 100644 index b14f1493..00000000 --- a/pages/docs/device-app/develop/code/plugin/selectors-detailed/_meta.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "init-contract": "Init Contract", - "provide-parameter": "Provide Parameter", - "finalize": "Finalize", - "provide-token": "Provide Token", - "query-contract-id": "Query Contract ID", - "query-contract-ui": "Query Contract UI" -} \ No newline at end of file diff --git a/pages/docs/device-app/develop/code/plugin/selectors-detailed/finalize.mdx b/pages/docs/device-app/develop/code/plugin/selectors-detailed/finalize.mdx deleted file mode 100644 index 8a97fa9b..00000000 --- a/pages/docs/device-app/develop/code/plugin/selectors-detailed/finalize.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: "Selectors detailed: finalize" ---- - -import { Callout } from 'nextra/components' - -# Finalize - -There are two easy steps: -1. Decide how many screens we need to display. -2. Tell the Eth App if more information is needed for a specific token. - -To address **1**, let's think about what we want to display. -As we are simply swapping tokens, the useful information is the amount of ETH sent, the amount of tokens received, and the beneficiary. -It looks something like this: -``` -| Swap | Receive Min | Beneficiary | -| ETH xx | USDT xx | 0x... | -``` - - -The Eth App adds screens to \"Accept\" or \"Reject\" the transaction. - - -So, the answer to **1** is 3. - -Now to answer **2**: we need information about the token the user receives. We already have the information for Ethereum: The ticker is "ETH", and the number of decimals is 18. - -The code you need to change in `handle_finalize` would look something like this: -```c copy -msg->numScreens = 2; -msg->tokenLookup1 = context->token_received; -``` - - - You can only request the information of two ERC20 tokens, using tokenLookup1 and tokenLookup2. There is no tokenLookup3. - - -This part is now done. The next is where the Ethereum App returns the requested information about the tokens. So let’s move on to `handle_provide_token`. - diff --git a/pages/docs/device-app/develop/code/plugin/selectors-detailed/init-contract.mdx b/pages/docs/device-app/develop/code/plugin/selectors-detailed/init-contract.mdx deleted file mode 100644 index 8f150aeb..00000000 --- a/pages/docs/device-app/develop/code/plugin/selectors-detailed/init-contract.mdx +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: "Selectors detailed: init contract" ---- - -import { Callout } from 'nextra/components' - -# Init Contract - -The first case condition is `handle_init_contract`. -In the corresponding `handle_init_contract.c`, `selectorIndex` is set as the first parameter to parse. Let's dive deeper into the parameters. - -Methods in a smart contract have parameters (or arguments). For example, `swapExactEthforTokens` has this format: - -```js copy -swapExactETHForTokens(uint256 amountOutMin, - address[] path, - address to, - uint256 deadline) -``` - -So, the parameters for this function are: -1. `uint256 amountOutMin`: is the minimum amount in tokens the user will get from their swap. -2. `address[] path`: is an array of paths. If we look at the contract source code on Etherscan, the only paths used are `path[0]` and `path[1]`, where `path[0]` is the OUTGOING token, and `path[1]` the INCOMING one. -3. `address to`: is the address to which the tokens are sent back. -4. `uint256 deadline`: is the swap deadline after which it will fail. - -These parameters are passed to your plugin in the same format order of the function. Parameters always have 32 bytes. - -The goal of this plugin is to display on the Ledger screen: the amount of ETH from the wallet, the return currency and address to which the tokens are sent. - -Hence, we need to parse `amountOutMin`, `path[1]`, and `to`. We don't care about `deadline` since we will not show this information. We also don't need to parse `path[0]` since we know we are sending `ETH`. - -Parsing these fields is done in the next call, `handle_provide_parameter.c`. We need to set the first parameter to parse, `amountOutMin`. - -```c -switch (context->selectorIndex) { - case SWAP_EXACT_ETH_FOR_TOKENS: - context->next_param = MIN_AMOUNT_RECEIVED; - break; - case BOILERPLATE_DUMMY_2: - context->next_param = TOKEN_RECEIVED; - break; - // Keep this - default: - PRINTF("Missing selectorIndex: %d\n", context->selectorIndex); - msg->result = ETH_PLUGIN_RESULT_ERROR; - return; -} -``` - - To trap errors in the plugin msg->result is set to ETH_PLUGIN_RESULT_ERROR - - -Now let's not forget to add this name to `parameter` in `boilerplate_plugin.h`. - -```c copy -typedef enum { - MIN_AMOUNT_RECEIVED = 0, // Min amount of tokens the user will get - TOKEN_RECEIVED, // Address of the tokens the user is swapping to - BENEFICIARY, // Address of the recipient of the tokens - // ... -} parameter; -``` - -Next, let's jump into `handle_provide_parameter.c`. - - - diff --git a/pages/docs/device-app/develop/code/plugin/selectors-detailed/provide-parameter.mdx b/pages/docs/device-app/develop/code/plugin/selectors-detailed/provide-parameter.mdx deleted file mode 100644 index 7af49ecb..00000000 --- a/pages/docs/device-app/develop/code/plugin/selectors-detailed/provide-parameter.mdx +++ /dev/null @@ -1,267 +0,0 @@ ---- -title: "Selectors detailed: provide parameter" ---- - -import { Callout } from 'nextra/components' - -# Provide Parameter - -The `handle_provider_parameter` function dispatches `msg` according to the value of `selectorIndex`. -Let's add our selector: - -```c copy -switch (context->selectorIndex) { - case SWAP_EXACT_ETH_FOR_TOKENS: - handle_swap_exact_eth_for_tokens(msg, context); - break; - case BOILERPLATE_DUMMY_2: - break; - default: - PRINTF("Selector Index %d not supported\n", context->selectorIndex); - msg->result = ETH_PLUGIN_RESULT_ERROR; - break; -} -``` -Now let's write `handle_swap_exact_eth_for_tokens()`. -Parsing these handlers is simple: just look at `next_param` to be parsed and copy the data to your context. - - - You will see context used frequently throughout the code. It is defined in the boilerplate_plugin.h header file.
- Since the execution toggles between the Ethereum App and the Plugin, the plugin needs somewhere to store the data (such as the contract address or the amount of tokens swapped) as it goes along. This is done in context. Make sure you change the definition of context in boilerplate_plugin.h to suit your plugin. -
- - - -Set `next_param` to be `MIN_AMOUNT_RECEIVED` in `handle_init_contract` because it is the first parameter to parse. Let's write the code to handle it. - -We want to store `amountOutMin` to be displayed later. This is what `context` is for: just copy `amountOutMin` to our context. Since `amountOutMin` is an amount, it is a `uint256` which are 32 bytes long. This is the size of `PARAMETER_LENGTH`. Use `copy_parameter`, which is designed for this purpose. - -```c copy -switch (context->next_param) { - case MIN_AMOUNT_RECEIVED: // amountOutMin - copy_parameter(context->amount_received, - sizeof(context->amount_received), - msg->parameter); - context->next_param = PATH_OFFSET; - break; -``` - - - -Notice we also need to set the next parameter to parse. We set it to `PATH_OFFSET`. Here is why. - - - Arrays (and structs) are special kinds of parameters. They are \"dynamic\", meaning their size is fixed because path has any number of elements. Dynamic parameters are parsed using an offset to the array, and by looking up the array size. - Let's work with an example. Here is the data for an actual swapExactEthForTokens - [0]: 0000000000000000000000000000000000000000000000000000000001ced03f - [1]: 0000000000000000000000000000000000000000000000000000000000000080 - [2]: 000000000000000000000000871732ce9ae62f065c6c787b600c1c44ceb873b8 - [3]: 00000000000000000000000000000000000000000000000000000000611e293d - [4]: 0000000000000000000000000000000000000000000000000000000000000002 - [5]: 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 - [6]: 000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7 - - As we said: -
    -
  • [0] is amountOutMin
  • -
  • [1] is the path offset (more later)
  • -
  • [2] is to
  • -
  • [3] is deadline
  • -
- - But what are [4], [5], [6]? - It is easy: [4] is the number of elements in the array, and [5] and [6] are the actual elements. Indeed, you can see [4] is 2 (because there are two elements in the array). path[0] is [5] and path[1] is [6]. - If we read the address at [5], we see the WETH token, and if we look for the address at [6], we get the USDT token ). Remember the initial transaction swapped ETH to USDT (via WETH), so it all makes sense. - And how do we know that [4] is the start of the array? This information is held in [1] (the offset). See, [1] contains 0x80 in hexadecimal or 128 in decimal. - - Since each parameter is 32 bytes long, let's have a look at the offsets of the data: -
    -
  • [0] is at offset 0
  • -
  • [1] is at offset 32
  • -
  • [2] is at 64
  • -
  • [3] at 96
  • -
  • [4] at 128
  • -
- There we have it. The array starts at 0x80, which is exactly the offset given by [1]. -
- - -With this in mind, let's continue writing the parser. - -But first let's add a parameter `offset` to our `context` (in `boilerplate_plugin.h`): -```c copy -typedef struct context_t { - // other code... - uint16_t offset; -} -``` - -We use `uint16_t` because we don't expect the offset to be bigger than 65535, but we could probably use `uint8_t`). Maybe your plugin needs `uint32_t` if you need to handle bigger offsets. - -Now let's add a case condition to store the offset: -```c copy -case PATH_OFFSET: - context->offset = U2BE(msg->parameter, - PARAMETER_LENGTH - sizeof(context->offset)); - context->next_param = BENEFICIARY; - break; -``` - - - We are using PATH_OFFSET even though it is not declared. We will add it at the end of the section, in the enum parameter declared in boilerplate_plugin.h. - - -We use `U2BE`, which stands for `Unsigned 2-Bytes Big Endian`. Indeed `offset` is a `uint16_t`, so it is two bytes long. We need it to parse the two last bytes, so we pass the offset `30` (`PARAMETER_LENGTH - 2`) to it. - -We have stored the offset in our context, but the next parameter to parse is `to` (which we named `BENEFICIARY`). -Let's write the case condition for `BENEFICIARY`: - -```c copy -// The `to` address is copied to `context->beneficiary`, using `copy_address()`. -// Indeed, since the beneficiary is an Ethereum address, we use `copy_address()` -// which only copies 20 bytes compared to `copy_parameter()` which -// would have copied 32 bytes. -case BENEFICIARY: - copy_address(context->beneficiary, - sizeof(context->beneficiary), - msg->parameter); - context->next_param = PATH_LENGTH; // See comments below - context->go_to_offset = true; // See comments below - break; -``` - -The next parameter to parse would be `deadline`, but we said earlier that we won't show this. Next, we have `context->offset`, which is where `PATH_LENGTH` is. One way to do this is to use a boolean `go_to_offset`, and write the function to: -1. Check if `go_to_offset` is true -2. If it is, check whether we have reached `context->offset`. -3. If we have not -> early return (so we continue parsing) -4. If we have, proceed to case condition. - -So first modify the `context` structure (`boilerplate_plugin.h`): -```c copy -typedef struct context { - // other code... - bool go_to_offset; -} -``` - -Then add a condition before the `switch` in the parsing code: -```c copy -// Hint: We add `SELECTOR_SIZE` because `msg->parameterOffset` also contains -// the `SELECTOR` (4 bytes ID of the method) data. -if (context->go_to_offset) { - if (msg->parameterOffset != context->offset + SELECTOR_SIZE) { - // We still haven't reached the offset... - return; - } - context->go_to_offset = false; -} -switch (context->next_param) { - // previous code... -} -``` - - - msg->parameterOffset is the offset of the transaction data. It is updated by the Ethereum App every time it parses data. It is useful for parsing. - - -The next case condition is for `PATH_LENGTH`: -```c copy -// We are now at the parameter `PATH_LENGTH` (`2` in our example). -// Remember we want to access `path[1]`, which is located two parameters -// away from the length. Hence we set `context->offset` to be the current -// offset (*minus SELECTOR_SIZE because it contains the selector*) -// plus two chunks (`PARAMETER_LENGTH * 2`). -case PATH_LENGTH: - context->offset = msg->parameterOffset - SELECTOR_SIZE - + PARAMETER_LENGTH * 2; - context->go_to_offset = true; - context->next_param = TOKEN_RECEIVED; -``` - - - We could calculate the offset in the case condition of PATH_OFFSET since we don't use the path length. However, see above to see how to get the path length. - - -And then we parse the last chunk of data which contains the token address the user receives: -```c copy -// We simply copy the address in `context->token_received`. -// Notice we set `context->next_param` to `UNEXPECTED_PARAMETER`. -// This is because we don't expect any other parameters. -// The default must set `msg->result` -// to `ETH_PLUGIN_RESULT_ERROR` to -// tell the Ethereum app if something goes wrong. -` -case TOKEN_RECEIVED: - copy_address(context->token_received, - sizeof(context->token_received), - msg->parameter); - context->next_param = UNEXPECTED_PARAMETER; - break; -default: - PRINTF("Unexpected parameter: %d\n", context->next_param); - msg->result = ETH_PLUGIN_RESULT_ERROR; - break -``` - -To summarize, the complete code looks something like this: -```c copy -static void handle_swap_exact_eth_for_tokens(ethPluginProvideParameter_t *msg, - context_t *context) { - if (context->go_to_offset) { - if (msg->parameterOffset != context->offset + SELECTOR_SIZE) { - return; - } - context->go_to_offset = false; - } - switch (context->next_param) { - case MIN_AMOUNT_RECEIVED: // amountOutMin - copy_parameter(context->amount_received, - sizeof(context->amount_received), - msg->parameter); - context->next_param = PATH_OFFSET; - break; - case PATH_OFFSET: // path - context->offset = U2BE(msg->parameter, PARAMETER_LENGTH - 2); - context->next_param = BENEFICIARY; - break; - case BENEFICIARY: // to - copy_address(context->beneficiary, - sizeof(context->beneficiary), - msg->parameter); - context->next_param = PATH_LENGTH; - context->go_to_offset = true; - break; - case PATH_LENGTH: - context->offset = msg->parameterOffset - SELECTOR_SIZE + PARAMETER_LENGTH * 2; - context->go_to_offset = true; - context->next_param = TOKEN_RECEIVED; - break; - case TOKEN_RECEIVED: // path[1] -> contract address of token received - copy_address(context->token_received, - sizeof(context->token_received), - msg->parameter); - context->next_param = UNEXPECTED_PARAMETER; - break; - default: - PRINTF("Param not supported: %d\n", context->next_param); - msg->result = ETH_PLUGIN_RESULT_ERROR; - break; - } -} -``` -We have added a few enums, so let's add them to the enum definition in `boilerplate_plugin.h`: -```c copy -typedef enum { - // other code... - PATH_OFFSET, - PATH_LENGTH, - UNEXPECTED_PARAMETER, -} parameter; -``` - -Congratulations, we have just written a parser for our first method! - -Now let's move on to `handle_finalize`. - - - diff --git a/pages/docs/device-app/develop/code/plugin/selectors-detailed/provide-token.mdx b/pages/docs/device-app/develop/code/plugin/selectors-detailed/provide-token.mdx deleted file mode 100644 index 36050f0f..00000000 --- a/pages/docs/device-app/develop/code/plugin/selectors-detailed/provide-token.mdx +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: "Selectors detailed: provide token" ---- - -# Provide Token - -If the Ethereum App finds information on the requested tokens, it will be in `msg->item1` and `msg->item2`. - -In our plugin, we need to account for when the Ethereum App does NOT find the token. - -Here is the code for our plugin, with comments: -```c copy - if (msg->item1) { - // The Ethereum App found the information for the requested token! - // Store its decimals. - context->decimals = msg->item1->decimals; - // Store its ticker. - strlcpy(context->ticker, - (char *) msg->item1->ticker, - sizeof(context->ticker)); - - // Keep track of the token found. - context->token_found = true; - } else { - // The Ethereum App did not manage to find - // the info for the requested token. - context->token_found = false; - - // If we wanted to add a screen, say a warning screen for example, - // then we tell the Ethereum app to add an additional screen - // by setting `msg->additionalScreens` here, just like so: - // msg->additionalScreens = 1; - } -``` - -The code is straightforward. Note that `msg->additionalScreens` tells the Ethereum App it needs X additional screens. - -The next step is about how to word your first screen (`query_contract_id`). \ No newline at end of file diff --git a/pages/docs/device-app/develop/code/plugin/selectors-detailed/query-contract-id.mdx b/pages/docs/device-app/develop/code/plugin/selectors-detailed/query-contract-id.mdx deleted file mode 100644 index aa535aac..00000000 --- a/pages/docs/device-app/develop/code/plugin/selectors-detailed/query-contract-id.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "Selectors detailed: query contract ID" ---- - -import { Callout } from 'nextra/components' - -# Query Contract ID - -The Ethereum App asks for information to be displayed. For the moment, it just wants an overview of this method (in our case, a swap). Let's look at what needs to be done. - -The Nanos devices screens have two lines. For example: -``` -| Ledger | <-- Upper line -| Rocks | <-- Bottom line -``` - - - For Stax devices it's possible to display more than 2 lines. The Ethereum application takes care of filling an entire screen before moving to the next one. As for the plugin, the functionality is the same for both Stax and Nanos devices. - - -In this call, the upper line is `msg->name`. It is the Plugin name. -The bottom line is `msg->version` (legacy name, etc.). It is the action context, for example "Stake", "Vote", "Buy", etc. In our example, the user is swapping, so we use "Swap". - -The boilerplate repository already contains code that copies `PLUGIN_NAME` to `msg->name`, so we only need to handle the `switch` case condition: -```c copy -switch (context->selectorIndex) { - case SWAP_EXACTH_ETH_FOR_TOKENS: - strlcpy(msg->version, "Swap", msg->versionLength); - break; -} -``` - -This results in the following screen: -``` -| Uniswap | -| Swap | -``` - -That's it! Now we can proceed to the last section. diff --git a/pages/docs/device-app/develop/code/plugin/selectors-detailed/query-contract-ui.mdx b/pages/docs/device-app/develop/code/plugin/selectors-detailed/query-contract-ui.mdx deleted file mode 100644 index f89e0ad3..00000000 --- a/pages/docs/device-app/develop/code/plugin/selectors-detailed/query-contract-ui.mdx +++ /dev/null @@ -1,157 +0,0 @@ ---- -title: "Selectors detailed: query contract UI" ---- - -# Query Contract UI - -The previous section showed how to display a single screen. This section shows how to display the rest of the screens. Similarly to the section before, the display itself is handled by the Ethereum App. The only thing the plugin needs to do is determine what strings need to be displayed. - -Each screen has a screen number (also called `screenIndex`). When the Ethereum App needs to display a screen, it just calls the plugin and gives it the `screenIndex`. The plugin fills `msg->title` and `msg->msg` accordingly. The Ethereum App then displays the strings. If the user presses the right button or the left button, the Ethereum App increments or decrements `screenIndex` and calls the plugin with the updated `screenIndex`. Here is the flow: - -```mermaid -sequenceDiagram - Eth App -> Plugin: We're on screen 1 - Note right of Plugin: Determine what
needs to be displayed - Plugin --> Eth App: - Note left of Eth App: User sees screen - Note left of Eth App: User presses
right button - Eth App -> Plugin: We're on screen 2 - Note right of Plugin: Determine what
needs to be displayed - Plugin --> Eth App: - Note left of Eth App: User sees screen - Note left of Eth App: User presses
*left* screen - Eth App -> Plugin: We're on-screen *1* - Note right of Plugin: Determine what
needs to be displayed - Plugin --> Eth App: - Note left of Eth App: etc -``` - -We will use `msg->title` to fill the upper line and `msg->msg` to fill the bottom one. -``` -| Ledger | <- msg->title -| Rocks | <- msg->msg -``` - -In our boilerplate example, this is what we want to display for a swap: - -``` - 0 1 2 <- screenIndex -| Send | Receive Min.| Beneficiary | -| ETH 0.1 | USDT 300.12 | 0x37abc... | -``` - -Let's dive right into `handle_query_contract_ui.c`: - -```c copy -// Switch on `screenIndex`, and call some (yet-to-be-written) function -// to set the UI accordingly. -switch (msg->screenIndex) { - case 0: - fn_status = set_send_ui(msg); - break; - case 1: - fn_status = set_receive_ui(msg, context); - break; - case 2: - fn_status = set_beneficiary_ui(msg, context); - break; - // Keep this - default: - PRINTF("Received an invalid screenIndex\n"); - msg->result = ETH_PLUGIN_RESULT_ERROR; - return; -} - - -if (!fn_status) { - msg->result = ETH_PLUGIN_RESULT_ERROR; -} -``` - -Let's start by writing `set_send_ui`. -```c copy -static bool set_send_ui(ethQueryContractUI_t *msg) { - // Copy the "Send" in the upper line. - strlcpy(msg->title, "Send", msg->titleLength); - - // The amount of ETH associated with this transaction is - // located in `msg->pluginSharedRO->txContent->value. - const uint8_t *eth_amount = msg->pluginSharedRO->txContent->value.value; - uint8_t eth_amount_size = msg->pluginSharedRO->txContent->value.length; - - // `amountToString` is a utility function that converts - // a `uin256_t` to a string. Also, `18` and `ETH` refer - // to the number of decimals and to the ticker respectively. - if (!amountToString(eth_amount, - eth_amount_size, - WEI_TO_ETHER, - "ETH", - msg->msg, - msg->msgLength)) { - return false; - } - - return true; -} -``` - -We can write a similar function for `set_receive_ui`: -```c copy -static bool set_receive_ui(ethQueryContractUI_t *msg, context_t *context) { - // Set the title - strlcpy(msg->title, "Receive Min.", msg->titleLength); - - uint8_t decimals = context->decimals; - const char *ticker = context->ticker; - // If the token look up failed, use the default network ticker along with the default decimals. - if (!context->token_found) { - decimals = WEI_TO_ETHER; - ticker = msg->network_ticker; - } - - // This time use amountToString with the data stored in our context! - if (!amountToString(context->amount_received, - sizeof(context->amount_received), - decimals, - ticker, - msg->msg, - msg->msgLength)) { - return false; - } - return true; -} -``` - -And finally, we need to write `set_beneficiary_ui`. Remember, this screen is only shown if the recipient's address doesn't match the user's address. This was done in `handle_plugin_finalize`, where we incremented `numScreens` if the addresses were different. - -```c copy -static bool set_beneficiary_ui(ethQueryContractUI_t *msg, context_t *context) { - // Set the upper line - strlcpy(msg->title, "Beneficiary", msg->titleLength); - - // Prefix the address with `0x`. - msg->msg[0] = '0'; - msg->msg[1] = 'x'; - - // We need a random chainID for legacy reasons with `getEthAddressStringFromBinary`. - // Setting it to `0` means it works with any chainID :) - uint64_t chainid = 0; - - // Get the string format of the address stored in `context->beneficiary`. Store it in - // `msg->msg`. - if (!getEthAddressStringFromBinary( - context->beneficiary, - msg->msg + 2, // +2 here because we've already prefixed with '0x'. - msg->pluginSharedRW->sha3, // Used by the function to calculate the hash - chainid)) { - return false; - } - - return true; -} -``` - -And that's it for your plugin. Now you have coded everything, it is time to test! - - - diff --git a/pages/docs/device-app/develop/code/plugin/selectors.mdx b/pages/docs/device-app/develop/code/plugin/selectors.mdx deleted file mode 100644 index 834e5568..00000000 --- a/pages/docs/device-app/develop/code/plugin/selectors.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: "Plugin development: Selector Setup" ---- - -# Selector Setup - -## contract.c - -Let’s go to the first `EDIT THIS` comment in `app-plugin-boilerplate/src/contract.c`. In this file you can list all the selectors for each contract. In this example we have two selectors but we focus only on the first one. - -The first selector (or methodID) to support is `SwapExactEthForToken` from Uniswap V2. -You can find it using Etherscan, and you can also compose it using the ABI (Application Binary Interface). - -By looking at [recent transactions on Uniswap](https://etherscan.io/txs?a=0x7a250d5630b4cf539739df2c5dacb4c659f2488d), we find a transaction with the "SwapExactEthForToken" method. Here is [an example](https://etherscan.io/tx/0x216bfa6fb8488901d168810cda1b716d1abcb002a87c3224180deaff00c950fc). Scroll down and click on "Click to see more", to read: - -![Uniswap](/plugin/uniswap.png) - -The header line shows `Method ID` to be `0x7ff36ab5`. -This is going to be the 4 bytes of `SWAP_EXACT_ETH_FOR_TOKENS_SELECTOR`: -```c -static const uint32_t SWAP_EXACT_ETH_FOR_TOKENS_SELECTOR = 0x7ff36ab5; -``` - -Just a few lines below, the selectors are grouped in an array, in which there is `SWAP_EXACT_ETH_FOR_TOKENS_SELECTOR`. - -You can also change the name of `BOILERPLATE_SELECTORS` throughout the project code. - -```c copy -// Array of all the different boilerplate selectors. -// Make sure this follows the same order as the -// enum defined in `boilerplate_plugin.h` -const uint32_t BOILERPLATE_SELECTORS[NUM_SELECTORS] = { - SWAP_EXACT_ETH_FOR_TOKENS_SELECTOR, - BOILERPLATE_DUMMY_SELECTOR_2, -}; -``` -## boilerplate_plugin.h - -Let's have a look at `app-plugin-boilerplate/src/boilerplate_plugin.h` mentioned in the code comments. - -Start by checking the value of `NUM_SELECTORS`. Indeed, for the moment we only have two selectors. -```c copy -#define NUM_SELECTORS 2 -``` - -Here `PLUGIN_NAME` is `Boilerplate`. Your plugin must be named appropriately here, and changed throughout the code - -```c copy -#define PLUGIN_NAME "Boilerplate" -``` - -Now let's look for the selectors mentioned in the comments. Since it is an enum let's change it in our selector: -```c copy -typedef enum { - SWAP_EXACT_ETH_FOR_TOKENS, - BOILERPLATE_DUMMY_2, -} selector_t; -``` - -## handlers - -Now we have to implement a handler for each case condition. They must all have a corresponding `handle_*.c` located in `src/`. - diff --git a/pages/docs/device-app/develop/code/plugin/testing.mdx b/pages/docs/device-app/develop/code/plugin/testing.mdx deleted file mode 100644 index 9a75ae09..00000000 --- a/pages/docs/device-app/develop/code/plugin/testing.mdx +++ /dev/null @@ -1,29 +0,0 @@ ---- -title: Testing -description: Test your plugin app with the Ragger testing framework. ---- - -# Testing - -Testing a plugin is very similar to that of a standalone app, with the [Ragger framework](https://github.com/LedgerHQ/ragger/) and [Speculos](https://github.com/LedgerHQ/speculos/). There are however a few differences : - -## Ethereum Ragger client - -It is a [Python module](https://pypi.org/project/ledger-app-clients.ethereum/) that abstracts the APDU crafting and response parsing. It comes from the Ethereum app itself and is what's used to test it in its CI but also has everything needed for testing plugins. -It also comes with test signing keys so that APDUs that are signed by either the [CAL](https://github.com/LedgerHQ/ledger-live/tree/develop/libs/ledgerjs/packages/cryptoassets) or a Ledger backend in production can be signed during the test and verified by the Ethereum app. - -## ABIs - -The Ethereum Ragger client relies on [Web3.py](https://pypi.org/project/web3/) for transaction crafting, it can also leverage a smart contract's ABI (Application Binary Interface) which is obtainable from a website like [Etherscan](https://etherscan.io/). It makes it really easy to craft a function call which greatly helps tests readability. - -## Signature verification - -The Ragger client can also easily verify a transaction's signature by [recovering](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm#Public_key_recovery) the Ethereum address of the wallet that signed the transaction. -Testing the UI is crucial but so is testing the signatures we produce. - -## Ethereum app dependency - -The plugins rely on the [Ethereum app](https://github.com/LedgerHQ/app-ethereum) so in order to run the tests the application needs to be compiled and placed in the [required folder](https://github.com/LedgerHQ/app-plugin-boilerplate/tree/develop/tests/.test_dependencies/ethereum). - -The easiest way and what we recommend is using Visual Studio Code with the official [Ledger -Extension](https://marketplace.visualstudio.com/items?itemName=LedgerHQ.ledger-dev-tools) which takes care of this automatically. diff --git a/pages/docs/device-app/develop/ui/ledger-stax-flex-display/transactions.mdx b/pages/docs/device-app/develop/ui/ledger-stax-flex-display/transactions.mdx index d0937e2a..7add4b4c 100644 --- a/pages/docs/device-app/develop/ui/ledger-stax-flex-display/transactions.mdx +++ b/pages/docs/device-app/develop/ui/ledger-stax-flex-display/transactions.mdx @@ -183,30 +183,31 @@ To help your users navigate longer or more complex transactions, you might want ## Blind signing -When a transaction cannot be fully decoded, a standard “blind signing warning sequence” must be presented to users. This sequence informs them about the risk they’re facing -- First, a generic warning message lets them “go back to safety” as the primary action. Your app should not customize this screen and its wording, unless strictly necessary -- Then, a detailed explanation of the risk is presented to users, with redirections to learn more about it. Your app should customize this detailed view, as it is likely dependent on your specific use cases and networks -- On the transaction intent and signature pages, a warning icon is introduced. When tapped, it brings back the detailed risk review as a modal. +When a transaction cannot be fully decoded, a standard “blind signing warning sequence” must be presented to users. It should be accompanied by a dedicated setting that the user must enable in order to sign non-decoded transactions or messages. -The example below shows a blind signing sequence on Ledger Stax, where the Ethereum app presents the approval of an unknown ERC-20 token. +- The “blind signing” setting should be off by default. When that’s the case, the user is presented with a specific warning when trying to sign an non-decoded transaction. +- After enabling the setting, users must still be warned of every blind transaction. +- On the transaction intent and signature pages, a warning icon is introduced. When tapped, it brings back the detailed risk review as a modal. +The example below shows a blind signing sequence on Ledger Stax, where the Ethereum app presents the approval of an unknown ERC-20 token, and after the blind signing setting was enabled: ![Stax Blind Signing](/device-app/stax-blind-signing-01.png) |:--:| ![Stax Blind Signing](/device-app/stax-blind-signing-02.png) -| *Note the null amount and lack of token info. The last screen is the modal that appears whenever the user taps on the warning icon.* | +| *Note the null amount and lack of token info: the user cannot understand that they are signing a token approval in this example. The last screen is displayed when the user touches the warning icon in the top-right corner, displayed on the intent and signing pages.* | + +If the “blind signing” setting is disabled, a dedicated warning message redirects the user when attempting to sign a transaction that cannot be fully decoded: + +![Flex Blind Signing](/device-app/flex-blind-signing.png) +|:--:| +| *The term “transaction” must be replaced with “message” when relevant.* | -Here’s the exact same sequence on Ledger Flex, for a comprehensive overview of this warning mechanism: -![Flex Blind Signing](/device-app/flex-blind-signing-01.png) -![Flex Blind Signing](/device-app/flex-blind-signing-02.png) API
- nbgl_useCaseChoice()
- nbgl_useCaseChoice()
- nbgl_useCaseReview(with BLIND_OPERATION flag)
- These high-level API functions allow you to manually set the two warning screens preceding the transaction review, and enable the warning icon on the top right of Review transaction and Sign transaction pages. View Boilerplate application PR example. + nbgl_useCaseReviewBlindSigning()
+ This high-level API function covers the special case of blind-signed transactions and messages, presented in this section. View API doc.
## Signing messages diff --git a/pages/docs/device-app/introduction/plugins.mdx b/pages/docs/device-app/introduction/plugins.mdx index 0cfaa137..460a071e 100644 --- a/pages/docs/device-app/introduction/plugins.mdx +++ b/pages/docs/device-app/introduction/plugins.mdx @@ -1,75 +1,20 @@ --- -title: Overview +title: Ethereum plugins description: Let’s start with a high-level overview of what a plugin is, how it interacts with the Ethereum app, and the steps required to write your plugin. --- -import { Callout } from 'nextra/components' +# Ethereum plugins -# Overview +## Why plugins? -Let’s start with a high-level overview of what a plugin is, how it interacts with the Ethereum app, and the steps required to write your plugin. +Plugins display specific smart contracts information to the users to avoid blind signing. They work hand-in-hand with the Ethereum App. - - Even though this guide is relatively beginner-friendly, you need to have prior experience of C and Solidity development. - - -## Why Plugins? - -If you’ve already interacted with any smart contract using a Ledger Device, then you’ve already seen this screen: - -![Blind Signing](/plugin/blind-signing.png) - -Blind signing transactions pose a significant security risk and result in a poor user experience by not providing users with clear transaction details. - -This is a UX disaster. The user has no guarantee of interacting with the right smart contract, nor of signing the correct data. The only user action is literally to blind-sign the transaction. - -Display information is specific to each smart contract: so when swapping on a decentralised exchange, you probably want to see information such as “Swapping X ETH for Y DAI”. When depositing DAI on Aave, you need to see the amount in DAI. So, the information is specific to the smart contract. - - -![Plugin](/plugin/contract-data.png) - -Modifying the Ethereum App would not do because its size would quickly go out of control. Instead, the solution lies in a small and versatile parser of smart contract data, which works hand-in-hand with the Ethereum App and decides what to display for the best user experience. - -This is precisely what a plugin is: a small app that the user installs on their device to show just what needs to be signed. Since plugins are very small, users can install many without worrying about memory. - -Ledger designed and implemented Paraswap, the first Ethereum plugin. - - - - - The second mandatory requirement to obtain official support by Ledger for your dApp is using a plugin to verify transaction details on the Ledger. - - -## Example - -Plugins work hand-in-hand with the Ethereum App, and implementation is straightforward. The Ethereum App handles parsing, signing, screen display etc. The only thing your plugin needs to do is: +The Ethereum App handles parsing, signing, screen display etc. The only thing your plugin needs to do is: 1. Extract the relevant information from the data. 2. Send the string to be displayed back to the Ethereum App. -Here is an overview of what the flow looks like: - -```mermaid - sequenceDiagram - Eth App ->> Plugin: Are you installed on this device? - Note right of Plugin: Fail if
no plugin found - Plugin -->> Eth App: Yes, bring it on! - Note left of Eth App: Parse transaction - Eth App ->> Plugin: Here is the contract data - Note right of Plugin: Extract
important fields - Plugin -->> Eth App: Ok - Note left of Eth App: Compute stuff - Note left of Eth App: Prepare the display - Eth App ->> Plugin: What must I display with this? - Note right of Plugin: Decide
what to display - Plugin -->> Eth App: 'Swap ETH 1 for DAI 10000' - Note left of Eth App: Display
the message -``` - -As you can see, the flow is a series of back-and-forth messages between the Ethereum App and the plugin. - -Before we start to code, let's set up the development environment. -## What now? +## Integration walkthrough -For further details on plugin development, please refer to [this tutorial](../develop/tutorials/plugin). \ No newline at end of file +Follow [this documentation](https://ethereum-plugin-sdk.ledger.com/) to integrate your Ethereum Plugin. diff --git a/pages/docs/exchange/card/providers-backend.mdx b/pages/docs/exchange/card/providers-backend.mdx index a5b572e1..e03c8f15 100644 --- a/pages/docs/exchange/card/providers-backend.mdx +++ b/pages/docs/exchange/card/providers-backend.mdx @@ -3,7 +3,7 @@ import 'react-medium-image-zoom/dist/styles.css' import { Tabs } from 'nextra/components' import { Callout } from 'nextra/components' import BackendIntro from '../../../../public/exchange/import/backend-intro.mdx' -import PayloadAndSignature from '../../../../public/exchange/import/payload-and-signature.mdx' +import PayloadAndSignature from '../../../../public/exchange/import/sell-payload-and-signature.mdx' # Backend @@ -29,14 +29,30 @@ You need to implement following endpoint for the card: "quoteId": "string", "amount": "string", "refundAddress": "string", - "currencyFrom": "string", - "fiatTo": "string", - "payloadCurrencyFrom": "string", - "payloadFiatTo": "string", + "fromCryptoCurrency": "string", + "toFiatCurrency": "string", + "payloadCryptoCurrency": "string", "nonce": "number", } ``` +An example response from the your backend to the Ledger backend + + ``` json copy +{ + "sellId": "SELL-ID-165940", + "payinAddress": "0xa0b86991c627e936c1d19d4a2e90a2ce3606eb48", + "createdAt": "2030-05-26T14:13:39", + "providerFees": "0.0001", + "referralFees": "0.0001", + "payoutNetworkFees": "0.0002", + "providerSig": { + "payload": "CgUweGZmZhoFMHhmZmYqBTB4ZmZmOgNCVENCA0JBVEoIMTIwMDAwMDBSCDExNTAwMDAwWhF2ZXJ5IGxvbmd1ZSBub25jZQ==", + "signature": "MEUCIBRm4PrdgRw0aBwRepuOGGRmR/YPcCoyKNJ7UDjFO030AiEA/VT0anolum0a3X/9lGPeovZHqzeDG9brcUB4zhYmwbs=" + } +} + ``` + Your Protobuf message should have the following structure: ```go copy @@ -59,22 +75,6 @@ You need to implement following endpoint for the card: bytes device_transaction_id = 7; } ``` -An example response from the ledger backend to the exchange SDK - - ``` json copy -{ - "sellId": "SELL-ID-165940", - "payinAddress": "0xa0b86991c627e936c1d19d4a2e90a2ce3606eb48", - "createdAt": "2030-05-26T14:13:39", - "providerFees": "0.0001", - "referralFees": "0.0001", - "payoutNetworkFees": "0.0002", - "providerSig": { - "payload": "CgUweGZmZhoFMHhmZmYqBTB4ZmZmOgNCVENCA0JBVEoIMTIwMDAwMDBSCDExNTAwMDAwWhF2ZXJ5IGxvbmd1ZSBub25jZQ==", - "payload_signature": "MEUCIBRm4PrdgRw0aBwRepuOGGRmR/YPcCoyKNJ7UDjFO030AiEA/VT0anolum0a3X/9lGPeovZHqzeDG9brcUB4zhYmwbs=" - } -} - ``` ## Real-time Status Updates: @@ -96,7 +96,7 @@ To ensure effective communication with Ledger's backend, you are required to imp [//]: # (Info needed to be exchange with the provider, but not out loud publicly) [//]: # (**IP address checking** ) -[//]: # (Additionally, we also need a way to know if a user will be able to perform a coin swap given his IP.) +[//]: # (Additionally, we also need a way to know if a user will be able to perform a coin sell given his IP.) [//]: # (Our back-end can adapt to how you decide to do this, but we recommend you use a dedicated endpoint. Our back-end will send the user’s IP address to that endpoint, without logging it. In response, your endpoint should tell us if the trade is accepted or rejected.) diff --git a/pages/docs/exchange/sell/providers-backend.mdx b/pages/docs/exchange/sell/providers-backend.mdx index 3b5b8da4..5b0a7cf5 100644 --- a/pages/docs/exchange/sell/providers-backend.mdx +++ b/pages/docs/exchange/sell/providers-backend.mdx @@ -3,7 +3,7 @@ import 'react-medium-image-zoom/dist/styles.css' import { Tabs } from 'nextra/components' import { Callout } from 'nextra/components' import BackendIntro from '../../../../public/exchange/import/backend-intro.mdx' -import PayloadAndSignature from '../../../../public/exchange/import/payload-and-signature.mdx' +import PayloadAndSignature from '../../../../public/exchange/import/sell-payload-and-signature.mdx' # Backend @@ -32,14 +32,30 @@ There are 4 main endpoints needed for the sell: "quoteId": "string", "amount": "string", "refundAddress": "string", - "currencyFrom": "string", - "fiatTo": "string", - "payloadCurrencyFrom": "string", - "payloadFiatTo": "string", + "fromCryptoCurrency": "string", + "toFiatCurrency": "string", + "payloadCryptoCurrency": "string", "nonce": "number", } ``` +An example response from the your backend to the Ledger backend + + ``` json copy +{ + "sellId": "SELL-ID-165940", + "payinAddress": "0xa0b86991c627e936c1d19d4a2e90a2ce3606eb48", + "createdAt": "2030-05-26T14:13:39", + "providerFees": "0.0001", + "referralFees": "0.0001", + "payoutNetworkFees": "0.0002", + "providerSig": { + "payload": "CgUweGZmZhoFMHhmZmYqBTB4ZmZmOgNCVENCA0JBVEoIMTIwMDAwMDBSCDExNTAwMDAwWhF2ZXJ5IGxvbmd1ZSBub25jZQ==", + "signature": "MEUCIBRm4PrdgRw0aBwRepuOGGRmR/YPcCoyKNJ7UDjFO030AiEA/VT0anolum0a3X/9lGPeovZHqzeDG9brcUB4zhYmwbs=" + } +} + ``` + Your Protobuf message should have the following structure: ```go copy @@ -62,22 +78,6 @@ There are 4 main endpoints needed for the sell: bytes device_transaction_id = 7; } ``` -An example response from the ledger backend to the exchange SDK - - ``` json copy -{ - "sellId": "SELL-ID-165940", - "payinAddress": "0xa0b86991c627e936c1d19d4a2e90a2ce3606eb48", - "createdAt": "2030-05-26T14:13:39", - "providerFees": "0.0001", - "referralFees": "0.0001", - "payoutNetworkFees": "0.0002", - "providerSig": { - "payload": "CgUweGZmZhoFMHhmZmYqBTB4ZmZmOgNCVENCA0JBVEoIMTIwMDAwMDBSCDExNTAwMDAwWhF2ZXJ5IGxvbmd1ZSBub25jZQ==", - "payload_signature": "MEUCIBRm4PrdgRw0aBwRepuOGGRmR/YPcCoyKNJ7UDjFO030AiEA/VT0anolum0a3X/9lGPeovZHqzeDG9brcUB4zhYmwbs=" - } -} - ``` ## Real-time Status Updates: @@ -99,7 +99,7 @@ To ensure effective communication with Ledger's backend, you are required to imp [//]: # (Info needed to be exchange with the provider, but not out loud publicly) [//]: # (**IP address checking** ) -[//]: # (Additionally, we also need a way to know if a user will be able to perform a coin swap given his IP.) +[//]: # (Additionally, we also need a way to know if a user will be able to perform a coin sell given his IP.) [//]: # (Our back-end can adapt to how you decide to do this, but we recommend you use a dedicated endpoint. Our back-end will send the user’s IP address to that endpoint, without logging it. In response, your endpoint should tell us if the trade is accepted or rejected.) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c3396ff..05a597db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,6 @@ settings: excludeLinksFromLockfile: false importers: - .: dependencies: '@docsly/react': @@ -8234,7 +8233,7 @@ snapshots: nextra: 2.13.2(next@14.3.0-canary.21(@babel/core@7.24.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - scroll-into-view-if-needed: 3.0.10 + scroll-into-view-if-needed: 3.1.0 zod: 3.22.4 nextra@2.13.2(next@14.3.0-canary.21(@babel/core@7.24.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0): diff --git a/public/blockchain/coin-module-inner-dependency.png b/public/blockchain/coin-module-inner-dependency.png new file mode 100644 index 00000000..c25527cc Binary files /dev/null and b/public/blockchain/coin-module-inner-dependency.png differ diff --git a/public/device-app/flex-blind-signing-01.png b/public/device-app/flex-blind-signing-01.png deleted file mode 100644 index baafc719..00000000 Binary files a/public/device-app/flex-blind-signing-01.png and /dev/null differ diff --git a/public/device-app/flex-blind-signing-02.png b/public/device-app/flex-blind-signing-02.png deleted file mode 100644 index afb47a9a..00000000 Binary files a/public/device-app/flex-blind-signing-02.png and /dev/null differ diff --git a/public/device-app/flex-blind-signing.png b/public/device-app/flex-blind-signing.png new file mode 100644 index 00000000..f2713339 Binary files /dev/null and b/public/device-app/flex-blind-signing.png differ diff --git a/public/device-app/stax-blind-signing-01.png b/public/device-app/stax-blind-signing-01.png index 955483bc..fa3c0b5f 100644 Binary files a/public/device-app/stax-blind-signing-01.png and b/public/device-app/stax-blind-signing-01.png differ diff --git a/public/device-app/stax-blind-signing-02.png b/public/device-app/stax-blind-signing-02.png index cab44627..90d69f1b 100644 Binary files a/public/device-app/stax-blind-signing-02.png and b/public/device-app/stax-blind-signing-02.png differ diff --git a/public/device-app/stax-flex-icons-template.png b/public/device-app/stax-flex-icons-template.png new file mode 100644 index 00000000..d605d724 Binary files /dev/null and b/public/device-app/stax-flex-icons-template.png differ diff --git a/public/device-app/stax-icons-template.png b/public/device-app/stax-icons-template.png deleted file mode 100644 index 90af4a6b..00000000 Binary files a/public/device-app/stax-icons-template.png and /dev/null differ diff --git a/public/exchange/import/sell-payload-and-signature.mdx b/public/exchange/import/sell-payload-and-signature.mdx new file mode 100644 index 00000000..7a87dc56 --- /dev/null +++ b/public/exchange/import/sell-payload-and-signature.mdx @@ -0,0 +1,24 @@ +import Zoom from 'react-medium-image-zoom' +import 'react-medium-image-zoom/dist/styles.css' + +### Input field: nonce + +A nonce field will be passed as a parameter of the **/sell** endpoint. +It is a 32 bytes nonce which is generated by the hardware wallet to avoid replay attacks. +It will be base 64 URL encoded before being sent to the **/sell** endpoint. + +### Output field: providerSig + +The real return value of the **/sell** endpoint is the `providerSig` field with the JSON Web Signature (JWS) in compact form within: +- `providerSig.payload` - base64 URL of the binary serialized protobuf message.NewTransactionResponse. +- `providerSig.signature` - base64 URL of the ES256 signature of providerSig.payload. More details in the [JWS signature](#jws-signature) section. + +### JWS-Signature +The output field is `providerSig`, and is based on the [flattened JWS JSON](https://www.rfc-editor.org/rfc/rfc7515#section-7.2.2), without the optional `protected` and `header` parameters. + +The algorithms supported by Ledger are [ES256](https://www.rfc-editor.org/rfc/rfc7518#section-3.4) with secp256k1 or secp256r1 curve. There is no need to specify them into the `header`, +as we will store your public key with this information in our system. + +As specified in the JWA spec, the signature is a concatenation of the (R, S) pair in a 64 octets array, and not the ASN1 format. + +The [JWS RFC](https://www.rfc-editor.org/rfc/rfc7515#section-5.1) requires the signature to be computed on: `.`. As we don't require any `header`, the signature has to be computed on: `.`. \ No newline at end of file diff --git a/public/exchange/openapi/card-provider-openapi.yml b/public/exchange/openapi/card-provider-openapi.yml index c3982a4c..d37e76e3 100644 --- a/public/exchange/openapi/card-provider-openapi.yml +++ b/public/exchange/openapi/card-provider-openapi.yml @@ -34,7 +34,7 @@ paths: payoutNetworkFees: "0.0002" providerSig: payload: "CgUweGZmZhoFMHhmZmYqBTB4ZmZmOgNCVENCA0JBVEoIMTIwMDAwMDBSCDExNTAwMDAwWhF2ZXJ5IGxvbmd1ZSBub25jZQ==" - payload_signature: "MEUCIBRm4PrdgRw0aBwRepuOGGRmR/YPcCoyKNJ7UDjFO030AiEA/VT0anolum0a3X/9lGPeovZHqzeDG9brcUB4zhYmwbs=" + signature: "MEUCIBRm4PrdgRw0aBwRepuOGGRmR/YPcCoyKNJ7UDjFO030AiEA/VT0anolum0a3X/9lGPeovZHqzeDG9brcUB4zhYmwbs=" "400": description: Deposit wallet not available. content: @@ -89,7 +89,7 @@ paths: operationId: statusUpdate description: Updates the status of a sell transaction. security: - - defaultApiKey: [] + - defaultApiKey: [ ] parameters: - name: sellId in: path @@ -131,10 +131,9 @@ components: - quoteId - amount - refundAddress - - currencyFrom - - fiatTo - - payloadCurrencyFrom - - payloadFiatTo + - fromCryptoCurrency + - toFiatCurrency + - payloadCryptoCurrency - nonce type: object properties: @@ -147,33 +146,22 @@ components: refundAddress: type: string description: User's refund address, typically the fromAddress. - payoutAddress: - type: string - description: User's payout transaction address. - refundAddressExtraId: - type: string - description: Field used for blockchains that require a memo (e.g., XRP, XLM). - payoutAddressExtraId: - type: string - description: Field used for blockchains that require a memo (e.g., XRP, XLM). - currencyFrom: + fromCryptoCurrency: type: string description: From currency ID, using the provider's identifiers. - currencyTo: + toFiatCurrency: type: string - description: To currency ID, using the provider's identifiers. - payloadCurrencyFrom: + description: Fiat currency at the ISO-4217 standard. + payloadCryptoCurrency: type: string - description: From currency ID, using Ledger's referential. (representing currency_from in the protobuf payload) - payloadCurrencyTo: - type: string - description: To currency ID, using Ledger's referential. (representing currency_to in the protobuf payload) + description: From currency ID, using Ledger's referential. (representing currency_from in the protobuf payload nonce: type: string description: Value for the `device_transaction_id_ng` field in the protobuf payload, in hex format of the byte array nonce received by the user. slippage: type: string description: Value between 0 to 1 (0.1 => 10%). Maximum difference between the quoted amount and the final amount received, as a ratio. If exceeded, user consent is required or a refund is made. + SellResponse: required: - sellId diff --git a/public/exchange/openapi/sell-provider-openapi.yml b/public/exchange/openapi/sell-provider-openapi.yml index a142cc38..1a1cf100 100644 --- a/public/exchange/openapi/sell-provider-openapi.yml +++ b/public/exchange/openapi/sell-provider-openapi.yml @@ -85,7 +85,7 @@ paths: description: CryptoCurrency amount to sell. required: true schema: - type: integer + type: string example: 0.3 - name: country in: query @@ -206,7 +206,7 @@ paths: payoutNetworkFees: "0.0002" providerSig: payload: "CgUweGZmZhoFMHhmZmYqBTB4ZmZmOgNCVENCA0JBVEoIMTIwMDAwMDBSCDExNTAwMDAwWhF2ZXJ5IGxvbmd1ZSBub25jZQ==" - payload_signature: "MEUCIBRm4PrdgRw0aBwRepuOGGRmR/YPcCoyKNJ7UDjFO030AiEA/VT0anolum0a3X/9lGPeovZHqzeDG9brcUB4zhYmwbs=" + signature: "MEUCIBRm4PrdgRw0aBwRepuOGGRmR/YPcCoyKNJ7UDjFO030AiEA/VT0anolum0a3X/9lGPeovZHqzeDG9brcUB4zhYmwbs=" "400": description: Deposit wallet not available. content: @@ -268,7 +268,7 @@ paths: type: string example: 1234 security: - - defaultApiKey: [] + - defaultApiKey: [ ] requestBody: description: Status update payload content: @@ -409,6 +409,7 @@ components: - $ref: '#/components/schemas/Visa' QuoteResponse: required: + - quoteId - amountTo - providerFees - referralFees @@ -467,10 +468,9 @@ components: - quoteId - amount - refundAddress - - currencyFrom - - fiatTo - - payloadCurrencyFrom - - payloadFiatTo + - fromCryptoCurrency + - toFiatCurrency + - payloadCryptoCurrency - nonce type: object properties: @@ -483,33 +483,22 @@ components: refundAddress: type: string description: User's refund address, typically the fromAddress. - payoutAddress: - type: string - description: User's payout transaction address. - refundAddressExtraId: - type: string - description: Field used for blockchains that require a memo (e.g., XRP, XLM). - payoutAddressExtraId: - type: string - description: Field used for blockchains that require a memo (e.g., XRP, XLM). - currencyFrom: + fromCryptoCurrency: type: string description: From currency ID, using the provider's identifiers. - currencyTo: - type: string - description: To currency ID, using the provider's identifiers. - payloadCurrencyFrom: + toFiatCurrency: type: string - description: From currency ID, using Ledger's referential. (representing currency_from in the protobuf payload) - payloadCurrencyTo: + description: Fiat currency at the ISO-4217 standard. + payloadCryptoCurrency: type: string - description: To currency ID, using Ledger's referential. (representing currency_to in the protobuf payload) + description: From currency ID, using Ledger's referential. (representing currency_from in the protobuf payload nonce: type: string description: Value for the `device_transaction_id_ng` field in the protobuf payload, in hex format of the byte array nonce received by the user. slippage: type: string - description: Value between 0 to 1 (0.1 => 10%). Maximum difference between the quoted amount and the final amount received, as a ratio. If exceeded, user consent is required or a refund is made. + description: Value between 0 to 1 (0.1 => 10%). Maximum difference between the quoted amount and the final amount received, as a ratio. If exceeded, user consent is required or a refund is made.\ + SellResponse: required: - sellId diff --git a/public/sitemap-0.xml b/public/sitemap-0.xml index 2e9feda5..1e66862b 100644 --- a/public/sitemap-0.xml +++ b/public/sitemap-0.xml @@ -1,246 +1,246 @@ -https://developers.ledger.com2024-04-16T14:35:53.333Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/appendix/families2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/appendix/manifest2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/appendix/migration-guide2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/appendix/transaction2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/appendix/working-with-ledger-live2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/configuration2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/getting-started2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/modules/account2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/modules/bitcoin2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/modules/currency2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/modules/custom2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/modules/device2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/modules/exchange2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/modules/message2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/modules/storage2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/modules/transaction2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/core/modules/wallet2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/examples/live-app-creation/configuration2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/examples/live-app-creation/hooking-up2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/examples/live-app-creation/manifest2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/examples/live-app-creation/start2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/examples/test-live-app/prerequisites2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/examples/test-live-app/testing2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/examples/use-live-app/import2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/examples/use-live-app/interact2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/examples/use-live-app/manifest2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/examples/use-live-app/prerequisites2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/introduction2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/configuration2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/getting-started2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useAccounts2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useCapabilities2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useCurrencies2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useRequestAccount2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useSignAndBroadcastTransaction2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useSignMessage2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useSignTransaction2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useUserId2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useWalletInfo2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/extras/rpc-node2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/extras/rxjs2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/handlers/account2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/handlers/bitcoin2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/handlers/currency2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/handlers/device2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/handlers/exchange2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/handlers/message2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/handlers/storage2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/handlers/transaction2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/handlers/wallet2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/usage-examples/with-constructor2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/usage-examples/within-ledger-live2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/wallet-api-server2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/wallet-api-server/diagrams2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/server/wallet-api-server/react-hook2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/simulator/configuration2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/simulator/getting-started2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/simulator/introduction2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api/simulator/profiles2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/dev-journey/blockchain-foundation2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/dev-journey/nft-project2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/dev-journey/overview2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/dev-journey/services-dapps2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/dev-journey/wallet2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/coding2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/coding/address-derivation2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/coding/bugs-troubleshooting2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/coding/cryptoassets-library2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/coding/desktop-mobile2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/coding/js-bindings2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/coding/light-sync2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/coding/send2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/coding/sync2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/coding/wallet-api2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/faq2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/getting-started2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/getting-started/general-architecture2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/getting-started/required-skills2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/setup-build2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/setup-build/ledger-live-cli2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/setup-build/ll-setup2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/setup-build/mostusefulcmd2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/terms-conditions2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/testing2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/testing/bridge-test2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/testing/generic-test-plan2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/testing/ui-tests2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/ui-ux-guidelines2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/ui-ux-guidelines/lld-ui-guidelines2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/blockchain/ui-ux-guidelines/llm-ui-guidelines2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/architecture2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/architecture/application-architecture2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/architecture/architecture-dataflow2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/clear-signing2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/faq2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/faq/U2F2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/faq/ledger-logos2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/faq/transport-nano2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/faq/usb-identifiers2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/desktop-application/node-electron-hid2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/mobile-application/environment-setup2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/mobile-application/react-native-android-hid2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/mobile-application/react-native-bluetooth-android2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/mobile-application/react-native-bluetooth-ios2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/web-application/web-bluetooth2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/web-application/web-hid-usb2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/open-close-info-on-apps2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/required-skills2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials/tutorial-1-hid-ethereum2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials/tutorial-1-usb-solana2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials/tutorial-2-web-bluetooth2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials/tutorial-3-sign-message2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials/tutorial-4-cosmos-webapp2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/bolos2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/bolos/application-environment2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/bolos/features2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/bolos/hardware-architecture2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/cryptography/examples2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/cryptography/general-concepts2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/cryptography/reference2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/flags2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/io2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/memory2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/memory/memory-alignment2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/memory/persistent-storage2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/memory/position-independent-code2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/psd2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/psd/application-isolation2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/psd/applications2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/psd/keys2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/psd/masterseed2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/security2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/architecture/ui2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/deliverables2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/deliverables/companion-wallet2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/deliverables/documentation2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/deliverables/icons2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/deliverables/legal2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/deliverables/marketing2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/deliverables/security-audit2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/deliverables/support2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/deliverables/ui-flow-video2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/maintenance2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/partners2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/process2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/deliver/submission-form2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/faq2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/requirements2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/requirements/development2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/requirements/security2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tips2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tools2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/c2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/clone2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin/boilerplate-plugin2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin/selectors2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin/selectors-detailed2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin/selectors-detailed/finalize2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin/selectors-detailed/init-contract2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin/selectors-detailed/provide-parameter2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin/selectors-detailed/provide-token2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin/selectors-detailed/query-contract-id2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin/selectors-detailed/query-contract-ui2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/plugin/testing2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/quickstart2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/tutorials/rust2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/flows2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/flows/advanced-display-management2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/flows/display-management-flow2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-nano-display2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-nano-display/design-requirements2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-nano-display/display-management-design2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-nano-display/low-level-display-management2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-display2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-display/address2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-display/graphic-lib2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-display/home2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-display/info-settings2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-display/messages2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-display/transactions2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/introduction2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/introduction/app2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/introduction/clones2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/introduction/community2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/device-app/introduction/plugins2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/dapp-browser2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/dapp-browser/dapp-customisation2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/dapp-browser/manifest2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/dapp-browser/plugin2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/developer-mode2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/faq2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/guidelines2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/required-skills2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/discover/wallet-api2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/buy2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/buy/providers-backend2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/buy/providers-liveapp2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/buy/providers-test-and-submit2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/glossary2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/sell2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/sell/providers-backend2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/sell/providers-liveapp2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/sell/providers-test-and-submit2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap/flow2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap/flow/detailed-technical-flows2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap/flow/device-flow2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap/flow/edge-cases2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap/flow/partner-live-app2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap/flow/quote2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap/flow/transaction-status2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap/providers-backend2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap/providers-liveapp2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/exchange/swap/providers-test-and-submit2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/nft-display2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/nft-display/image-requirements2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/nft-display/manifold-studio2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/nft-display/metadata-tool2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/nft-display/optimized-images2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/nft-display/update-metadata2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/sections2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/tokens2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/tokens/eip712-messages2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/tokens/integrating-tokens2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/tokens/integrating-tokens/asa2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/tokens/integrating-tokens/evm-chains-tokens2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/tokens/integrating-tokens/faq2024-04-16T14:35:53.334Zweekly0.7 -https://developers.ledger.com/docs/tokens/integrating-tokens/trc2024-04-16T14:35:53.334Zweekly0.7 +https://developers.ledger.com2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/dev-journey/blockchain-foundation2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/dev-journey/nft-project2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/dev-journey/overview2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/dev-journey/services-dapps2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/dev-journey/wallet2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding/address-derivation2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding/bugs-troubleshooting2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding/create-module2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding/cryptoassets-library2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding/desktop-mobile2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding/js-bindings2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding/light-sync2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding/send2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding/sync2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/coding/wallet-api2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/faq2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/getting-started2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/getting-started/general-architecture2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/getting-started/required-skills2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/setup-build2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/setup-build/ledger-live-cli2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/setup-build/ll-setup2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/setup-build/mostusefulcmd2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/terms-conditions2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/testing2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/testing/bridge-test2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/testing/generic-test-plan2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/testing/ui-tests2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/ui-ux-guidelines2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/ui-ux-guidelines/lld-ui-guidelines2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/blockchain/ui-ux-guidelines/llm-ui-guidelines2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/clear-signing2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/clear-signing/eip7122024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/clear-signing/eip77302024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/clear-signing/nft2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/architecture2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/architecture/application-architecture2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/architecture/architecture-dataflow2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/faq2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/faq/U2F2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/faq/ledger-logos2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/faq/transport-nano2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/faq/usb-identifiers2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/desktop-application/node-electron-hid2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/mobile-application/environment-setup2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/mobile-application/react-native-android-hid2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/mobile-application/react-native-bluetooth-android2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/mobile-application/react-native-bluetooth-ios2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/web-application/web-bluetooth2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/integration-walkthrough/web-application/web-hid-usb2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/open-close-info-on-apps2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/required-skills2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials/tutorial-1-hid-ethereum2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials/tutorial-1-usb-solana2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials/tutorial-2-web-bluetooth2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials/tutorial-3-sign-message2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/connectivity/ledgerJS/tutorials/tutorial-4-cosmos-webapp2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/device-app2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/bolos2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/bolos/application-environment2024-09-11T08:53:46.348Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/bolos/features2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/bolos/hardware-architecture2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/cryptography/examples2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/cryptography/general-concepts2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/cryptography/reference2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/flags2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/io2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/memory2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/memory/memory-alignment2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/memory/persistent-storage2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/memory/position-independent-code2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/psd2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/psd/application-isolation2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/psd/applications2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/psd/keys2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/psd/masterseed2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/security2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/architecture/ui2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/deliverables2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/deliverables/companion-wallet2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/deliverables/documentation2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/deliverables/icons2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/deliverables/legal2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/deliverables/marketing2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/deliverables/security-audit2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/deliverables/support2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/deliverables/ui-flow-video2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/maintenance2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/partners2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/process2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/deliver/submission-form2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/code/c2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/code/clone2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/code/plugin2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/code/rust2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/faq2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/quickstart2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/requirements2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/requirements/development2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/requirements/security2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/tips2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/tools2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/flows2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/flows/advanced-display-management2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/flows/display-management-flow2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-nano-display2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-nano-display/design-requirements2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-nano-display/display-management-design2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-nano-display/low-level-display-management2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-flex-display2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-flex-display/address2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-flex-display/advanced2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-flex-display/graphic-lib2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-flex-display/home2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-flex-display/info-settings2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-flex-display/intro2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/develop/ui/ledger-stax-flex-display/transactions2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/introduction2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/introduction/app2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/introduction/clones2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/introduction/community2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/device-app/introduction/plugins2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/dapp-browser2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/dapp-browser/dapp-customisation2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/dapp-browser/manifest2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/dapp-browser/migration2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/dapp-browser/plugin2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/developer-mode2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/faq2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/guidelines2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/required-skills2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/appendix/families2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/appendix/manifest2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/appendix/migration-guide2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/appendix/transaction2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/appendix/working-with-ledger-live2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/configuration2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/getting-started2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/modules/account2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/modules/bitcoin2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/modules/currency2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/modules/custom2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/modules/device2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/modules/exchange2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/modules/message2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/modules/storage2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/modules/transaction2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/core/modules/wallet2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/examples/live-app-creation/configuration2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/examples/live-app-creation/hooking-up2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/examples/live-app-creation/manifest2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/examples/live-app-creation/start2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/examples/test-live-app/prerequisites2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/examples/test-live-app/testing2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/examples/use-live-app/import2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/examples/use-live-app/interact2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/examples/use-live-app/manifest2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/examples/use-live-app/prerequisites2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/introduction2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/configuration2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/getting-started2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useAccounts2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useCapabilities2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useCurrencies2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useRequestAccount2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useSignAndBroadcastTransaction2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useSignMessage2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useSignTransaction2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useUserId2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/react/hooks/useWalletInfo2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/extras/rpc-node2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/extras/rxjs2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/handlers/account2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/handlers/bitcoin2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/handlers/currency2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/handlers/device2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/handlers/exchange2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/handlers/message2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/handlers/storage2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/handlers/transaction2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/handlers/wallet2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/usage-examples/with-constructor2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/usage-examples/within-ledger-live2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/wallet-api-server2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/wallet-api-server/diagrams2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/server/wallet-api-server/react-hook2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/simulator/configuration2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/simulator/getting-started2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/simulator/introduction2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/discover/wallet-api/simulator/profiles2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/buy2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/buy/providers-backend2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/buy/providers-liveapp2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/buy/providers-test-and-submit2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/card2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/card/providers-backend2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/card/providers-liveapp2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/card/providers-test-and-submit2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/glossary2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/sell2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/sell/providers-backend2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/sell/providers-liveapp2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/sell/providers-test-and-submit2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/README2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/flow2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/flow/detailed-technical-flows2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/flow/device-flow2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/flow/edge-cases2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/flow/partner-live-app2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/flow/quote2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/flow/transaction-status2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/providers-backend2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/providers-currencies2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/providers-liveapp2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/exchange/swap/providers-test-and-submit2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/nft-display2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/nft-display/image-requirements2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/nft-display/manifold-studio2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/nft-display/metadata-tool2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/nft-display/optimized-images2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/nft-display/update-metadata2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/sections2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/tokens2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/tokens/asa2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/tokens/evm-chains-tokens2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/tokens/faq2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/tokens/integrating-tokens2024-09-11T08:53:46.349Zweekly0.7 +https://developers.ledger.com/docs/tokens/trc2024-09-11T08:53:46.349Zweekly0.7 \ No newline at end of file