From 01a96aa8c59c45ae9ecd405418df954ced951ae6 Mon Sep 17 00:00:00 2001 From: aristides Date: Wed, 18 Dec 2024 09:22:00 -0700 Subject: [PATCH 01/12] add empty asset list message on manage asset page (#1752) --- .../manageAssets/ChooseAsset/index.tsx | 33 ++++++++++++------- .../manageAssets/ChooseAsset/styles.scss | 13 ++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx b/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx index 0c4f05a7cf..3bf6883bd7 100644 --- a/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx +++ b/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx @@ -172,18 +172,27 @@ export const ChooseAsset = ({ balances }: ChooseAssetProps) => { ) : (
-
- {isManagingAssets ? ( - - ) : ( - - )} -
+ {!assetRows.length ? ( +
+

+ You have no assets added. Get started by adding an asset + below. +

+
+ ) : ( +
+ {isManagingAssets ? ( + + ) : ( + + )} +
+ )}
)} diff --git a/extension/src/popup/components/manageAssets/ChooseAsset/styles.scss b/extension/src/popup/components/manageAssets/ChooseAsset/styles.scss index 8ff92c68c4..f97f58555d 100644 --- a/extension/src/popup/components/manageAssets/ChooseAsset/styles.scss +++ b/extension/src/popup/components/manageAssets/ChooseAsset/styles.scss @@ -12,6 +12,19 @@ left: 0; } + &__empty { + display: flex; + justify-content: center; + height: 100%; + width: 100%; + color: var(--sds-clr-gray-11); + text-align: center; + + p { + font-size: var(--sds-fs-secondary); + } + } + &__wrapper { display: flex; flex-direction: column; From 299de5cabb235f6b8f1ee2b4ba95e493f38b319b Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 20 Dec 2024 12:12:18 -0500 Subject: [PATCH 02/12] add API stubbing for addAsset and test gh action (#1749) * add API stubbing for addAsset and test gh action * fix yml ternary * try empty string for ternary * fix stubbing for stellar.expert API * all multiple workers when not in INTEGRATION MODE * don't allow multiple workers until all calls are stubbed * adding central location for stubs --- .github/workflows/runTests.yml | 2 +- extension/e2e-tests/addAsset.test.ts | 17 +++++------------ extension/e2e-tests/helpers/stubs.ts | 22 ++++++++++++++++++++++ extension/e2e-tests/helpers/test-token.ts | 3 +++ extension/e2e-tests/test-fixtures.ts | 15 ++++++++++++++- 5 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 extension/e2e-tests/helpers/stubs.ts diff --git a/.github/workflows/runTests.yml b/.github/workflows/runTests.yml index 0517e5b371..20c8408705 100644 --- a/.github/workflows/runTests.yml +++ b/.github/workflows/runTests.yml @@ -20,7 +20,7 @@ jobs: - run: yarn build:freighter-api - run: yarn build:extension - run: yarn test:ci - - run: yarn test:e2e + - run: ${{ github.base_ref == 'master' && 'IS_INTEGRATION_MODE=true' || '' }} yarn test:e2e - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: diff --git a/extension/e2e-tests/addAsset.test.ts b/extension/e2e-tests/addAsset.test.ts index 2a373159b9..5e8319407c 100644 --- a/extension/e2e-tests/addAsset.test.ts +++ b/extension/e2e-tests/addAsset.test.ts @@ -1,13 +1,13 @@ import { test, expect, expectPageToHaveScreenshot } from "./test-fixtures"; import { loginToTestAccount, PASSWORD } from "./helpers/login"; -import { TEST_TOKEN_ADDRESS } from "./helpers/test-token"; +import { TEST_TOKEN_ADDRESS, USDC_TOKEN_ADDRESS } from "./helpers/test-token"; -test.skip("Adding unverified Soroban token", async ({ page, extensionId }) => { +test("Adding unverified Soroban token", async ({ page, extensionId }) => { test.slow(); await loginToTestAccount({ page, extensionId }); await page.getByTestId("account-options-dropdown").click(); - await page.getByText("Manage Assets").click({ force: true }); + await page.getByText("Manage assets").click({ force: true }); await expect(page.getByText("Your assets")).toBeVisible(); await expectPageToHaveScreenshot({ page, @@ -34,14 +34,6 @@ test.skip("Adding unverified Soroban token", async ({ page, extensionId }) => { await expect(page.getByTestId("account-view")).toContainText("E2E"); }); test("Adding Soroban verified token", async ({ page, extensionId }) => { - const assetsList = await fetch( - "https://api.stellar.expert/explorer/testnet/asset-list/top50", - ); - const assetsListData = await assetsList.json(); - const verifiedToken = - assetsListData?.assets[0]?.contract || - "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA"; - test.slow(); await loginToTestAccount({ page, extensionId }); @@ -51,7 +43,7 @@ test("Adding Soroban verified token", async ({ page, extensionId }) => { await expect(page.getByText("Your assets")).toBeVisible(); await page.getByText("Add an asset").click({ force: true }); await page.getByText("Add manually").click({ force: true }); - await page.getByTestId("search-token-input").fill(verifiedToken); + await page.getByTestId("search-token-input").fill(USDC_TOKEN_ADDRESS); await expect(page.getByTestId("asset-notification")).toHaveText( "On your listsFreighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings.", ); @@ -83,6 +75,7 @@ test("Adding Soroban verified token", async ({ page, extensionId }) => { }); test.afterAll(async ({ page, extensionId }) => { if ( + process.env.IS_INTEGRATION_MODE && test.info().status !== test.info().expectedStatus && test.info().title === "Adding Soroban verified token" ) { diff --git a/extension/e2e-tests/helpers/stubs.ts b/extension/e2e-tests/helpers/stubs.ts new file mode 100644 index 0000000000..72b40aec28 --- /dev/null +++ b/extension/e2e-tests/helpers/stubs.ts @@ -0,0 +1,22 @@ +import { USDC_TOKEN_ADDRESS } from "./test-token"; + +export const STELLAR_EXPERT_ASSET_LIST_JSON = { + name: "StellarExpert Top 50", + provider: "StellarExpert", + description: + "Dynamically generated list based on technical asset metrics, including payments and trading volumes, interoperability, userbase, etc. Assets included in this list were not verified by StellarExpert team. StellarExpert is not affiliated with issuers, and does not endorse or advertise assets in the list. Assets reported for fraudulent activity removed from the list automatically.", + version: "1.0", + network: "testnet", + feedback: "https://stellar.expert", + assets: [ + { + code: "USDC", + issuer: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", + contract: USDC_TOKEN_ADDRESS, + name: "USDC", + org: "unknown", + domain: "centre.io", + decimals: 7, + }, + ], +}; diff --git a/extension/e2e-tests/helpers/test-token.ts b/extension/e2e-tests/helpers/test-token.ts index 9f8dc9d02b..0a913ea242 100644 --- a/extension/e2e-tests/helpers/test-token.ts +++ b/extension/e2e-tests/helpers/test-token.ts @@ -1,2 +1,5 @@ export const TEST_TOKEN_ADDRESS = "CC7YMFMYZM2HE6O3JT5CNTFBHVXCZTV7CEYT56IGBHR4XFNTGTN62CPT"; + +export const USDC_TOKEN_ADDRESS = + "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA"; diff --git a/extension/e2e-tests/test-fixtures.ts b/extension/e2e-tests/test-fixtures.ts index 02bdcdc36a..dc1c6f9022 100644 --- a/extension/e2e-tests/test-fixtures.ts +++ b/extension/e2e-tests/test-fixtures.ts @@ -1,9 +1,12 @@ -import { test as base, chromium, BrowserContext } from "@playwright/test"; +import { test as base, chromium, BrowserContext, Page } from "@playwright/test"; import path from "path"; +import { STELLAR_EXPERT_ASSET_LIST_JSON } from "./helpers/stubs.ts"; + export const test = base.extend<{ context: BrowserContext; extensionId: string; + page: Page; }>({ context: async ({}, use) => { const pathToExtension = path.join(__dirname, "../build"); @@ -15,6 +18,7 @@ export const test = base.extend<{ `--load-extension=${pathToExtension}`, ], }); + await use(context); await context.close(); }, @@ -30,6 +34,15 @@ export const test = base.extend<{ const extensionId = background.url().split("/")[2]; await use(extensionId); }, + page: async ({ page }, use) => { + if (!process.env.IS_INTEGRATION_MODE) { + await page.route("*/**/testnet/asset-list/top50", async (route) => { + const json = STELLAR_EXPERT_ASSET_LIST_JSON; + await route.fulfill({ json }); + }); + } + use(page); + }, }); export const expectPageToHaveScreenshot = async ( From ad6962dc9230f7ce957d47506fe9009165f64ab7 Mon Sep 17 00:00:00 2001 From: aristides Date: Fri, 20 Dec 2024 12:25:34 -0700 Subject: [PATCH 03/12] [BUG] Make signMessage API SEP-43 compliant (#1755) * add v3 and v4 response types for signMessage, return v4 response according to package version * Added translations * moves version toggle for signMessage to the background script --- @shared/api/external.ts | 2 ++ @shared/api/types.ts | 1 + @stellar/freighter-api/package.json | 5 +++- @stellar/freighter-api/src/signMessage.ts | 25 ++++++++++++++----- .../freighterApiMessageListener.ts | 11 +++++++- .../src/popup/locales/en/translation.json | 1 - .../src/popup/locales/pt/translation.json | 1 - 7 files changed, 36 insertions(+), 10 deletions(-) diff --git a/@shared/api/external.ts b/@shared/api/external.ts index c0f674a882..ace3fe5b60 100644 --- a/@shared/api/external.ts +++ b/@shared/api/external.ts @@ -94,6 +94,7 @@ export const submitTransaction = async ( export const submitMessage = async ( blob: string, + version: string, opts?: { address?: string; networkPassphrase?: string; @@ -110,6 +111,7 @@ export const submitMessage = async ( response = await sendMessageToContentScript({ blob, accountToSign, + apiVersion: version, type: EXTERNAL_SERVICE_TYPES.SUBMIT_BLOB, }); } catch (e) { diff --git a/@shared/api/types.ts b/@shared/api/types.ts index 270900830e..f560816d08 100644 --- a/@shared/api/types.ts +++ b/@shared/api/types.ts @@ -115,6 +115,7 @@ export interface ExternalRequestTx extends ExternalRequestBase { } export interface ExternalRequestBlob extends ExternalRequestBase { + apiVersion: string; blob: string; } diff --git a/@stellar/freighter-api/package.json b/@stellar/freighter-api/package.json index 088169c0a2..c471176b72 100644 --- a/@stellar/freighter-api/package.json +++ b/@stellar/freighter-api/package.json @@ -15,7 +15,10 @@ "start": "webpack --config webpack.dev.js --watch --mode development" }, "types": "build/@stellar/freighter-api/src/index.d.ts", - "dependencies": {}, + "dependencies": { + "buffer": "^6.0.3", + "semver": "^7.6.3" + }, "devDependencies": { "@lavamoat/allow-scripts": "^2.3.1" } diff --git a/@stellar/freighter-api/src/signMessage.ts b/@stellar/freighter-api/src/signMessage.ts index 34b77bde34..7f1776572e 100644 --- a/@stellar/freighter-api/src/signMessage.ts +++ b/@stellar/freighter-api/src/signMessage.ts @@ -1,21 +1,34 @@ +import packageJson from "../package.json"; +import { Buffer } from "buffer"; + import { submitMessage } from "@shared/api/external"; import { FreighterApiError } from "@shared/api/types"; import { FreighterApiNodeError } from "@shared/api/helpers/extensionMessaging"; import { isBrowser } from "."; +type SignMessageV3Response = { + signedMessage: Buffer | null; + signerAddress: string; +} & { + error?: FreighterApiError; +}; + +type SignMessageV4Response = { + signedMessage: string; + signerAddress: string; +} & { + error?: FreighterApiError; +}; + export const signMessage = async ( message: string, opts?: { networkPassphrase?: string; address?: string; } -): Promise< - { signedMessage: Buffer | null; signerAddress: string } & { - error?: FreighterApiError; - } -> => { +): Promise => { if (isBrowser) { - const req = await submitMessage(message, opts); + const req = await submitMessage(message, packageJson.version, opts); if (req.error) { return { signedMessage: null, signerAddress: "", error: req.error }; diff --git a/extension/src/background/messageListener/freighterApiMessageListener.ts b/extension/src/background/messageListener/freighterApiMessageListener.ts index 50d7bc2db9..471c059d44 100644 --- a/extension/src/background/messageListener/freighterApiMessageListener.ts +++ b/extension/src/background/messageListener/freighterApiMessageListener.ts @@ -3,6 +3,7 @@ import * as StellarSdk from "stellar-sdk"; import browser from "webextension-polyfill"; import { Store } from "redux"; +import semver from "semver"; import { ExternalRequestAuthEntry, @@ -288,7 +289,7 @@ export const freighterApiMessageListener = ( const submitBlob = async () => { try { - const { blob, accountToSign, address, networkPassphrase } = + const { apiVersion, blob, accountToSign, address, networkPassphrase } = request as ExternalRequestBlob; const { tab, url: tabUrl = "" } = sender; @@ -339,6 +340,14 @@ export const freighterApiMessageListener = ( allowList.push(punycodedDomain); localStore.setItem(ALLOWLIST_ID, allowList.join()); } + + if (semver.gte(apiVersion, "4.0.0")) { + resolve({ + signedBlob: Buffer.from(signedBlob).toString("base64"), + signerAddress, + }); + return; + } resolve({ signedBlob, signerAddress }); } diff --git a/extension/src/popup/locales/en/translation.json b/extension/src/popup/locales/en/translation.json index 3dfdc9c999..2bf724e8d8 100644 --- a/extension/src/popup/locales/en/translation.json +++ b/extension/src/popup/locales/en/translation.json @@ -528,7 +528,6 @@ "You must have a buying liability of": "You must have a buying liability of", "You still have a balance of": "You still have a balance of", "You still have a buying liability of": "You still have a buying liability of", - "You won’t be asked to do this again for the next 24 hours": "You won’t be asked to do this again for the next 24 hours.", "You’re all set!": "You’re all set!", "You’re good to go!": "You’re good to go!", "Your account balance is not sufficient for this transaction": { diff --git a/extension/src/popup/locales/pt/translation.json b/extension/src/popup/locales/pt/translation.json index e62966279f..c210e3c227 100644 --- a/extension/src/popup/locales/pt/translation.json +++ b/extension/src/popup/locales/pt/translation.json @@ -528,7 +528,6 @@ "You must have a buying liability of": "You must have a buying liability of", "You still have a balance of": "You still have a balance of", "You still have a buying liability of": "You still have a buying liability of", - "You won’t be asked to do this again for the next 24 hours": "You won’t be asked to do this again for the next 24 hours.", "You’re all set!": "You’re all set!", "You’re good to go!": "You’re good to go!", "Your account balance is not sufficient for this transaction": { From 916a37ca0cbfdb890aa0575aad581112c7e1cdf2 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Thu, 2 Jan 2025 13:00:29 -0500 Subject: [PATCH 04/12] Feature/show xdr during send swap (#1758) * show user XDR string when sending/swapping * Added translations * fix duplicate test-id * fix type issue with memo --- @shared/api/internal.ts | 2 +- extension/e2e-tests/sendPayment.test.ts | 2 + .../send-payment-confirm-chromium-darwin.png | Bin 21835 -> 24251 bytes .../SendConfirm/TransactionDetails/index.tsx | 65 ++++++++++-------- .../TransactionDetails/styles.scss | 14 ++++ extension/src/popup/ducks/token-payment.ts | 4 +- .../src/popup/ducks/transactionSubmission.ts | 2 +- extension/src/popup/helpers/sorobanSwap.ts | 2 +- .../src/popup/locales/en/translation.json | 1 + .../src/popup/locales/pt/translation.json | 1 + 10 files changed, 59 insertions(+), 34 deletions(-) diff --git a/@shared/api/internal.ts b/@shared/api/internal.ts index aa296d6648..174a030a7f 100644 --- a/@shared/api/internal.ts +++ b/@shared/api/internal.ts @@ -1467,7 +1467,7 @@ export const modifyAssetsList = async ({ export const simulateTokenTransfer = async (args: { address: string; publicKey: string; - memo: string; + memo?: string; params: { publicKey: string; destination: string; diff --git a/extension/e2e-tests/sendPayment.test.ts b/extension/e2e-tests/sendPayment.test.ts index 7bc9934bfb..dc6df5cbdb 100644 --- a/extension/e2e-tests/sendPayment.test.ts +++ b/extension/e2e-tests/sendPayment.test.ts @@ -6,6 +6,7 @@ import { PASSWORD, } from "./helpers/login"; import { TEST_TOKEN_ADDRESS } from "./helpers/test-token"; +import { toBeVisible } from "@testing-library/jest-dom/matchers"; test("Swap doesn't throw error when account is unfunded", async ({ page, @@ -81,6 +82,7 @@ test("Send XLM payment to G address", async ({ page, extensionId }) => { await page.getByText("Review Send").click({ force: true }); await expect(page.getByText("Confirm Send")).toBeVisible(); + await expect(page.getByText("XDR")).toBeVisible(); await expectPageToHaveScreenshot({ page, screenshot: "send-payment-confirm.png", diff --git a/extension/e2e-tests/sendPayment.test.ts-snapshots/send-payment-confirm-chromium-darwin.png b/extension/e2e-tests/sendPayment.test.ts-snapshots/send-payment-confirm-chromium-darwin.png index c1cf8bcf4039cec9b92273dac4e7d6e21f8de009..ef75b409c44e3918f2d03166fe59f12e9d702b30 100644 GIT binary patch literal 24251 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%0DA?dI5kjp^(#kfF{ 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 literal 21835 zcmeIacT|+?yCqnF0tZAmCKMIKgdjl$M6#lSf&`Hu8AQoB=RyTDqLNgS2qhVmAUT-8 zM9x`2a+X|BFuU&QJ2O42*X^}tR?nTD{?#8AYEkul@B4)P?7iQ*uco5F!o65juJpf- zsh(hZ*|*_v-)WUsPTS2tB!5u1GdpZhwQM!`L?`@))|>91ZHkeNdz6kbsA-%#w{HWF z#_7w07c1z77Ax1fW(Tgl*#98ksHUu1$9u|Eri0UQVI?iMMXR-=gZWiyTWjm@-@hB( zx8p)Q4(7Lh+A62hL%l`=EP)+S9ia>MUfIe%&4(9_}h~U!VTO%A*>i zpKVt4Y5n3d{-zo$Lzyg^V&cALYI^(Dt@IB1L#Dt!n-gjlew>Oaeq)qEFpQY)(zP@(X)H(}VT3Wa9)^}D-eB7Vy%P4O1 zqfLb=IwW=9g*J0HH@6?*0?f?J!pD#Q#mML}-=ux?=+R|iMnZY)enLNQ@5o4jABCKh zl(hY*R-?NBfg?uS(37Mbd2%a{$|hFU2;D7Pwq)oRWZAT3MHu;cQpoeUW6j^cW5M={ zJN<4>*OR-K*W*{T+1jq|_4y*Vi5>h`-} zD2TGuADpRQus>j@l!pV>PCG+i+uP2KA+ zEk?u=r-AQAe#-;Xy`R?B*3RDDOqpv??eQ8*|M>A^x?Y~()wjkY^`rv|kH_1x&DGWG zwT*m98hqwIpZ5+9hCg{Sja@Y}H*Y!8@!`W6#>?vJ>Lrd7?YX6;rB=DRsZC7WFHhP^ zm{vS8kM&&`tcon7EOg+C61uP~d7iU_TIsqhdnFn_e?BNBwb~ynn~|0l`u;JOY5!8~dr>(Cqg&>&d$YY?V$0D2(7stk@TDN2{Y~FLqdGP0OU3^QLPXE}L zo|+ol?%f@^)-BBJg8HRCI*h$zW4pI+KXju-(4a6)C%fYF=j&B3Mc=%6V^HX#Q{v^S zsHkXRG5k=dba-UM^7?g`(Z*zH?}bO4(qzhF&w5MQnr3`)LPEm#@84etn`>!m?v?c` ze*0F9(Qy#lXkL9mGQXgJg2X5&D8PbbzkByW(tT36MryYAE*BGbaY;#}h^4xg)_Vks zm{{IB)5^i$zi}P%jK{I3*n|0@SYq|JZ@cieZ2QH;7T>M969~Va9w5Y=f8XmWVY|jzpUCsW3f};z#x!K%)Yz0Qiig!xcKJ}hp^ct7ndBiJ_JNp z8F4+wvcc@Q$k%uU19IpNN@fyNS zDMGNxDmNx3M(*C*w{O2ZWM}3+AuK#p9bI(eM>t6-f{7bfL(jtD;(l@0u3b~bv(>mF z*_H8ZEYauBpTB4b)NM zhsT`Kd?im_FU6g`n~gujaWrxIq6Gw zvAmxaBkg_X&K-{3yCYvvAJI~t>LhhfZ*Qf_I{^U~cm$jHDNMim`{T7ziR2NU{N(WKnoR40{Tk70NCaviIBDN2ezx)v`7zVjD>8645G*PhY`+bcZn5egKdg}$sJzrtbte&bA}LvC4ZkHPPAnY zadIJp)9Nh7Cnn|v(sgrFCjO!!On>Gk^josAvoB)Ly*)kC4NG)6J`?;IvTQp}a&d7{ z=8V66eR=>*fh1o3?b|oBwUZ}LlG72H_wV1IzdXLKef4T9x=lsU?w%4}X=!OmNl97R zGTV;#d22s@{v1dCW^&q1g-1p42Ml)>WS5dE%Kie@;Xr-9kN!qYk&X_%!etf$=hs5l zv1cJ6s5N4Cou^o-k6JIyM)`l`vwygJI#$||MK;;>58@bBL_SO%}O zWdsi2kt417j;4!~T>||4Hnz4ab0f3e-czJEZz?J)nYq!$YLPk!v}BDG#HWzn`=8&w z@g6!f!0F2=?KRh)YfUZW^$GWH-u#g7*q@f3&eboDHL`GIJr=`>g8%6*tGO~iAy@74 z<>P*gpV^>tSJGd|`YiPB@{lHQAe%>fN|8(3pHhJ6(7C zmM!W!I-V#RO{v-p8#YK~I3sZC)H}WTwoQAq{_hBFAHRTrUXI0O4GkBx@FJpGgM|on zr&Cf2T}JA+W{t+EC%d@1+P3Eah>XmSwb=KR8kjQZ>2>T7&;`oV$IE0^G*X-J3SL3M zQm=Wd=+5p<1pV>ezs?XUw%#ne4^*dmTySAJ;1?&_?EeA~@4t&=sTeScPF`Lf?TjNx zo}|vZ?EzVk0sVtFu3?1l=usslrK!$>D_UAwJp%;RhlEG;H5C=|@pap_Z@*z?hQ=PP zqTRC{Kw9e1Hl{$`bR^jO+*|Q5ownsrRv0>o%Z;M=AluvTT#w2O< ze!c?-4g|@EL`LfM1Y!@rp0~5hkdHrp;zY`noWCyKB<_T$r~n_|wQ+Xb#=E)^m-FY( zj6d1OWgfl&0dBpF})+wk1gZN{=QX_Z&g~EP!o}uC1>&?q{EnZn_rm zr~CHP0)PA0sqt=O8kD8OeVUDesb#$&?NoaXxMX5mhb;C#$ zFi>?!)P9Al8YDN3%f@xF;ER62{g{oJ1c`1ttvMvHu? zH-aUy|C(R)K5?9n|Cb#l^+>`7pVxckgB%?Gnx~ zDg%D4K6MQ5QhWLxlgRHc9Ub1N4CJ}UhzK!&%vY~o3Fy5)b?Ve34pFPdq|11lNHKfA z{vff1sh-S+ksRF#v^fc%B?oi!A(d!}ro~;jH-{dhuiby!# z%YeayiY)*MlbY7WhCZUN0Bizw)>?=F8K4t2yiqP%X)X0#Iehr=+Ds)Ea!B+fN*~|x z<1;^m%F>!-F`#d^#l6JhA+S#jQVoUeXSVi!*U@a&mBRFg7+eG3iy{ z@^zX1bq0NtNF-vGW1w{AdJ4@ z;csZuTBJXQ5d*lux*~Ub3T><$Bbo>{`0&Al4%{>a+=R38b8WfB#jC(ZOo7+1)8sjC zz}M%`pSKu#3miK(js12Ws+M*cR;!7TCa%s@q6PcRR*BWexYz6}!+`ZjJ z(!T>g_kOxt_46mL_>KKYL!#>C%XN4vc;es5=Ffcq2~htK1r>Me?@t)vE=T5-t*@=1 z>#i(L5pe;y>T(;uRwJb+Rym zAy=u`g1EjG>+|P+L_|x%#SndceIFm6gfJd#y0t+$h8PqC376rwSa$|p=hyl9r}poc zTQ6xU@pd;g?Z-U|+X9*`C(Q>2$i*?TuuL>01}l=b$UTpYbf0L~e95wSWNc&1xedl& z@AZ`dRGfPGlp|A`tilvY zvc=6CuU@@+fx8$0idDj6HwVXPf4}LqYx^lym=ePxBDBX8(cW&)K4IU2uG!t)U7vCU zWC?%ON<5}9UH=GWWXi<}EiQ~&ktJmJoxgNF|@aW`ietPDol zb8v778Wt}hWv;xr1nS~Ktc)+nieJOS`G8&Pt3$Gw8k#Lc>c3yb5Ng@+KHZNdrOe<> zU*_CY5Aj#|%~Xu@n2pb!`;2&8AGKs4ro4SS{rg)8m<6l}T2I;fn)k$a)LfRjvxXNg z+%88Ni;9X)pf;8zoPCdC@f5i-;!+6q0g0e+?i?bn7u$i?MavRPqocdP{(zy7b;bVb z?ovu@00OUd#fB`@Au8a z8h7v9Sr~1y{`!Rb`0?Wy&~lHU$%~wGb>%iOHy69{eOsX1-#d0JP=iOGik3WlP$|Nn zK0R*oG2qXiKU9kApFZp7=a+3>!zp6%#P5+dS&&yqs0SterKmM3S2GGt@^NWt3Mzl@ z((p#+9anXAkzl1+S?bhTtk7$I6kGsCOfOIX^)i!_lS0MQa`W*jF;Y5|35=Y;xFR4h zP>Q8LQ(=gOv6U}3hO$gzP+u7M=7eWXzjEM6KR#sLP7D*)~lqhj$ zj3y-|CFlfgxTxY{DN2Js>0J+QKvib>dQ*yKw1g`etdX*^GAZ%*UI~{Y0s<3fWZkio zXk}N6+-%j=pOreIQ=PrD>4mtn+10DhMWQ-z!{~lmvJT|A5x}MOlVW0Ge0++!?`OEz z&`(dF{d0_c3y(@vQa6aP`a)OhLD{p_+gN8WvA@dr2l-3Ya-&}WU3k{A{#jUXb9I&W zCC}l4v1)I$QuFM4WK1hI1%|Nz8hYo>)@YK-I@QtNHJb&G58+NJ&RGy^e4>)%0*iHa~TN|0d zVkF$JlDk)5Uw`Ws?T9AgXhErH&O*7^j9{O==pAii-O8Ddhd5N1M0FDUdAM^0=P)O{x z4)5*>aui2J?ZL=$<;oSX3t@`q>Cr%wj!HylWMySxr6naJU(|RovH8!4965TlU+4s` z+TYuhNd(V?(>oZ;sm}zReFW-;5U$@Opp;{9*+@&pzW?*Qz+$@5Dz~J>7Zn7xt+~<*kqQGhlccQnqE=BWbn4)G+E+IIo zu#}m=sTj;DO}}ZUv5gIX2`W|>C>-#$qtgdu$iwmI>Y!+g*T`c4V4v==xYnnGS;cmv zcSg@)@dMc8;?#7mYJ`OjfB)gCYkOY)j!Nowxg(oc!YV4lHg1$lEQ+;5-Cn3u;PUkH z;@if=jkZZfa!|p!ww9Kn;H>+=u&R%aBr1JKxj z#TyHp%@H=-=R7?{L2FgZPJRzN>e`A4sRdvVrQ6`HFD+r8bMx)Htqf(J zrZe8>6Aw7MPkrwyQL+-7AFcK1E!t&W`n@Y}(Oz=q&mR;l5CP3)>y#;gy>J0NOsr^z z%^yCPfb%AIRmaMf*>y=+ZfI(1nh#_*Tv}V1@}3_Jd-m)Pn5>Z^CLsAdyJ#78!$KEJ z>hZei=p$|*#u`ncj~qEt8^L|~$TkpKE)EX)ew4y(e^8w5EiIRqhhi1Z zp9d(w=K>zNV){WfHM6lvTPp`AZFb{^<`l-JcNwD_IE6tL9J9Bd>g?>q-j$}LC^A09 zqQu6^qN}^RFJ4JhX7F>b)2dy}efe zJ|)G)Q49AI zDX%AqnOrHe^Yim_b5Q_Jp?>o+YfGl-S{;~l%3l6`*uXS2JbcYI8Fzz?>4nwC3*gPN5%b!rorOlMo0PT_e4#=ki@ym~)w9GOS#+}X5?y0+u3 z$FZ<^-CjSct5p=MF|&p6sDdbGW@XLG$=L(R=H|_tY#)J7A@V(b{1_oMI5>#JAO}0U zQXgT4K>t3U@BNKT0e1|(AuBU8Gd4DMTfyzyx3Rx~1sG*;*Vt}Ex3z{Ja)R2N&Ov}P zF^LNd`~p7x=g*(uJ-9eIyD=$z3*pITL&IIHDX+U2r=`?Fhqgs}`cw|%(h&_l{PeN> zH2I}gQWO1SmBr!8PoK^*UZ$`+T$Gou05bdd>C^h$5m5WTe*MD4aqr%}mE;9zhKLYs zt+1$Q^obh}fUr=+=JqA77=x-PUFxHbs+eAt@{ae5Mns_R{+k!TeZ2KDqa%W|$UVHe z1XVFOgY(}+Uixv6>#hzC!&G(9t_!Fs!?1*do%-OR+;Dev^qedh0#n3YHGBWjBWef) z2iFLdt?my1tpa8B8NFFULjy_g8o-&?;-o;pRm`~HjWJfOuCCIva;_%E8iE-E&jWgq zJK*;1v$*&`HWW^GS671)uiP9q?d#WnN7|PrvkrM7+uWR-tQy_~&FvMw_L-5SUf?#N zYuVQTb<4c!WfSlbs+CkY@d8j=@qe zs6$=Iuc6}#m5aMt=#m^C-@i1|FAP~n9xKCrPFJ_RYbEi`8z%0ksI@8cTP(XzUSOAc z2K1)M#ZBPYfs*(ZD?$}kz|T^N2w-R2I93c1n}r4iv;yS7hi?aYY#HcRz;;r+I*jOSh$t5(_~jLVDJ2H$5_w77Z{5a zGlPPHFk;WqEFq5(4q;o~#K)VW?ouVd`&)UshvW{{Khs8P!NWiXPd7F6q}_8Ow*^~G zeaY9a7kuOA?p4Z0zZFPlG=P$1FYY=Ty0&&=L~fu?lUd3Nys!s&Xgo0eH(7`v+mM); zKfAJSJWNkdS0ufZCHl=rKYZ{YfgMn>UZ6;hk++gBOfgImVf7*+BC%&3g9*9IDrxPD znvM>y@6)S#dMy`=VHWM$Q7((yt|%X<4Ze~Jh+rzQp(vpdh;2yLF1K32l@ zJG7+s9I&0E*@e*JmND9n^dRiXce(tiY~m#GP&F5E)e)Rg85#y9c(-g4nxHZa6{xhL z%@N@herO>;srvm-iWu%fi>lmz)x(Mn^mSW%y9x*!jBA*JBXkv&l!Et4qEp8 zI4)>Zs;@n9pGa;gl=a?Ztf?8Mm71HI+mr_w>LD*PAb>)N2C0Kdp0u*Uo^seKGmGw zAx&-^ae<75aBs=eAo|LJMcKP|FW9w&Jh(mfWNjjj;`-I;x+SyJv}XL((#od3F9j&xLp_>3~|eN})j0M_~mD{xvkTnCG4zaQALU zN5{(2Qc{zZo}QkOQBT=RT1q07K+x1YBwdw>Y&>)34AIr&;>G)OdRZvY;QXN{7rITP zs3!w7!WjdIY<1}YF!EyfJ3oVO(nG3Y2M?R28Mz1V4Pe8hW8^b)9-Aa)(+bJj)ZV@a z+$4%RqN=E3IukirPK2Oi+m=Pr$u{fAvnMWXb#!s*d~d5~{A)dM8C6ot=&)DoTNAJx z&JGSwIJ>F+8cbkEdprGx4QRbeHGBMEyTPvcfqDaJSUO#n5ZfiY{Cg`i^FvhU;GM@` zqi#W1KrE6$Rh>9-LM!d+%(OX;)Qjh|LG=6w51xxtn;UH!<62+3+o%r06C{G=#R}9t zct(PD37dhIg(?D44n*mC2Tyynw0HBjZ3el`H<~A$NrzQq zs0a*d3_#2J$k_=YgTl}kFT^eD6MznYhLI(~d&HfVrh64hCuC)bU4^cTv-{2<%MIFB z-J=GJZ5Q+-io~`@Kfbi?F+y?qGg~8zp|u(&r$T$J#D+sWJmBY>zJDJDaIT$d2H_Yi zyLQzpIwZsymn+uo(J#Yw+r=If1F%cd+L1RW5fHJT#)pSn%lPJ zA#Uz^JbJeYki|5bby4k*C2hgf??OS0?!PT94iGi_3 z-?;t~m(1&UwDZQQO~bT#VF7ud53qx3%LLl;9>T z5wsOZc?x-ZT0ZHI)41LvJmUFyWmfDRpgn9#t03k`L*p0?Cu2cbg2c$q${H=gWbN-Y z+o(St7l$!hEdtaO`kttDbK39U*O719+AXLWa24ToJTwQ&J@!i}%Q9gMgi?fr4Gj&& zXHLKMz<(!>Tw`V7I8c6iZ^MWSI`gkyDk)MPa;I`1^@snL#`Q<^Hc;+k{m7O;*FkQ$ z%zr0Fot#n<>v0{NTxZXILX?6{E+{P2vZsy_(2h{KMl(fzeUl@|--Zkz87Ox-p0xHI zm!8ChK*Oukr}+L&PGZr+YzBi>5SJvZQ3zZu;(5HR@ivwWqa}v7W_RxL5G4b+D_IW- z3pc>T)6gJ%=+JqqciGv_E-q3Izx*FQeCU^l$)1HnU10I8xL{L*F9`!2 zIRBqjRp|AP_lVbL8p(nRK)D07!f>JJ-QL=Ya!C#R)FuSE2?$n9JoAnfxJ9BJ@{^8X za}+`ngZTF!Kc>3lz=zqF%xgh@0Bc=>zu_Z5$}@4BRlN+0jP!<`4N3^KS4cm_i{0Mn zaP^lV9QmLGMT%OxK|}#ki8_uc*i*bG(psN zYw&7tprMGKKJCBu-h_uI5qgsp00y5KaSGVQYKtJwg>Jh49eZJ8JuuV$M;^6Q0k_2c_@(EO6nJu1C* zPa9CF$w;}ld%?j|9eJ7A*|TVpB*GppE+gMqu%y^*@Di0Mm~!`^G5GuYLttRvu>&qH zWo58}FC!v&0zB3!ixU$Q>;k$P`ua9zX73$(<&Z~>jg6q2`NF;<2gAd{{^EwHa0-cr zWtnC^0#Wnz=PzF`IXh;vXA3d!6(goBtS@Qg^xMQ@B=%=t)ph>Bg zG{zCXU!ycvlQrI3HXIfdtcLU$4R5$*34FnI|G3ht1D*8%D~ z;2bkIC{W*tc2Qmm6SrMgp(bS4{t8oSo%jF~o*}jrGumhn!a+bc=ak=Se(52^cbu9O z%un?lt*uw7zX+sd18UyddTeB*4W;3> zz+P%zh+mz<@r@j+w-H2$J4hoU*3JC51oE5LuU|tng+^VxUT+~%gFvSX>M^^1eK_%w z(owm{moK%9%$YG+e1nGt%u@*{m%)Juqj0#|)B&(!y(h&fGW8Rti)&|O#l>|P>!5n- ztv8`;0Gla|r4U&DraEw=RoJ<>dO$A1Bx1>ZcNFqW`|I<}?(Xg+buj10z2+>yCKM$~ z^nCgcpp=Kx!9$09kstL!OH6^UlahwP@9#pzgNu0^(-#;$g&q5-rbl!YZx^bRgNd5m z(Bnf@m2!MtF(zj?BvrJuc7VHPzw&A(^xngVAU>2YT*zM$a)J&+A`M^$M3o3t8~|p< zNIi3!n2#EYMy`N53XcOx{RMU(5ep1dCUJ!5LqCoNYy|33^}03O6aC3I3I;By~uZO+7tj2!_^o zrca+fUF;?{r|G~b|LESmeD4s{dbCt*mi(igahS8@g@viIXIOan=G`aLp#7XYxiAL* zO5Z~vt3B2xCfvSr)FuGak9N1Y%Am9`G>034JAjK6Tx83|8W8g!Rwbaqg@iBD5X$LZpdo?|hf^4iBdX05X3Z|s zAqc$aeHkFrh=rn13qhV)w`C1rGMWp5Ss^bkFILRn5R?n0u>od%4gn~?i|%~oArGmS zgDJG7Y+-ix$G30do`0^FXt|U@4*{pGt6lQqH8h=2= z3SyS34^3-s(lJ29O~ugy#etGL)VHCk`rxMk2=u|_{MQbV#2CCc^sn7J zccM&Q5P1LoJs|(33l|(*U48Fu-8i}n*`EkDR9jOM;F*dYutng@!N<)nEFg_s9UW)F zj%s80hA4F?0H%T4MMd6d#o*OJFJQPNP5s3k@bu|rfD+6>_U7j9@LOUbJ4=1_M$4#> zyd#e|_`)313l0o8tuD=Aq_qrzoxiNC3B0Q#t4`M$cO!gS$dDOR}PivrmEBM-y-v{MjX3#9Xfn?va3kDJ)s5j7UX0m zZt}te7$^_;>XZEuYwc}qUHOg&_V4dQ^{uaF3jEm8((>cSkH$t!QmJqV7q3X-6iwqp zZ2rLDAeDix?|==8e&fdZiH=38UFV%SM1MF}Vbs9L?-aZlj1e595MFaVX8PcqnSh^G zRaMmpZXVxeXN*c9BHhQD1*D|(t^N;x3)aud^+Ea8o4mCi65jhZ~={J^M!#@bOL8?*Haq( z^eK111w}uSOp;kpl|ER0vc^UYwEzIhdRZy9(yikC?|{@dtN^ zlNqp-q&mI?Tm^bzm+M3qL?X)FLkN}N^Q>~oP=Vm!K~j5xz$Ha1z$ShD>yz>EahO5c zIy%bcoAuD&gJ-A?FydK|0=%+&gY*gcyRw`vQH8Ua75E$Ih zYvd5X1cYwx0QvYf06I_+0c>E*{*h@eC@DdKy7&083}6r2ewh3qez-e1DJmr*bSRr}Zz_cauSpvENTtn0$HL(E&#|jb>vas{5$?cGkE0HH{seXHbTZi^0sT~1b z>jN4R%-BT%v=vB45a6GSH0u9D;9juShS85otX7CCZY_gzdh71p-ZOpY;cMhRd*^dS zg&*w7a%ijfUcK4}HyVH#z{60Cw_WAqy=cQ*V><`Tt5X z+F&e|3zx-QstE<+A?yoxbJpV0(`|8s0c`aS86pb9u(Z@$^Lm260;LL4LK^A=br9M6 z9&Y&1g*K|v^2Hb<1hTxZ54QXS&8>U)?Ad?dz`Se^ctRX$B z>_yp3$A^vGPSQ=EY)+4aWeKl;5TQABsj81LcA@_TT`dHTttY5<#H{@M6H&>!4*{5c1 zGl0E(DpxR}Q!rp@NOoN(z6*>6a1BYl3W}tcH+*v?I6OuORycF!CW!v|9SG#RM!ilS zZfyi~LDhOG<>{oRR*STqT5p9<8An4vBqJ(Q{;^4sx2yW!c}Uo#Lv7c_#=p=!(9xZp zoq?n&ID9bH|8DI(3*1AXUm?F?gwsUP0HFqH2K+SQcYJbk4SX(DSV&5;fZ$Q@`@a6f zeLy8LCj`Srt9hd?`zwZqg3|l1_FS{K&s00Y#LPTp&}#IwG#rQBgt}PMa9ZIirdM6v zLS)mZuZ+byFz(;qzJ5(A%@0*1Wxao2ithYrt_{a_5T}?ke4t)|E_yEEItmLFsp#R< z74Vn1Q+N@*=Z0shC8wk=9WUQ{>-^h&%+wyJqOzHp89f=UX%vI{J%iqRu3fu!!sH_k z;oXH13x^&cDPZX_Gs6&v@f=CDwB!NJ7b4gwz;!CxIP!$7au{|koE$16-$M-pWWi|V z`*2SGrY^DL;(-p}MbGu7G#%j)lSS~d96=TEq?RSae~j*$+QmRa@Gso{_uqBJp3W5h zw`jR|(fM=d8Vk))Pv8?868ad24upIlp7W7qt77-)88 zIrLYs;0C}w?1Dz;>3Q)7Z_i5UOAHYQ4#Z=|1$A>ZP7M}iC~ z0Z$=>%>V*uk2Zsq0N0N>)y&@B-q^Snm9)IP{NqQtXcc$RIImytM<)U~gBZnmnWg3B zrkzK?$j$?=`++*2Dp`yH(Te(kXmtl}!0L0K0|Sd=N_v(XLss&PKhP>E{lFe*Qkf3* zD_1bbQfoSB*0DuLcXwhhvB{&tUQzKot^*EEXE>Kf-OXsI(*-v(+*%k!zjWA_8E9%! z^>thyFoYZK4KDBe%FDn4dxXt?Lh%8Q06!6?RMi+MUJ8fAwa@opdcp|;SxB)c(x96G zN}Dl{LwmFbsSdVQk?tep^!mPfFbCg%{)`f_tOpgD(zCr7B(1aaEa0SNt}c!hL`4;& zFx(Yypcc(AZ9|~Y_b%GAXgcV%Jb@Gc4JtPx{3~^#CIMJL08(iMhD|&71%TLy6}1j` z$yUd7E#W??Pl-t`6Lo;>O-G+*P;`=)_hL^zPG6!`{EQM8MhOxYzVe_M5G&2^7j9EE z>eS_uLkABg;CfK)T^++uQ?7vTZEGM66#~$+$tfsID5Ldf!vffFG66-PUR%kPY8~^i335XYAy9F; zqTBRK8pAaV`?j{!!;Pjhn=p3{4_nySP(x{jxMd$06e$fpO|a>JM=_;3y1_%&SeNwB zbpBq7WW*uoZT;6+RT(E~urB`#o^bvTjAzvK_|G0N|8)(78w>s^1j4on>h}Wt&v^oW zJ+s6+_+6IF)>CVe!k;!d_x8P_J0a|}OaA69x1iJ;dLh@|xdkm`G2JRWM4u$zpYg%A zVWQsU%1M%S$hB*>_fHt;b~LJ)wtc?!K5~azfM8#Eo2lCI&PbM zlgmDO8*Ltf8_%z#lNb?ze?r*kpA!_IVtJuJk_IKI~GNSo&UKV zXzni%Dg~UFCcFEpIDG^Dhuri3%}|Ql*Fsl*R}_jN7&{FutwGQmUOV;`V(`Z7!EaPr zoRKkkcGAjce$MfLJ^$WQ`$J}0toBtF78PajEQpO8=#;Gt6~t_m3B3gACBxib!_ct1 zvC-v?o~E`o{3$N*v%vmZBFuhT^?G(2s1_V}amRoy-CCTs)Te5=mj3!<9Nix!FB1uW zPOB*?zpRPjQ2`@s%P#jf9Sh@CoKdc{&t>AwD?fuOn|!7uI1+uIAcK*~t;^vPi*)Ps z8|$^yG>_{-mo_-;$jO=X9dvh(9?`<@eALv8%A|h1iB0QUeN`FEhiI#eK zUGA;ze!=*Q{|Mh63H&u9(Vufmc~mCIg5K}uuY`^Y2t?yF6JBfgJ1E14o}5Lqe5hZ( z`}r8}tGgSH4W;61LiB?P(==WV34D7`y~gNz%6d=oDdJzo)!L=AyM|ZrAAh22PWKF5 z__F+C-2lP(U*RL_!V{uyfcvwCyBUv+3>kAF@NanNf?&CN)uY;Q+r9By%*;X=9M5I< zWtYu-?pRTY3G*EsFn>Tl=<4Q@7db;=|#jedI%987XA7f8p z{2Cf0Q8jTry_Me~sxgrfbo+n&sQFo3MBd#K>9JV$IC@3Mbcd9bNq0o$wuq>}sXN6s ze_q+IJN#i^w`^?Nx%HKh6aCI%p7xH8MAg_*5TI~^bwZWH@nINR2X3>TAYIQ(Q)d$o!j!hYdupH%m9ku1-c4aI(D0+**EGcBa)d7;iT5 z;An;ie6(MPQcfeU`D%}kuHPm^(Z-*ihLSfy=ap?UB zIsu~YPplZujTIP5u>BZruMe$fE|bU^2|U0{GRWjwUl}>ju2Sq>_b18mkXRLm`0JG6 z35u=I8o5?UXwOAngOwM|LhBve)>?aC`ZPUxIYW{h%eId7^{$Cb=Oxvj*_hXD+pCD1edPb1d+epI#b$X+E@{X_3U91fub&Wiiz5@eMpu+1rB-!JM z1D!Bmw$#=-;J~??iwnN9$q!#dwELtD2(;&-)>;|btQ(vhroIL1zgRPIyD|U#nf6-d za**uxSdZW7S6B`7rKfM&HzV2>#A0?{(L1q_b$NN?i=K2l4!@alB_5U(&MQt*9pX#3 zZUrj~#5#O=W#3sOO>*hE_40e&m#(g48;1|15c&ZxM?%RDbN`!u*FaeFl4Qw!snCh= z2(1T;0vGToIB6fSmH4isOS~m8l}<@dytK~n#CJp)d5w@DL4oXoRArB=hi@Y}C8ZtA z6_g9G5)c^+z%qeuc6V{%$A*KsD_i;^g!;tg`TMCpKl{D0mZO{Zv^u}WjUph*!{a8@OE1x1;roV1K`@W>~Kx9K0&71`F4k4@`AvHEPFPL-T{J|h( zSgNsKax&i(94K+_CkP%=sQlwSa6`^x+izsIApxQ#?&5fIxVkP5IO143h+^FA6-6QV z)p3ui8yCQQZRV2k+04##F2Xmlzr(2ec<+NVTi4ATCKSHk(cZk;>iVg|==hQzYpJey z$|;hDaNKyKERI1aXt40_ots=K(I)mpsY~FZzs9}q4WoNVKdAkfMEGmoK5@0)dY5`T22E1nC@vms0l{*wuFk8S1O4LHWD|M~2Uck`W*3BM4oX zY2ljvvu4Y{;PrX>W$~zWQ6C>ObB+z9QDqvA4?KPlbBji8Axai7{TIm9lFy2JT%rW{0na zc?ac>dJbS~?9w?D6!cO_6m|R-A)>sAgnuwY#yYzt-tYkxL zt4D`zUR9KMc513M`p1h)gCCiN#wqhO#bfV1o8l|Nq-IL;487JaC??NTHJ@c@$2}kt z%Sy^7P`Sl2r2mvzzdZ3m#LY)%bkfdp@>EyG%gr&Dc!TyDd5xM)oJ%C${O8Kk^SW_v zEZ7b-8bl~ihh1+?&cX)l=eMR^Ms5mKhucum5@7_BV-LPGO?EnG0^*h|6fC#~eFlyi zUF4;Vo6GK&_Ht2Hu7brY1_rykoL#bN%MBD{ZKeQJ(^7>ukMxzoM$ScZ*ql zcm34uGqE(>{w$^L%iYaUoh8}xq@hT`XpMyzdJ3VT1HqA3PcEl$m-RTfm{Tle+lp@V zzpJ5`-lz)5S}B#8j5eLnUhCR8_z#$-OX`XbgRqs1)FhR7y#S^;Cvz*-?MLibK?ss{a@6X3z(M%8RkT2{NZyA8T}Vl?E~nkNrEy z@m^RZ^|%s-nE`aZ$4pC#oHn9D9VLQuscxx{=gG*b`E7*6uqrc1&wg+8M8^%lR*EF@Z{Ox^wiSXa$Iu2PA z7L>`@p?_ZQX@imv>w**K&SC4Uws9Ct@I_r6*;U&1%eqQqhC?@RmcA*O^-vn?Zqh3a zA3O7E=nr%G|?DpVaht5`*_}Tcq@2rx z#L%ciw8SaW44f@}rR|Zez}5SR`rR)7Um1DVS#2-7`{v7@xsQt?Ts*IGE { const { sourceAsset, @@ -249,12 +250,18 @@ const getBuiltTx = async ( isFunded, publicKey, ); - return new TransactionBuilder(sourceAccount, { + const transaction = new TransactionBuilder(sourceAccount, { fee: xlmToStroop(fee).toFixed(), networkPassphrase: networkDetails.networkPassphrase, }) .addOperation(operation) .setTimeout(transactionTimeout); + + if (memo) { + transaction.addMemo(Memo.text(memo)); + } + + return transaction; }; export const TransactionDetails = ({ @@ -303,6 +310,7 @@ export const TransactionDetails = ({ const hardwareWalletType = useSelector(hardwareWalletTypeSelector); const isHardwareWallet = !!hardwareWalletType; const [destAssetIcons, setDestAssetIcons] = useState({} as AssetIcons); + const [transactionXdr, setTransactionXdr] = useState(""); const sourceAsset = getAssetFromCanonical(asset); const destAsset = getAssetFromCanonical(destinationAsset || "native"); @@ -353,6 +361,7 @@ export const TransactionDetails = ({ useEffect(() => { const url = "internal"; // blockaid prefers a URL for this endpoint, but this does not originate from a URL const scanSorobanTx = async () => { + setTransactionXdr(transactionSimulation.preparedTransaction!); if ( shouldScanTx && submission.submitStatus === ActionStatus.IDLE && @@ -385,9 +394,11 @@ export const TransactionDetails = ({ transactionFee, transactionTimeout, networkDetails, + memo, ); - - await scanTx(transaction.build().toXDR(), url, networkDetails); + const xdr = transaction.build().toXDR(); + setTransactionXdr(xdr); + await scanTx(xdr, url, networkDetails); } setLoading(false); }; @@ -443,33 +454,10 @@ export const TransactionDetails = ({ const handlePaymentTransaction = async () => { try { - const transaction = await getBuiltTx( - publicKey, - { - sourceAsset, - destAsset, - amount, - destinationAmount, - destination, - allowedSlippage, - path, - isPathPayment, - isSwap, - isFunded: destinationBalances.isFunded!, - }, - transactionFee, - transactionTimeout, - networkDetails, - ); - - if (memo) { - transaction.addMemo(Memo.text(memo)); - } - if (isHardwareWallet) { dispatch( startHwSign({ - transactionXDR: transaction.build().toXDR(), + transactionXDR: transactionXdr, shouldSubmit: true, }), ); @@ -477,7 +465,7 @@ export const TransactionDetails = ({ } const res = await dispatch( signFreighterTransaction({ - transactionXDR: transaction.build().toXDR(), + transactionXDR: transactionXdr, network: networkDetails.networkPassphrase, }), ); @@ -700,6 +688,25 @@ export const TransactionDetails = ({ )} + {submission.submitStatus !== ActionStatus.SUCCESS ? ( +
+
{t("XDR")}
+
+ + <> +
+ +
+ {`${transactionXdr.slice(0, 10)}…`} + +
+
+
+ ) : null} +
{scanResult && ( diff --git a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/styles.scss b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/styles.scss index 430c51fbc3..c67784a302 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/styles.scss +++ b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/styles.scss @@ -49,6 +49,20 @@ margin-bottom: 0.25rem; } } + + &__right--hasOverflow { + color: var(--sds-clr-gray-12); + } + + &__copy { + margin-right: pxToRem(4px); + + svg { + stroke: var(--sds-clr-gray-09); + height: pxToRem(14px); + width: pxToRem(14px); + } + } } &__processing { diff --git a/extension/src/popup/ducks/token-payment.ts b/extension/src/popup/ducks/token-payment.ts index 86b4259a95..d590a12315 100644 --- a/extension/src/popup/ducks/token-payment.ts +++ b/extension/src/popup/ducks/token-payment.ts @@ -22,7 +22,7 @@ export const simulateTokenPayment = createAsyncThunk< { address: string; publicKey: string; - memo: string; + memo?: string; params: { publicKey: string; destination: string; @@ -118,7 +118,7 @@ export const simulateSwap = createAsyncThunk< amountInDecimals: number; amountOut: string; amountOutDecimals: number; - memo: string; + memo?: string; transactionFee: string; path: string[]; }, diff --git a/extension/src/popup/ducks/transactionSubmission.ts b/extension/src/popup/ducks/transactionSubmission.ts index 31c05c8d4d..a86365b2c3 100644 --- a/extension/src/popup/ducks/transactionSubmission.ts +++ b/extension/src/popup/ducks/transactionSubmission.ts @@ -564,7 +564,7 @@ interface TransactionData { federationAddress: string; transactionFee: string; transactionTimeout: number; - memo: string; + memo?: string; destinationAsset: string; destinationDecimals?: number; destinationAmount: string; diff --git a/extension/src/popup/helpers/sorobanSwap.ts b/extension/src/popup/helpers/sorobanSwap.ts index ce7b3a7708..4e3335474d 100644 --- a/extension/src/popup/helpers/sorobanSwap.ts +++ b/extension/src/popup/helpers/sorobanSwap.ts @@ -186,7 +186,7 @@ interface BuildAndSimulateSoroswapTxParams { path: string[]; networkDetails: NetworkDetails; publicKey: string; - memo: string; + memo?: string; transactionFee: string; } diff --git a/extension/src/popup/locales/en/translation.json b/extension/src/popup/locales/en/translation.json index 2bf724e8d8..0f25a8bad2 100644 --- a/extension/src/popup/locales/en/translation.json +++ b/extension/src/popup/locales/en/translation.json @@ -512,6 +512,7 @@ "which is not available on Freighter": { " If you own this account, you can import it into Freighter to complete this request": "which is not available on Freighter. If you own this account, you can import it into Freighter to complete this request." }, + "XDR": "XDR", "XLM": "XLM", "You are attempting to sign an arbitrary message": { " Please use extreme caution and understand the implications of signing this data": "You are attempting to sign an arbitrary message. Please use extreme caution and understand the implications of signing this data." diff --git a/extension/src/popup/locales/pt/translation.json b/extension/src/popup/locales/pt/translation.json index c210e3c227..41c945ca97 100644 --- a/extension/src/popup/locales/pt/translation.json +++ b/extension/src/popup/locales/pt/translation.json @@ -512,6 +512,7 @@ "which is not available on Freighter": { " If you own this account, you can import it into Freighter to complete this request": "which is not available on Freighter. If you own this account, you can import it into Freighter to complete this request." }, + "XDR": "XDR", "XLM": "XLM", "You are attempting to sign an arbitrary message": { " Please use extreme caution and understand the implications of signing this data": "You are attempting to sign an arbitrary message. Please use extreme caution and understand the implications of signing this data." From 41851dac03d2344b91cb20d8d83802309a6fc175 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:36:28 -0700 Subject: [PATCH 05/12] Bump @stellar/design-system from 2.0.0-beta.15 to 2.0.0-beta.17 (#1728) Bumps [@stellar/design-system](https://github.com/stellar/stellar-design-system) from 2.0.0-beta.15 to 2.0.0-beta.17. - [Release notes](https://github.com/stellar/stellar-design-system/releases) - [Commits](https://github.com/stellar/stellar-design-system/commits) --- updated-dependencies: - dependency-name: "@stellar/design-system" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- extension/package.json | 2 +- yarn.lock | 61 +++++++++++++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/extension/package.json b/extension/package.json index bdb3404073..96d0a17f7f 100644 --- a/extension/package.json +++ b/extension/package.json @@ -22,7 +22,7 @@ "@shared/api": "1.0.0", "@shared/constants": "1.0.0", "@shared/helpers": "1.0.0", - "@stellar/design-system": "2.0.0-beta.15", + "@stellar/design-system": "2.0.0-beta.17", "@stellar/typescript-wallet-sdk-km": "^1.0.1", "@testing-library/react": "^14.2.1", "@testing-library/user-event": "^7.1.2", diff --git a/yarn.lock b/yarn.lock index 75997fcddb..33d4d77dc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1308,7 +1308,20 @@ "@babel/parser" "^7.25.9" "@babel/types" "^7.25.9" -"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3", "@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" + integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/generator" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/template" "^7.25.9" + "@babel/types" "^7.25.9" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/traverse@^7.25.3", "@babel/traverse@^7.25.9", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== @@ -2218,7 +2231,7 @@ dependencies: "@floating-ui/utils" "^0.2.8" -"@floating-ui/dom@^1.6.3": +"@floating-ui/dom@^1.6.11": version "1.6.12" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.12.tgz#6333dcb5a8ead3b2bf82f33d6bc410e95f54e556" integrity sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w== @@ -3399,16 +3412,16 @@ resolved "https://registry.yarnpkg.com/@stablelib/utf8/-/utf8-2.0.0.tgz#05725ef9d39ed10a017e1b6e01374bd998c83167" integrity sha512-bHaUduwFKYgj6rRvA5udyyg+ASx6gJZiQaXvfBHb7A2r+X9tRIKJ/VmpQKFQnEMInpBTh7jJLy+Gt99GH9YZ9g== -"@stellar/design-system@2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@stellar/design-system/-/design-system-2.0.0-beta.15.tgz#431976a02faf29fa6e671b78e7ddb6c8cc1d73f4" - integrity sha512-jISw5zR/WCvbQspFe+TyuipoHDYAO7PQdlfu6Wpqmc4Gvc6g5O2hhbthn6mtF+wlsv6X3oz0Un6+FWPFnUVKiQ== +"@stellar/design-system@2.0.0-beta.17": + version "2.0.0-beta.17" + resolved "https://registry.yarnpkg.com/@stellar/design-system/-/design-system-2.0.0-beta.17.tgz#67c0fdd9bc62d3b69277b6bd8c6e96952dc0613c" + integrity sha512-OI550YuErfOebfGfkMvDYF0KizzzouI9+npsBvAs1eL1+ILNTbeZusX+dYoVvmHJMkzpnF9gXTAcUJnfUIfWSg== dependencies: - "@floating-ui/dom" "^1.6.3" + "@floating-ui/dom" "^1.6.11" bignumber.js "^9.1.2" lodash "^4.17.21" react-copy-to-clipboard "^5.1.0" - tslib "^2.6.2" + tslib "^2.7.0" "@stellar/eslint-config@^2.1.2": version "2.1.2" @@ -3458,7 +3471,7 @@ optionalDependencies: sodium-native "^4.1.1" -"@stellar/stellar-sdk@13.0.0-beta.1", "stellar-sdk-next@yarn:@stellar/stellar-sdk@13.0.0-beta.1", "stellar-sdk@yarn:@stellar/stellar-sdk@13.0.0-beta.1": +"@stellar/stellar-sdk@13.0.0-beta.1": version "13.0.0-beta.1" resolved "https://registry.yarnpkg.com/@stellar/stellar-sdk/-/stellar-sdk-13.0.0-beta.1.tgz#9a995575b806bea3a383b2d9fe4b1fde065caeb4" integrity sha512-yJN2HzibhZFJsdLRU83bkUwb9dq1sZRRiQptTJyunVv0hQsF+tTldrP3hHst3LROv/2GWTn20tmAqnp0hkzOhg== @@ -15960,6 +15973,34 @@ stellar-identicon-js@^1.0.0: dependencies: html-webpack-plugin "^3.2.0" +"stellar-sdk-next@yarn:@stellar/stellar-sdk@13.0.0-beta.1": + version "13.0.0-beta.1" + resolved "https://registry.yarnpkg.com/@stellar/stellar-sdk/-/stellar-sdk-13.0.0-beta.1.tgz#9a995575b806bea3a383b2d9fe4b1fde065caeb4" + integrity sha512-yJN2HzibhZFJsdLRU83bkUwb9dq1sZRRiQptTJyunVv0hQsF+tTldrP3hHst3LROv/2GWTn20tmAqnp0hkzOhg== + dependencies: + "@stellar/stellar-base" "13.0.0-beta.1" + axios "^1.7.7" + bignumber.js "^9.1.2" + eventsource "^2.0.2" + feaxios "^0.0.20" + randombytes "^2.1.0" + toml "^3.0.0" + urijs "^1.19.1" + +"stellar-sdk@yarn:@stellar/stellar-sdk@13.0.0-beta.1": + version "13.0.0-beta.1" + resolved "https://registry.yarnpkg.com/@stellar/stellar-sdk/-/stellar-sdk-13.0.0-beta.1.tgz#9a995575b806bea3a383b2d9fe4b1fde065caeb4" + integrity sha512-yJN2HzibhZFJsdLRU83bkUwb9dq1sZRRiQptTJyunVv0hQsF+tTldrP3hHst3LROv/2GWTn20tmAqnp0hkzOhg== + dependencies: + "@stellar/stellar-base" "13.0.0-beta.1" + axios "^1.7.7" + bignumber.js "^9.1.2" + eventsource "^2.0.2" + feaxios "^0.0.20" + randombytes "^2.1.0" + toml "^3.0.0" + urijs "^1.19.1" + stop-iteration-iterator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" @@ -16625,7 +16666,7 @@ tsconfig-paths@^4.1.2: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.8.1, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.6.0, tslib@^2.6.2: +tslib@2.8.1, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.6.0, tslib@^2.6.2, tslib@^2.7.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== From 825c99d75d2b2f1e08f27c5664e80c0f125edf61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:36:51 -0700 Subject: [PATCH 06/12] Bump @stellar/design-system in /extension (#1729) Bumps [@stellar/design-system](https://github.com/stellar/stellar-design-system) from 2.0.0-beta.15 to 2.0.0-beta.17. - [Release notes](https://github.com/stellar/stellar-design-system/releases) - [Commits](https://github.com/stellar/stellar-design-system/commits) --- updated-dependencies: - dependency-name: "@stellar/design-system" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 2ebb82afa1af598113abf53afc24d63d9f40763e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:50:11 -0700 Subject: [PATCH 07/12] Bump the all-actions group across 1 directory with 2 updates (#1745) Bumps the all-actions group with 2 updates in the / directory: [rtCamp/action-slack-notify](https://github.com/rtcamp/action-slack-notify) and [ruby/setup-ruby](https://github.com/ruby/setup-ruby). Updates `rtCamp/action-slack-notify` from 2.3.0 to 2.3.2 - [Release notes](https://github.com/rtcamp/action-slack-notify/releases) - [Commits](https://github.com/rtcamp/action-slack-notify/compare/v2.3.0...c33737706dea87cd7784c687dadc9adf1be59990) Updates `ruby/setup-ruby` from 1.203.0 to 1.204.0 - [Release notes](https://github.com/ruby/setup-ruby/releases) - [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb) - [Commits](https://github.com/ruby/setup-ruby/compare/v1.203.0...v1.204.0) --- updated-dependencies: - dependency-name: rtCamp/action-slack-notify dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all-actions - dependency-name: ruby/setup-ruby dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deployFreighterApiBeta.yml | 2 +- .github/workflows/deployFreighterApiProduction.yml | 2 +- .github/workflows/submitSafari.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deployFreighterApiBeta.yml b/.github/workflows/deployFreighterApiBeta.yml index de23afecbb..de24df39ca 100644 --- a/.github/workflows/deployFreighterApiBeta.yml +++ b/.github/workflows/deployFreighterApiBeta.yml @@ -35,7 +35,7 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Slack Notification - uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907 #v2.3.0 + uses: rtCamp/action-slack-notify@c33737706dea87cd7784c687dadc9adf1be59990 #v2.3.2 env: MSG_MINIMAL: true SLACK_CHANNEL: team-wallet-eng diff --git a/.github/workflows/deployFreighterApiProduction.yml b/.github/workflows/deployFreighterApiProduction.yml index b0bc7367ac..1af3e6af89 100644 --- a/.github/workflows/deployFreighterApiProduction.yml +++ b/.github/workflows/deployFreighterApiProduction.yml @@ -37,7 +37,7 @@ jobs: with: title: Bump @stellar/freighter-api version to ${{ github.event.inputs.version }} - name: Slack Notification - uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907 #v2.3.0 + uses: rtCamp/action-slack-notify@c33737706dea87cd7784c687dadc9adf1be59990 #v2.3.2 env: MSG_MINIMAL: true SLACK_CHANNEL: release diff --git a/.github/workflows/submitSafari.yml b/.github/workflows/submitSafari.yml index d970cfa4d4..9270610011 100644 --- a/.github/workflows/submitSafari.yml +++ b/.github/workflows/submitSafari.yml @@ -61,7 +61,7 @@ jobs: - name: Convert extension to Xcode project run: xcrun safari-web-extension-converter ./extension/build --project-location $GYM_PROJECT --macos-only - name: Set up ruby env - uses: ruby/setup-ruby@v1.203.0 + uses: ruby/setup-ruby@v1.204.0 with: ruby-version: 2.6.10 bundler-cache: true From 535943f9be6de308e6d32bd757683090ba4fd531 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:51:14 -0700 Subject: [PATCH 08/12] Bump nanoid in the npm_and_yarn group across 1 directory (#1742) Bumps the npm_and_yarn group with 1 update in the / directory: [nanoid](https://github.com/ai/nanoid). Updates `nanoid` from 3.3.7 to 3.3.8 - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8) --- updated-dependencies: - dependency-name: nanoid dependency-type: indirect dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 33d4d77dc6..e03e5532e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12612,9 +12612,9 @@ nan@^2.14.0: integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw== nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== natural-compare-lite@^1.4.0: version "1.4.0" From 3209145dfd010cc67f8ae7fed4a96c95c7d00a57 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 3 Jan 2025 17:27:05 -0500 Subject: [PATCH 09/12] update test token address (#1760) --- extension/e2e-tests/helpers/test-token.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/e2e-tests/helpers/test-token.ts b/extension/e2e-tests/helpers/test-token.ts index 0a913ea242..ec480f1841 100644 --- a/extension/e2e-tests/helpers/test-token.ts +++ b/extension/e2e-tests/helpers/test-token.ts @@ -1,5 +1,5 @@ export const TEST_TOKEN_ADDRESS = - "CC7YMFMYZM2HE6O3JT5CNTFBHVXCZTV7CEYT56IGBHR4XFNTGTN62CPT"; + "CB5IKHNEXCADYXZMCS75IDR4F6HJY4PYYAGL6LUPJHRLWFA4BWUYLWZE"; export const USDC_TOKEN_ADDRESS = "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA"; From 898e83c4cd444c63bb149b49b4e8f7bc9fc56078 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Tue, 7 Jan 2025 13:51:14 -0500 Subject: [PATCH 10/12] adding blockaid byline and feedback form --- @shared/api/types.ts | 4 +- .../components/WarningMessages/index.tsx | 506 ++++++++++++------ .../components/WarningMessages/styles.scss | 93 +++- .../manageAssets/ManageAssetRows/index.tsx | 28 +- .../sendPayment/SendAmount/index.tsx | 29 +- .../SendConfirm/TransactionDetails/index.tsx | 4 +- extension/src/popup/helpers/blockaid.ts | 78 +++ .../src/popup/views/SignTransaction/index.tsx | 44 +- .../popup/views/SignTransaction/styles.scss | 9 + .../views/__tests__/SendPayment.test.tsx | 2 + 10 files changed, 557 insertions(+), 240 deletions(-) diff --git a/@shared/api/types.ts b/@shared/api/types.ts index f560816d08..03ddbe1dd6 100644 --- a/@shared/api/types.ts +++ b/@shared/api/types.ts @@ -244,7 +244,9 @@ export interface Balance { export type BlockAidScanAssetResult = Blockaid.TokenScanResponse; export type BlockAidScanSiteResult = Blockaid.SiteScanResponse; -export type BlockAidScanTxResult = Blockaid.StellarTransactionScanResponse; +export type BlockAidScanTxResult = Blockaid.StellarTransactionScanResponse & { + request_id: string; +}; export type BlockAidBulkScanAssetResult = Blockaid.TokenBulkScanResponse; export interface AssetBalance extends Balance { diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index 418b4e6cdd..9056dc72ed 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -1,8 +1,21 @@ import React, { useState, useRef, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { createPortal } from "react-dom"; -import { Button, Icon, Loader, Notification } from "@stellar/design-system"; +import { + Alert, + Button, + Card, + Icon, + Loader, + Notification, + Select, + Textarea, + Text, +} from "@stellar/design-system"; import { useTranslation, Trans } from "react-i18next"; +import { Field, FieldProps, Formik, Form } from "formik"; +import { object as YupObject, string as YupString } from "yup"; + import { POPUP_HEIGHT } from "constants/dimensions"; import { Account, @@ -61,7 +74,12 @@ import IconShieldBlockaid from "popup/assets/icon-shield-blockaid.svg"; import IconWarningBlockaid from "popup/assets/icon-warning-blockaid.svg"; import IconWarningBlockaidYellow from "popup/assets/icon-warning-blockaid-yellow.svg"; import { getVerifiedTokens } from "popup/helpers/searchAsset"; -import { isAssetSuspicious, isBlockaidWarning } from "popup/helpers/blockaid"; +import { + isAssetSuspicious, + isBlockaidWarning, + reportAssetWarning, + reportTransactionWarning, +} from "popup/helpers/blockaid"; import { CopyValue } from "../CopyValue"; import "./styles.scss"; @@ -257,15 +275,204 @@ export const BackupPhraseWarningMessage = () => { ); }; -const BlockaidByLine = () => { +interface BlockaidFeedbackFormValues { + details: string; + transactionIssue?: string; +} + +const BlockaidFeedbackFormSchema = YupObject().shape({ + details: YupString().required(), +}); + +interface BlockaidFeedbackFormProps { + address?: string; + requestId?: string; + setIsFeedbackActive: (isActive: boolean) => void; +} + +const BlockaidFeedbackForm = ({ + address, + requestId, + setIsFeedbackActive, +}: BlockaidFeedbackFormProps) => { + const { t } = useTranslation(); + const feedbackRef = useRef(null); + const networkDetails = useSelector(settingsNetworkDetailsSelector); + + const handleSubmit = async (values: BlockaidFeedbackFormValues) => { + if (requestId && values.transactionIssue) { + await reportTransactionWarning({ + details: values.details, + requestId, + event: values.transactionIssue, + }); + } else if (address) { + await reportAssetWarning({ + address, + details: values.details, + networkDetails, + }); + } + + setIsFeedbackActive(false); + }; + + const initialValues: BlockaidFeedbackFormValues = { + details: "", + transactionIssue: "should_be_benign", + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + feedbackRef.current && + !feedbackRef.current.contains(event.target as Node) + ) { + setIsFeedbackActive(false); + } + }; + + document.addEventListener("click", handleClickOutside, true); + return () => { + document.removeEventListener("click", handleClickOutside, true); + }; + }, [setIsFeedbackActive]); + + return ( + <> +
+ +
+
+
+ + + {({ dirty, isValid, isSubmitting }) => ( +
+
+ + {t("Leave feedback about Blockaid warnings and messages")} + + {requestId ? ( + + {({ field }: FieldProps) => ( + + )} + + ) : null} + + + {({ field }: FieldProps) => ( +