From 9fc214bb8018d496f27b7f6745a8745da8a74234 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 26 Jan 2024 12:25:56 +0300 Subject: [PATCH] feat: add serialization of TokenAccount extra fields --- .../src/account/serialization.test.ts | 26 +++++++++++++++ .../src/account/serialization.ts | 20 ++++++++++++ .../sortByMarketcap.test.ts.snap | 2 ++ .../bridge.integration.test.ts.snap | 1 + .../src/families/solana/bridge/bridge.ts | 9 +++++- .../src/families/solana/serialization.ts | 31 ++++++++++++++++-- libs/ledgerjs/packages/types-live/README.md | 32 +++++++++++++++++-- .../packages/types-live/src/bridge.ts | 21 +++++++++++- 8 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 libs/ledger-live-common/src/account/serialization.test.ts diff --git a/libs/ledger-live-common/src/account/serialization.test.ts b/libs/ledger-live-common/src/account/serialization.test.ts new file mode 100644 index 000000000000..0aa979de1482 --- /dev/null +++ b/libs/ledger-live-common/src/account/serialization.test.ts @@ -0,0 +1,26 @@ +import { getCryptoCurrencyById, getTokenById, setSupportedCurrencies } from "../currencies"; +import { genAccount, genTokenAccount } from "@ledgerhq/coin-framework/mocks/account"; +import { toAccountRaw, fromAccountRaw } from "./serialization"; +import { setWalletAPIVersion } from "../wallet-api/version"; +import { WALLET_API_VERSION } from "../wallet-api/constants"; + +setWalletAPIVersion(WALLET_API_VERSION); + +setSupportedCurrencies(["solana"]); +const Solana = getCryptoCurrencyById("solana"); +const USDC = getTokenById("solana/spl/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); + +describe("serialization", () => { + test("TokenAccount extra fields should be serialized/deserialized", () => { + const acc: any = genAccount("mocked-account-1", { currency: Solana }); + const tokenAcc: any = genTokenAccount(1, acc, USDC); + tokenAcc.state = "initialized"; + acc.subAccounts = [tokenAcc]; + + const accRaw: any = toAccountRaw(acc); + expect(accRaw.subAccounts?.[0]?.state).toBe("initialized"); + + const deserializedAcc: any = fromAccountRaw(accRaw); + expect(deserializedAcc.subAccounts?.[0]?.state).toBe("initialized"); + }); +}); diff --git a/libs/ledger-live-common/src/account/serialization.ts b/libs/ledger-live-common/src/account/serialization.ts index 46078a05ed4d..2b56f9ccfb8c 100644 --- a/libs/ledger-live-common/src/account/serialization.ts +++ b/libs/ledger-live-common/src/account/serialization.ts @@ -404,6 +404,16 @@ export function fromAccountRaw(rawAccount: AccountRaw): Account { if (assignFromAccountRaw) { assignFromAccountRaw(rawAccount, res); } + + const assignFromTokenAccountRaw = bridge.assignFromTokenAccountRaw; + if (assignFromTokenAccountRaw) { + res.subAccounts?.forEach((subAcc, index) => { + const subAccRaw = subAccountsRaw?.[index]; + if (subAcc.type === "TokenAccount" && subAccRaw?.type === "TokenAccountRaw") { + assignFromTokenAccountRaw(subAccRaw, subAcc); + } + }); + } } } @@ -496,6 +506,16 @@ export function toAccountRaw(account: Account): AccountRaw { if (assignToAccountRaw) { assignToAccountRaw(account, res); } + + const assignToTokenAccountRaw = bridge.assignToTokenAccountRaw; + if (assignToTokenAccountRaw) { + res.subAccounts?.forEach((subAccRaw, index) => { + const subAcc = subAccounts?.[index]; + if (subAccRaw.type === "TokenAccountRaw" && subAcc?.type === "TokenAccount") { + assignToTokenAccountRaw(subAcc, subAccRaw); + } + }); + } } } diff --git a/libs/ledger-live-common/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap b/libs/ledger-live-common/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap index 77e124a80624..0e19bb63473b 100644 --- a/libs/ledger-live-common/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap +++ b/libs/ledger-live-common/src/currencies/__snapshots__/sortByMarketcap.test.ts.snap @@ -13052,5 +13052,7 @@ exports[`sortCurrenciesByIds snapshot 1`] = ` "cardano/native/f7c777fdd4531cf1c477551360e45b9684073c05c2fa61334f8f9add5665726974726565546f6b656e", "stellar/asset/USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", "casper/asset/USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", + "solana/spl/EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "solana/spl/So11111111111111111111111111111111111111112", ] `; diff --git a/libs/ledger-live-common/src/families/solana/__snapshots__/bridge.integration.test.ts.snap b/libs/ledger-live-common/src/families/solana/__snapshots__/bridge.integration.test.ts.snap index 4d404724fa27..9f6a8560ac59 100644 --- a/libs/ledger-live-common/src/families/solana/__snapshots__/bridge.integration.test.ts.snap +++ b/libs/ledger-live-common/src/families/solana/__snapshots__/bridge.integration.test.ts.snap @@ -42,6 +42,7 @@ exports[`solana currency bridge scanAccounts solana seed 1 1`] = ` "pendingOperations": [], "spendableBalance": "7960720", "starred": false, + "state": "initialized", "swapHistory": [], "tokenId": "solana/spl/So11111111111111111111111111111111111111112", "type": "TokenAccountRaw", diff --git a/libs/ledger-live-common/src/families/solana/bridge/bridge.ts b/libs/ledger-live-common/src/families/solana/bridge/bridge.ts index 16cbc2481b7f..751dbe4b95cd 100644 --- a/libs/ledger-live-common/src/families/solana/bridge/bridge.ts +++ b/libs/ledger-live-common/src/families/solana/bridge/bridge.ts @@ -25,7 +25,12 @@ import { PRELOAD_MAX_AGE, hydrate, preloadWithAPI } from "../js-preload"; import { prepareTransaction as prepareTransactionWithAPI } from "../js-prepareTransaction"; import { signOperationWithAPI } from "../js-signOperation"; import { getAccountShapeWithAPI } from "../js-synchronization"; -import { assignFromAccountRaw, assignToAccountRaw } from "../serialization"; +import { + assignFromAccountRaw, + assignFromTokenAccountRaw, + assignToAccountRaw, + assignToTokenAccountRaw, +} from "../serialization"; import type { SolanaAccount, SolanaPreloadDataV1, Transaction } from "../types"; import { endpointByCurrencyId } from "../utils"; @@ -164,6 +169,8 @@ export function makeBridges({ signOperation: makeSign(getAPI), assignFromAccountRaw, assignToAccountRaw, + assignFromTokenAccountRaw, + assignToTokenAccountRaw, }; const currencyBridge: CurrencyBridge = { diff --git a/libs/ledger-live-common/src/families/solana/serialization.ts b/libs/ledger-live-common/src/families/solana/serialization.ts index 9e86f96c1705..2ebb62e85db9 100644 --- a/libs/ledger-live-common/src/families/solana/serialization.ts +++ b/libs/ledger-live-common/src/families/solana/serialization.ts @@ -1,5 +1,12 @@ -import { SolanaAccount, SolanaAccountRaw, SolanaResources, SolanaResourcesRaw } from "./types"; -import { Account, AccountRaw } from "@ledgerhq/types-live"; +import { + SolanaAccount, + SolanaAccountRaw, + SolanaResources, + SolanaResourcesRaw, + SolanaTokenAccount, + SolanaTokenAccountRaw, +} from "./types"; +import { Account, AccountRaw, TokenAccount, TokenAccountRaw } from "@ledgerhq/types-live"; import { BigNumber } from "bignumber.js"; export function toSolanaResourcesRaw(resources: SolanaResources): SolanaResourcesRaw { @@ -30,3 +37,23 @@ export function assignFromAccountRaw(accountRaw: AccountRaw, account: Account) { if (solanaResourcesRaw) (account as SolanaAccount).solanaResources = fromSolanaResourcesRaw(solanaResourcesRaw); } + +export function assignToTokenAccountRaw( + tokenAccount: TokenAccount, + tokenAccountRaw: TokenAccountRaw, +) { + const solanaTokenAccount = tokenAccount as SolanaTokenAccount; + if (solanaTokenAccount.state) { + (tokenAccountRaw as SolanaTokenAccountRaw).state = solanaTokenAccount.state; + } +} + +export function assignFromTokenAccountRaw( + tokenAccountRaw: TokenAccountRaw, + tokenAccount: TokenAccount, +) { + const stateRaw = (tokenAccountRaw as SolanaTokenAccountRaw).state; + if (stateRaw) { + (tokenAccount as SolanaTokenAccount).state = stateRaw; + } +} diff --git a/libs/ledgerjs/packages/types-live/README.md b/libs/ledgerjs/packages/types-live/README.md index 5f75c5d89d2c..6968cb280566 100644 --- a/libs/ledgerjs/packages/types-live/README.md +++ b/libs/ledgerjs/packages/types-live/README.md @@ -46,8 +46,12 @@ Ledger Live main types. * [Parameters](#parameters) * [assignFromAccountRaw](#assignfromaccountraw) * [Parameters](#parameters-1) - * [initAccount](#initaccount) + * [assignToTokenAccountRaw](#assigntotokenaccountraw) * [Parameters](#parameters-2) + * [assignFromTokenAccountRaw](#assignfromtokenaccountraw) + * [Parameters](#parameters-3) + * [initAccount](#initaccount) + * [Parameters](#parameters-4) * [CurrenciesData](#currenciesdata) * [Properties](#properties-11) * [DatasetTest](#datasettest) @@ -483,6 +487,30 @@ Type: function (accountRaw: [AccountRaw](#accountraw), account: [Account](#accou * `accountRaw` **[AccountRaw](#accountraw)** The account in its serialized form. * `account` **[Account](#account)** The original account object. +#### assignToTokenAccountRaw + +This function mutates the 'tokenAccountRaw' object in-place to add any extra fields that the coin may need to set. +It is called during the serialization mechanism + +Type: function (tokenAccount: [TokenAccount](#tokenaccount), tokenAccountRaw: [TokenAccountRaw](#tokenaccountraw)): void + +##### Parameters + +* `tokenAccount` **[TokenAccount](#tokenaccount)** The original token account object. +* `tokenAccountRaw` **[TokenAccountRaw](#tokenaccountraw)** The token account in its serialized form. + +#### assignFromTokenAccountRaw + +This function mutates the 'tokenAccount' object in-place to add any extra fields that the coin may need to set. +It is called during the deserialization mechanism + +Type: function (tokenAccountRaw: [TokenAccountRaw](#tokenaccountraw), tokenAccount: [TokenAccount](#tokenaccount)): void + +##### Parameters + +* `tokenAccountRaw` **[TokenAccountRaw](#tokenaccountraw)** The token account in its serialized form. +* `tokenAccount` **[TokenAccount](#tokenaccount)** The original token account object. + #### initAccount This function mutates the 'account' object to extend it with any extra fields of the coin. @@ -1009,7 +1037,7 @@ Type: {ticker: [string](https://developer.mozilla.org/docs/Web/JavaScript/Refere ### OperationType -Type: (`"IN"` | `"OUT"` | `"NONE"` | `"CREATE"` | `"REVEAL"` | `"DELEGATE"` | `"UNDELEGATE"` | `"REDELEGATE"` | `"REWARD"` | `"FEES"` | `"FREEZE"` | `"UNFREEZE"` | `"WITHDRAW_EXPIRE_UNFREEZE"` | `"UNDELEGATE_RESOURCE"` | `"LEGACY_UNFREEZE"` | `"VOTE"` | `"REWARD_PAYOUT"` | `"BOND"` | `"UNBOND"` | `"WITHDRAW_UNBONDED"` | `"SET_CONTROLLER"` | `"SLASH"` | `"NOMINATE"` | `"CHILL"` | `"APPROVE"` | `"OPT_IN"` | `"OPT_OUT"` | `"LOCK"` | `"UNLOCK"` | `"WITHDRAW"` | `"REVOKE"` | `"ACTIVATE"` | `"REGISTER"` | `"NFT_IN"` | `"NFT_OUT"` | `"STAKE"` | `"UNSTAKE"` | `"WITHDRAW_UNSTAKED"`) +Type: (`"IN"` | `"OUT"` | `"NONE"` | `"CREATE"` | `"REVEAL"` | `"DELEGATE"` | `"UNDELEGATE"` | `"REDELEGATE"` | `"REWARD"` | `"FEES"` | `"FREEZE"` | `"UNFREEZE"` | `"WITHDRAW_EXPIRE_UNFREEZE"` | `"UNDELEGATE_RESOURCE"` | `"LEGACY_UNFREEZE"` | `"VOTE"` | `"REWARD_PAYOUT"` | `"BOND"` | `"UNBOND"` | `"WITHDRAW_UNBONDED"` | `"SET_CONTROLLER"` | `"SLASH"` | `"NOMINATE"` | `"CHILL"` | `"APPROVE"` | `"OPT_IN"` | `"OPT_OUT"` | `"LOCK"` | `"UNLOCK"` | `"WITHDRAW"` | `"REVOKE"` | `"ACTIVATE"` | `"REGISTER"` | `"NFT_IN"` | `"NFT_OUT"` | `"STAKE"` | `"UNSTAKE"` | `"WITHDRAW_UNSTAKED"` | `"BURN"`) ### Operation diff --git a/libs/ledgerjs/packages/types-live/src/bridge.ts b/libs/ledgerjs/packages/types-live/src/bridge.ts index 8c7fc15fc20c..690be832690e 100644 --- a/libs/ledgerjs/packages/types-live/src/bridge.ts +++ b/libs/ledgerjs/packages/types-live/src/bridge.ts @@ -6,7 +6,7 @@ import { BigNumber } from "bignumber.js"; import type { Observable } from "rxjs"; import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets"; -import type { AccountLike, Account, AccountRaw } from "./account"; +import type { AccountLike, Account, AccountRaw, TokenAccount, TokenAccountRaw } from "./account"; import type { SignOperationEvent, SignedOperation, @@ -182,6 +182,25 @@ export interface AccountBridge { * @param {Account} account - The original account object. */ assignFromAccountRaw?: (accountRaw: AccountRaw, account: Account) => void; + /** + * This function mutates the 'tokenAccountRaw' object in-place to add any extra fields that the coin may need to set. + * It is called during the serialization mechanism + * + * @param {TokenAccount} tokenAccount - The original token account object. + * @param {TokenAccountRaw} tokenAccountRaw - The token account in its serialized form. + */ + assignToTokenAccountRaw?: (tokenAccount: TokenAccount, tokenAccountRaw: TokenAccountRaw) => void; + /** + * This function mutates the 'tokenAccount' object in-place to add any extra fields that the coin may need to set. + * It is called during the deserialization mechanism + * + * @param {TokenAccountRaw} tokenAccountRaw - The token account in its serialized form. + * @param {TokenAccount} tokenAccount - The original token account object. + */ + assignFromTokenAccountRaw?: ( + tokenAccountRaw: TokenAccountRaw, + tokenAccount: TokenAccount, + ) => void; /** * This function mutates the 'account' object to extend it with any extra fields of the coin. * For instance bitcoinResources needs to be created.