From 4e4289bee68dd2aa83d5b857eb7aaf9afa26be0e Mon Sep 17 00:00:00 2001 From: LeafBreezes Date: Sun, 25 Aug 2024 14:35:48 +0800 Subject: [PATCH 01/21] added translation --- packages/core/src/lib/locale/bg.json | 47 +++++++ packages/core/src/lib/locale/es.json | 47 +++++++ packages/core/src/lib/locale/hi.json | 180 +++++++++++++++++---------- 3 files changed, 208 insertions(+), 66 deletions(-) diff --git a/packages/core/src/lib/locale/bg.json b/packages/core/src/lib/locale/bg.json index 72f35ef1f..d7a965b5b 100644 --- a/packages/core/src/lib/locale/bg.json +++ b/packages/core/src/lib/locale/bg.json @@ -63,6 +63,53 @@ "bridge": "Мостов портфейл", "mobile": "Мобилен портфейл", "instant-link": "Мигновен портфейл" + }, + "exportAccounts": { + "afterDecide": "След като изберете портфейл, можете да изберете кои акаунти искате да прехвърлите.", + "chooseAWallet": "Изберете Портфейл", + "complete": { + "button": "Завърши", + "descOne": "Сега ще бъдете пренасочени към избрания от вас портфейл, за да завършите прехвърлянето.", + "descTwo": "След като частта от процеса по импортиране от избрания портфейл бъде завършена, натиснете бутона, за да завършите процеса на прехвърляне.", + "startOverButton": "Започнете отначало", + "title": "Завършете Прехвърлянето" + }, + "disclaimer": "Няма да можете да прехвърлите акаунти, които никога не са били финансирани или използвани в NEAR.", + "getPassphrase": { + "button": "Продължи", + "checkLabel": "Копирах или написах паролата", + "desc": "Ще трябва да въведете тази парола, когато започнете да експортирате акаунтите си в различен портфейл.", + "label": "Щракнете, за да копирате", + "title": "Копирайте Временната Парола", + "transferButton": "Прехвърлете Акаунти" + }, + "selectAWallet": "Изберете портфейл, който отговаря на нуждите ви и поддържа свързаните ви акаунти.", + "selectAccounts": { + "button": "Продължи", + "deselectAll": "Отметете всички", + "error": "Акаунтът не съществува", + "noBalance": "Акаунтът не е финансиран", + "selectAll": "Отметете всички", + "title": "Изберете Акаунти за Прехвърляне", + "unavailable": "Прехвърлянето е недостъпно", + "warningLedger": "Изисква се поддръжка на Ledger" + }, + "selectYourAccounts": "Изберете Вашите Акаунти", + "transferYourAccounts": "Прехвърлете Вашите Акаунти", + "walletTypes": { + "bridge": "Мостов Портфейл", + "browser": "Портфейл за Браузър", + "hardware": "Хардуерен Портфейл", + "injected": "Разширение на Портфейл", + "mobile": "Мобилен Портфейл" + }, + "warning": "не поддържа експортиране на акаунти в момента. Моля, изберете друг портфейл." + } + }, + "component": { + "clickToCopy": { + "label": "Копирано", + "tooltip": "Щракнете, за да копирате" } } } diff --git a/packages/core/src/lib/locale/es.json b/packages/core/src/lib/locale/es.json index e81f75c11..d09827f16 100644 --- a/packages/core/src/lib/locale/es.json +++ b/packages/core/src/lib/locale/es.json @@ -63,6 +63,53 @@ "bridge": "Cartera de Puente", "mobile": "Cartera Móvil", "instant-link": "Cartera Instantánea" + }, + "exportAccounts": { + "afterDecide": "Después de decidirte por una billetera, puedes seleccionar qué cuentas deseas transferir.", + "chooseAWallet": "Elige una Billetera", + "complete": { + "button": "Completar", + "descOne": "Ahora serás redirigido a la billetera que seleccionaste para completar la transferencia.", + "descTwo": "Una vez que se haya completado la parte de importación del proceso desde la billetera seleccionada, presiona el botón para finalizar el proceso de transferencia.", + "startOverButton": "Empezar de Nuevo", + "title": "Completar la Transferencia" + }, + "disclaimer": "No podrás transferir cuentas que nunca hayan sido financiadas o utilizadas en NEAR.", + "getPassphrase": { + "button": "Continuar", + "checkLabel": "Copié o anoté la contraseña", + "desc": "Necesitarás ingresar esta contraseña cuando comiences a exportar tus cuentas a otra billetera.", + "label": "Haga clic para Copiar", + "title": "Copiar Contraseña Temporal", + "transferButton": "Transferir Cuentas" + }, + "selectAWallet": "Selecciona una billetera que se ajuste a tus necesidades y que soporte tus cuentas conectadas.", + "selectAccounts": { + "button": "Continuar", + "deselectAll": "Deseleccionar Todo", + "error": "La cuenta no existe", + "noBalance": "Cuenta no financiada", + "selectAll": "Seleccionar Todo", + "title": "Seleccionar Cuentas para Transferir", + "unavailable": "Transferencia No Disponible", + "warningLedger": "Se requiere soporte de Ledger" + }, + "selectYourAccounts": "Selecciona Tus Cuentas", + "transferYourAccounts": "Transfiere Tus Cuentas", + "walletTypes": { + "bridge": "Billetera Bridge", + "browser": "Billetera de Navegador", + "hardware": "Billetera de Hardware", + "injected": "Extensión de Billetera", + "mobile": "Billetera Móvil" + }, + "warning": "no admite la exportación de cuentas en este momento. Por favor selecciona otra billetera." + } + }, + "component": { + "clickToCopy": { + "label": "Copiado", + "tooltip": "Haga clic para copiar" } } } diff --git a/packages/core/src/lib/locale/hi.json b/packages/core/src/lib/locale/hi.json index 75dc8ed9e..f78b53815 100644 --- a/packages/core/src/lib/locale/hi.json +++ b/packages/core/src/lib/locale/hi.json @@ -1,67 +1,115 @@ { - "modal": { - "wallet": { - "connectYourWallet": "अपना वॉलेट (Wallet) कनेक्ट करें।", - "whatIsAWallet": "वॉलेट क्या है?", - "secureAndManage": "सुरक्षित और प्रबंधित करें।", - "safelyStore": "अपनी क्रिप्टोकरेंसी और एनएफटी को सुरक्षित रूप से स्टोर और ट्रांसफर करें।", - "logInToAny": "NEAR पर किसी भी ऐप में साइन इन करें।", - "noNeedToCreate": "नए खाते या लॉगिन बनाने की आवश्यकता नहीं है। अपना वॉलेट(NEAR Wallet)कनेक्ट करें और आरंभ करें।", - "getAWallet": "एक वॉलेट बनाएँ।", - "useAWallet": "अपनी NEAR संपत्तियों को सुरक्षित और प्रबंधित करने के लिए वॉलेट का उपयोग करें और किसी भी NEAR ऐप (app) में लॉग इन करें, किसी उपयोगकर्ता (user) नाम और पासवर्ड की आवश्यकता नहीं है।", - "connectionFailed": "कनेक्शन विफल|", - "connectionSuccessful": "कनेक्शन सफल|", - "rememberWallet": "वॉलेटों को याद रखें", - "connected": "वॉलेट जुड़ गया|", - "connectingTo": "वॉलेट जुड़ रहा हे|", - "connectingMessage": { - "injected": "एक्सटेंशन विंडो में कनेक्शन की पुष्टि करें|", - "browser": "रीडायरेक्ट होने के बाद वॉलेट में कनेक्शन की पुष्टि करें|", - "hardware": "कोल्ड वॉलेट के साथ कनेक्शन की पुष्टि करें|" - } - }, - "ledger": { - "connectWithLedger": "लेजर(Ledger)वॉलेट कनेक्ट करें|", - "makeSureYourLedger": "सुनिश्चित करें कि आपका लेजर सुरक्षित रूप से जुड़ा हुआ है और NEAR ऐप आपके डिवाइस (Device)पर पहले से ही खुला है|", - "continue": "जारी रखे|", - "specifyHDPath": "हार्ड डिस्क(Hard Disk)पथ(Path)निर्धारित करे|", - "enterYourPreferredHDPath": "अपना एचडी पथ दर्ज करें, फिर सक्रिय खातों के लिए स्कैन करें|", - "scan": "स्कैन करे|", - "retry": "दोबारा प्रयास करे|", - "ledgerIsNotAvailable": "लेजर उपलब्ध नहीं है|", - "accessDeniedToUseLedgerDevice": "लेजर डिवाइस का उपयोग करने के लिए प्रवेश निषेध|", - "noAccountsFound": "खाता नहीं मिला|", - "selectYourAccounts": "अपने खाते चुनें|", - "connecting1Account": "एक खाता कनेक्ट हो रहा है|", - "cantFindAnyAccount": "इस लेजर से जुड़ा कोई खाता नहीं मिला। कृपया एक नया NEAR खाता बनाएँ|", - "orConnectAnAnotherLedger": "अथवा दूसरे लेजर वॉलेट से कनेक्ट करें।", - "connecting": "जुड़ रहा हे|", - "ofAccounts": "खाता अब जुड़ा नहीं हे|", - "failedToAutomatically": "खाता आईडी स्वचालित रूप से खोजने में असमर्थ। मैन्युअल (Manuall) रूप से कोशिश करें|", - "overviewTheListOfAuthorized": "अवलोकन अधिकृत की सूची, नीचे दिए गए बटन पर क्लिक करके लॉगिन पूरा करें।", - "finish": "समाप्त|" - }, - "install": { - "youllNeedToInstall": "आपको इंस्टॉल करना होगा|", - "toContinueAfterInstalling": "इंस्टॉल करने के बाद जारी रखे|", - "refreshThePage": "पुन: लोड करें।", - "open": "खोले|" - }, - "qr": { - "copiedToClipboard": "क्लिपबोर्ड(Clipboard)पर कॉपी किया गया|", - "failedToCopy": "क्लिपबोर्ड पर कॉपी करना विफल रहा|", - "scanWithYourMobile": "अपने फोन (Mobile)से स्कैन करें|", - "copyToClipboard": " क्लिपबोर्ड पर कॉपी करें|", - "preferTheOfficial": "आधिकारिक संवाद को प्राथमिकता दें|", - "open": "खोले|" - }, - "walletTypes": { - "hardware": "हार्डवेयर वॉलेट", - "browser": "ब्राउज़र वॉलेट", - "injected": "वॉलेट एक्सटेंशन", - "bridge": "ब्रिज वॉलेट", - "mobile": "मोबाइल वॉलेट", - "instant-link": "इंस्टेंट वॉलेट" - } - } - } + "modal": { + "wallet": { + "connectYourWallet": "अपना वॉलेट (Wallet) कनेक्ट करें।", + "whatIsAWallet": "वॉलेट क्या है?", + "secureAndManage": "सुरक्षित और प्रबंधित करें।", + "safelyStore": "अपनी क्रिप्टोकरेंसी और एनएफटी को सुरक्षित रूप से स्टोर और ट्रांसफर करें।", + "logInToAny": "NEAR पर किसी भी ऐप में साइन इन करें।", + "noNeedToCreate": "नए खाते या लॉगिन बनाने की आवश्यकता नहीं है। अपना वॉलेट(NEAR Wallet)कनेक्ट करें और आरंभ करें।", + "getAWallet": "एक वॉलेट बनाएँ।", + "useAWallet": "अपनी NEAR संपत्तियों को सुरक्षित और प्रबंधित करने के लिए वॉलेट का उपयोग करें और किसी भी NEAR ऐप (app) में लॉग इन करें, किसी उपयोगकर्ता (user) नाम और पासवर्ड की आवश्यकता नहीं है।", + "connectionFailed": "कनेक्शन विफल|", + "connectionSuccessful": "कनेक्शन सफल|", + "rememberWallet": "वॉलेटों को याद रखें", + "connected": "वॉलेट जुड़ गया|", + "connectingTo": "वॉलेट जुड़ रहा हे|", + "connectingMessage": { + "injected": "एक्सटेंशन विंडो में कनेक्शन की पुष्टि करें|", + "browser": "रीडायरेक्ट होने के बाद वॉलेट में कनेक्शन की पुष्टि करें|", + "hardware": "कोल्ड वॉलेट के साथ कनेक्शन की पुष्टि करें|", + "bridge": "वॉलेट में कनेक्शन की पुष्टि करें" + } + }, + "ledger": { + "connectWithLedger": "लेजर(Ledger)वॉलेट कनेक्ट करें|", + "makeSureYourLedger": "सुनिश्चित करें कि आपका लेजर सुरक्षित रूप से जुड़ा हुआ है और NEAR ऐप आपके डिवाइस (Device)पर पहले से ही खुला है|", + "continue": "जारी रखे|", + "specifyHDPath": "हार्ड डिस्क(Hard Disk)पथ(Path)निर्धारित करे|", + "enterYourPreferredHDPath": "अपना एचडी पथ दर्ज करें, फिर सक्रिय खातों के लिए स्कैन करें|", + "scan": "स्कैन करे|", + "retry": "दोबारा प्रयास करे|", + "ledgerIsNotAvailable": "लेजर उपलब्ध नहीं है|", + "accessDeniedToUseLedgerDevice": "लेजर डिवाइस का उपयोग करने के लिए प्रवेश निषेध|", + "noAccountsFound": "खाता नहीं मिला|", + "selectYourAccounts": "अपने खाते चुनें|", + "connecting1Account": "एक खाता कनेक्ट हो रहा है|", + "cantFindAnyAccount": "इस लेजर से जुड़ा कोई खाता नहीं मिला। कृपया एक नया NEAR खाता बनाएँ|", + "orConnectAnAnotherLedger": "अथवा दूसरे लेजर वॉलेट से कनेक्ट करें।", + "connecting": "जुड़ रहा हे|", + "ofAccounts": "खाता अब जुड़ा नहीं हे|", + "failedToAutomatically": "खाता आईडी स्वचालित रूप से खोजने में असमर्थ। मैन्युअल (Manuall) रूप से कोशिश करें|", + "overviewTheListOfAuthorized": "अवलोकन अधिकृत की सूची, नीचे दिए गए बटन पर क्लिक करके लॉगिन पूरा करें।", + "finish": "समाप्त|" + }, + "install": { + "youllNeedToInstall": "आपको इंस्टॉल करना होगा|", + "toContinueAfterInstalling": "इंस्टॉल करने के बाद जारी रखे|", + "refreshThePage": "पुन: लोड करें।", + "open": "खोले|" + }, + "qr": { + "copiedToClipboard": "क्लिपबोर्ड(Clipboard)पर कॉपी किया गया|", + "failedToCopy": "क्लिपबोर्ड पर कॉपी करना विफल रहा|", + "scanWithYourMobile": "अपने फोन (Mobile)से स्कैन करें|", + "copyToClipboard": " क्लिपबोर्ड पर कॉपी करें|", + "preferTheOfficial": "आधिकारिक संवाद को प्राथमिकता दें|", + "open": "खोले|" + }, + "walletTypes": { + "hardware": "हार्डवेयर वॉलेट", + "browser": "ब्राउज़र वॉलेट", + "injected": "वॉलेट एक्सटेंशन", + "bridge": "ब्रिज वॉलेट", + "mobile": "मोबाइल वॉलेट", + "instant-link": "इंस्टेंट वॉलेट" + }, + "exportAccounts": { + "afterDecide": "जब आप एक वॉलेट चुन लेंगे, तब आप चुन सकते हैं कि आप कौन से खाते स्थानांतरित करना चाहते हैं।", + "chooseAWallet": "एक वॉलेट चुनें", + "complete": { + "button": "पूर्ण करें", + "descOne": "अब आपको स्थानांतरित करने के लिए आपने जो वॉलेट चुना है, उस पर रीडायरेक्ट किया जाएगा।", + "descTwo": "चुने हुए वॉलेट से आयात प्रक्रिया पूरी होने के बाद, स्थानांतरण प्रक्रिया को पूरा करने के लिए बटन दबाएं।", + "startOverButton": "फिर से शुरू करें", + "title": "स्थानांतरण पूरा करें" + }, + "disclaimer": "आप उन खातों को स्थानांतरित नहीं कर सकेंगे जो कभी भी NEAR पर फंडेड या उपयोग किए गए नहीं हैं।", + "getPassphrase": { + "button": "जारी रखें", + "checkLabel": "मैंने पासवर्ड को कॉपी या नोट किया है", + "desc": "जब आप अपने खातों को एक अलग वॉलेट में निर्यात करना शुरू करेंगे, तो आपको इस पासवर्ड को दर्ज करने की आवश्यकता होगी।", + "label": "कॉपी करने के लिए क्लिक करें", + "title": "अस्थायी पासवर्ड कॉपी करें", + "transferButton": "खातों का स्थानांतरण करें" + }, + "selectAWallet": "एक वॉलेट चुनें जो आपकी आवश्यकताओं को पूरा करता है और आपके जुड़े खातों का समर्थन करता है।", + "selectAccounts": { + "button": "जारी रखें", + "deselectAll": "सभी अचयनित करें", + "error": "खाता अस्तित्व में नहीं है", + "noBalance": "खाता निधि नहीं है", + "selectAll": "सभी चुनें", + "title": "स्थानांतरण के लिए खातों का चयन करें", + "unavailable": "स्थानांतरण अस्वीकृत", + "warningLedger": "लेजर समर्थन की आवश्यकता है" + }, + "selectYourAccounts": "अपने खाते चुनें", + "transferYourAccounts": "अपने खातों का स्थानांतरण करें", + "walletTypes": { + "bridge": "ब्रिज वॉलेट", + "browser": "ब्राउज़र वॉलेट", + "hardware": "हार्डवेयर वॉलेट", + "injected": "वॉलेट एक्सटेंशन", + "mobile": "मोबाइल वॉलेट" + }, + "warning": "वर्तमान में खाते निर्यात का समर्थन नहीं करता है। कृपया एक अन्य वॉलेट चुनें।" + } + }, + "component": { + "clickToCopy": { + "label": "कॉपी हो गया", + "tooltip": "कॉपी करने के लिए क्लिक करें" + } + } +} From 04d9038840c1c3f67ad007ce587994f22fd508e5 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Fri, 24 Jan 2025 21:54:06 -0300 Subject: [PATCH 02/21] temp --- .../src/lib/my-near-wallet copy.ts | 450 ++++++++++++++++++ .../src/lib/my-near-wallet-connection.ts | 303 ++++++++++++ .../my-near-wallet/src/lib/my-near-wallet.ts | 156 +++++- 3 files changed, 892 insertions(+), 17 deletions(-) create mode 100644 packages/my-near-wallet/src/lib/my-near-wallet copy.ts create mode 100644 packages/my-near-wallet/src/lib/my-near-wallet-connection.ts diff --git a/packages/my-near-wallet/src/lib/my-near-wallet copy.ts b/packages/my-near-wallet/src/lib/my-near-wallet copy.ts new file mode 100644 index 000000000..746e01a6a --- /dev/null +++ b/packages/my-near-wallet/src/lib/my-near-wallet copy.ts @@ -0,0 +1,450 @@ +import * as nearAPI from "near-api-js"; +import type { + WalletModuleFactory, + WalletBehaviourFactory, + BrowserWallet, + Transaction, + Optional, + Network, + Account, +} from "@near-wallet-selector/core"; +import { createAction } from "@near-wallet-selector/wallet-utils"; +import icon from "./icon"; +import { PublicKey } from "near-api-js/lib/utils"; + +export interface MyNearWalletParams { + walletUrl?: string; + iconUrl?: string; + deprecated?: boolean; + successUrl?: string; + failureUrl?: string; +} + +interface MyNearWalletState { + wallet: nearAPI.WalletConnection; + keyStore: nearAPI.keyStores.BrowserLocalStorageKeyStore; +} + +interface MyNearWalletExtraOptions { + walletUrl: string; +} + +const resolveWalletUrl = (network: Network, walletUrl?: string) => { + if (walletUrl) { + return walletUrl; + } + + switch (network.networkId) { + case "mainnet": + return "https://app.mynearwallet.com"; + case "testnet": + return "https://testnet.mynearwallet.com"; + default: + throw new Error("Invalid wallet url"); + } +}; + +const setupWalletState = async ( + params: MyNearWalletExtraOptions, + network: Network +): Promise => { + const keyStore = new nearAPI.keyStores.BrowserLocalStorageKeyStore(); + + const near = await nearAPI.connect({ + keyStore, + walletUrl: params.walletUrl, + ...network, + headers: {}, + }); + + const wallet = new nearAPI.WalletConnection(near, "near_app"); + + return { + wallet, + keyStore, + }; +}; + +const MyNearWallet: WalletBehaviourFactory< + BrowserWallet, + { params: MyNearWalletExtraOptions } +> = async ({ metadata, options, store, params, logger, id, emitter, storage }) => { + const _state = await setupWalletState(params, options.network); + const getAccounts = async (): Promise> => { + const accountId = _state.wallet.getAccountId(); + const account = _state.wallet.account(); + if (!accountId || !account) { + return []; + } + + const publicKey = await account.connection.signer.getPublicKey( + account.accountId, + options.network.networkId + ); + return [ + { + accountId, + publicKey: publicKey ? publicKey.toString() : "", + }, + ]; + // const {accountId,publicKey} = JSON.parse(localStorage.getItem('my-near-wallet') || "{}"); + + // if(!accountId || !publicKey){ + // return []; + // } + + // return [{accountId,publicKey}]; + }; + + const transformTransactions = async ( + transactions: Array> + ) => { + const account = _state.wallet.account(); + const { networkId, signer, provider } = account.connection; + + const localKey = await signer.getPublicKey(account.accountId, networkId); + + return Promise.all( + transactions.map(async (transaction, index) => { + const actions = transaction.actions.map((action) => + createAction(action) + ); + const accessKey = await account.accessKeyForTransaction( + transaction.receiverId, + actions, + localKey + ); + + if (!accessKey) { + throw new Error( + `Failed to find matching key for transaction sent to ${transaction.receiverId}` + ); + } + + const block = await provider.block({ finality: "final" }); + + const nonce = accessKey.access_key.nonce + BigInt(index + 1); + + return nearAPI.transactions.createTransaction( + account.accountId, + nearAPI.utils.PublicKey.from(accessKey.public_key), + transaction.receiverId, + nonce, + actions, + nearAPI.utils.serialize.base_decode(block.header.hash) + ); + }) + ); + }; + + return { + async signIn({ contractId, methodNames, successUrl, failureUrl }) { + const existingAccounts = await getAccounts(); + + if (existingAccounts.length) { + return existingAccounts; + } + + await _state.wallet.requestSignIn({ + contractId, + methodNames, + successUrl, + failureUrl, + }); + + return getAccounts(); + + + const url = await _state.wallet.requestSignInUrl({ + contractId, + methodNames, + successUrl, + failureUrl, + }); + + // contractId && publishUrl.searchParams.set('contractId', contractId ); + // methodNames && publishUrl.searchParams.set('methodNames', methodNames.join(",") ); + + console.log("Url requestSignInUrl 23",url); + + // @ts-ignore + const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); + + return await new Promise((resolve, reject) => { + const checkWindowClosed = setInterval(() => { + if (childWindow?.closed) { + clearInterval(checkWindowClosed); + reject(new Error('La ventana se cerró antes de completar la transacción.')); + } + }, 500); + window.addEventListener('message', async(event) => { + if (event.data?.status === 'success') { + + console.log("check",event.data); + + const { public_key:publicKey, all_keys:allKeys, account_id:accountId } = event.data; + console.log({publicKey, allKeys, accountId}); + + // _state.keyStore.setKey(options.network.networkId, account_id,public_key); + // const keyPair = await this._keyStore.getKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); + // await this._keyStore.setKey(this._networkId, accountId, keyPair); + // await this._keyStore.removeKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); + + localStorage.setItem('near_app_wallet_auth_key', JSON.stringify({ accountId, allKeys })); + if (publicKey) { + _state.keyStore.setKey(options.network.networkId, accountId,publicKey); + } + // ?account_id=maguila.testnet&all_keys=ed25519%3AAtH7GEjv2qmBVoT8qoRhWXizXM5CC12DC6tiqY9iNoRm + childWindow?.close(); + window.removeEventListener('message', () => { }); + // emitter.emit("signedIn", { + // contractId: contractId, + // methodNames: methodNames ?? [], + // accounts: await getAccounts(), + // }); + + // return await getAccounts(); + // window.location.assign(parsedUrl.toString()); + _state.wallet.isSignedIn = () => true; + return resolve([{accountId,publicKey}]); + } + }); + }) + // return getAccounts(); + }, + + async signOut() { + if (_state.wallet.isSignedIn()) { + // _state.wallet.isSignedIn = () => false; + _state.wallet.signOut(); + } + }, + + async getAccounts() { + return getAccounts(); + }, + + async verifyOwner() { + throw new Error(`Method not supported by ${metadata.name}`); + }, + + async signMessage({ message, nonce, recipient, callbackUrl, state }) { + logger.log("sign message", { message }); + + if (id !== "my-near-wallet") { + throw Error( + `The signMessage method is not supported by ${metadata.name}` + ); + } + + const locationUrl = + typeof window !== "undefined" ? window.location.href : ""; + + const url = callbackUrl || locationUrl; + + if (!url) { + throw new Error(`The callbackUrl is missing for ${metadata.name}`); + } + + const href = new URL(params.walletUrl); + href.pathname = "sign-message"; + href.searchParams.append("message", message); + href.searchParams.append("nonce", nonce.toString("base64")); + href.searchParams.append("recipient", recipient); + href.searchParams.append("callbackUrl", url); + if (state) { + href.searchParams.append("state", state); + } + + window.location.replace(href.toString()); + + return; + }, + + async signAndSendTransaction({ + signerId, + receiverId, + actions, + callbackUrl, + }) { + logger.log("signAndSendTransaction", { + signerId, + receiverId, + actions, + callbackUrl, + }); + + const { contract } = store.getState(); + + if (!_state.wallet.isSignedIn() || !contract) { + throw new Error("Wallet not signed in"); + } + // const account = _state.wallet.account(); + // console.log( "pepe",account["signAndSendTransaction"]) + const account = _state.wallet.account(); + // const { networkId, signer, provider } = account.connection; + return account["signAndSendTransaction"]({ + receiverId: receiverId || contract.contractId, + actions: actions.map((action) => createAction(action)), + walletCallbackUrl: callbackUrl, + }); + // const account = _state.wallet.account(); + // const { networkId, signer, provider } = account.connection; + + // const block = await provider.block({ finality: "final" }); + + + // const transactions = await nearAPI.transactions.createTransaction( + // signerId || account.accountId, + // PublicKey.fromString("ed25519:AtH7GEjv2qmBVoT8qoRhWXizXM5CC12DC6tiqY9iNoRm"), + // receiverId || contract.contractId, + // 0, + // actions.map((action) => createAction(action)), + // new Uint8Array(32) + // ); + // // @ts-ignore + // // console.log({transactions,test:atob(transactions.encode())}); + // // console.log("orginal",atob("DwAAAG1hZ3VpbGEudGVzdG5ldACS3ARWu0sYjX63OYbDojizriWL55RnrStWodM6c%2BbIrMrc%2F3zXjAAAGwAAAGhlbGxvLm5lYXItZXhhbXBsZXMudGVzdG5ldF6mKSLVD4Nu%2ByY53uE0fD4CCaELfzNdK18eVlDURC4QAQAAAAIMAAAAc2V0X2dyZWV0aW5nEQAAAHsiZ3JlZXRpbmciOiJoaSJ9AOBX60gbAAAAAAAAAAAAAAAAAAAAAAAA")); + // console.log({signerId, + // receiverId, + // actions, + // callbackUrl}); + + + + // const publishUrl = new URL('sign', `https://localhost:1234`); + // publishUrl.searchParams.set('transactions', Buffer.from(transactions.encode()).toString("base64")); + // publishUrl.searchParams.set('callbackUrl', "http://localhost:3000/hello-near"); + // // @ts-ignore + // const childWindow = window.open(publishUrl.toString(),"My Near Wallet", "width=480,height=640"); + + // const childWindow = window.open("https://localhost:1234/sign?transactions=DwAAAG1hZ3VpbGEudGVzdG5ldACS3ARWu0sYjX63OYbDojizriWL55RnrStWodM6c%2BbIrMrc%2F3zXjAAAGwAAAGhlbGxvLm5lYXItZXhhbXBsZXMudGVzdG5ldF6mKSLVD4Nu%2ByY53uE0fD4CCaELfzNdK18eVlDURC4QAQAAAAIMAAAAc2V0X2dyZWV0aW5nEQAAAHsiZ3JlZXRpbmciOiJoaSJ9AOBX60gbAAAAAAAAAAAAAAAAAAAAAAAA&callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fhello-near", "Ventana Secundaria", "width=400,height=400"); + + + // return new Promise((resolve, reject) => { + // const checkWindowClosed = setInterval(() => { + // if (childWindow?.closed) { + // clearInterval(checkWindowClosed); + // reject(new Error('La ventana se cerró antes de completar la transacción.')); + // } + // }, 500); + // window.addEventListener('message', async(event) => { + // if (event.data?.status === 'success') { + // console.log('Transacción exitosa'); + // childWindow?.close(); + // window.removeEventListener('message', () => { }); + + + // const result = await provider.txStatus(event.data?.transactionHashes, 'unused',"NONE"); + + // resolve(result); + // } + // }); + // }) + }, + + async signAndSendTransactions({ transactions, callbackUrl }) { + logger.log("signAndSendTransactions", { transactions, callbackUrl }); + + if (!_state.wallet.isSignedIn()) { + throw new Error("Wallet not signed in"); + } + + return _state.wallet.requestSignTransactions({ + transactions: await transformTransactions(transactions), + callbackUrl, + }); + }, + + buildImportAccountsUrl() { + return `${params.walletUrl}/batch-import`; + }, + }; +}; + +export function setupMyNearWallet({ + walletUrl, + iconUrl = icon, + deprecated = false, + successUrl = "", + failureUrl = "", +}: MyNearWalletParams = {}): WalletModuleFactory { + return async (moduleOptions) => { + return { + id: "my-near-wallet", + type: "browser", + metadata: { + name: "MyNearWallet", + description: + "NEAR wallet to store, buy, send and stake assets for DeFi.", + iconUrl, + deprecated, + available: true, + successUrl, + failureUrl, + walletUrl: resolveWalletUrl(moduleOptions.options.network, walletUrl), + }, + init: (options) => { + return MyNearWallet({ + ...options, + params: { + walletUrl: resolveWalletUrl(options.options.network, walletUrl), + }, + }); + }, + }; + }; +} + +// signAndSendTransaction({receiverId, actions, walletMeta, walletCallbackUrl=window.location.href}) { +// const _super = Object.create(null, { +// signAndSendTransaction: { +// get: ()=>super.signAndSendTransaction +// } +// }); +// return __awaiter(this, void 0, void 0, function*() { +// const localKey = yield this.connection.signer.getPublicKey(this.accountId, this.connection.networkId); +// let accessKey = yield this.accessKeyForTransaction(receiverId, actions, localKey); +// if (!accessKey) { +// throw new Error(`Cannot find matching key for transaction sent to ${receiverId}`); +// } +// if (localKey && localKey.toString() === accessKey.public_key) { +// try { +// return yield _super.signAndSendTransaction.call(this, { +// receiverId, +// actions +// }); +// } catch (e) { +// if (e.type === 'NotEnoughAllowance') { +// accessKey = yield this.accessKeyForTransaction(receiverId, actions); +// } else { +// throw e; +// } +// } +// } +// const block = yield this.connection.provider.block({ +// finality: 'final' +// }); +// const blockHash = (0, +// utils_1.baseDecode)(block.header.hash); +// const publicKey = crypto_1.PublicKey.from(accessKey.public_key); +// // TODO: Cache & listen for nonce updates for given access key +// const nonce = accessKey.access_key.nonce + BigInt(1); +// const transaction = (0, +// transactions_1.createTransaction)(this.accountId, publicKey, receiverId, nonce, actions, blockHash); +// yield this.walletConnection.requestSignTransactions({ +// transactions: [transaction], +// meta: walletMeta, +// callbackUrl: walletCallbackUrl +// }); +// return new Promise((resolve,reject)=>{ +// setTimeout(()=>{ +// reject(new Error('Failed to redirect to sign transaction')); +// } +// , 1000); +// } +// ); +// // TODO: Aggregate multiple transaction request with "debounce". +// // TODO: Introduce TransactionQueue which also can be used to watch for status? +// }); +// } \ No newline at end of file diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts new file mode 100644 index 000000000..219b2ead8 --- /dev/null +++ b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts @@ -0,0 +1,303 @@ +import { serialize } from "borsh"; +import { ConnectedWalletAccount, InMemorySigner, KeyPair, Near } from "near-api-js"; +import { KeyStore } from "near-api-js/lib/key_stores"; +import { SCHEMA, Transaction } from "near-api-js/lib/transaction"; + +const LOGIN_WALLET_URL_SUFFIX = '/login/'; +const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; +const LOCAL_STORAGE_KEY_SUFFIX = '_wallet_auth_key'; +const PENDING_ACCESS_KEY_PREFIX = 'pending_key'; // browser storage key for a pending access key (i.e. key has been generated but we are not sure it was added yet) + +interface SignInOptions { + contractId?: string; + methodNames?: string[]; + // TODO: Replace following with single callbackUrl + successUrl?: string; + failureUrl?: string; + keyType?: 'ed25519' | 'secp256k1' +} + +/** + * Information to send NEAR wallet for signing transactions and redirecting the browser back to the calling application + */ +interface RequestSignTransactionsOptions { + /** list of transactions to sign */ + transactions: Transaction[]; + /** url NEAR Wallet will redirect to after transaction signing is complete */ + callbackUrl?: string; + /** meta information NEAR Wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param */ + meta?: string; +} + +/** + * This class is not intended for use outside the browser. Without `window` (i.e. in server contexts), it will instantiate but will throw a clear error when used. + * + * @see [https://docs.near.org/tools/near-api-js/quick-reference#wallet](https://docs.near.org/tools/near-api-js/quick-reference#wallet) + * @example + * ```js + * // create new WalletConnection instance + * const wallet = new WalletConnection(near, 'my-app'); + * + * // If not signed in redirect to the NEAR wallet to sign in + * // keys will be stored in the BrowserLocalStorageKeyStore + * if(!wallet.isSignedIn()) return wallet.requestSignIn() + * ``` + */ +export class MyNearWalletConnection { + /** @hidden */ + _walletBaseUrl: string; + + /** @hidden */ + _authDataKey: string; + + /** @hidden */ + _keyStore: KeyStore; + + /** @hidden */ + _authData: { accountId?: string; allKeys?: string[] }; + + /** @hidden */ + _networkId: string; + + /** @hidden */ + // _near: Near; + _near: Near; + + /** @hidden */ + _connectedAccount: ConnectedWalletAccount; + + /** @hidden */ + _completeSignInPromise: Promise; + + constructor(near: Near, appKeyPrefix: string) { + if (typeof(appKeyPrefix) !== 'string') { + throw new Error('Please define a clear appKeyPrefix for this WalletConnection instance as the second argument to the constructor'); + } + + this._near = near; + const authDataKey = appKeyPrefix + LOCAL_STORAGE_KEY_SUFFIX; + const authData = JSON.parse(window.localStorage.getItem(authDataKey||'') || '{}'); + this._networkId = near.config.networkId; + this._walletBaseUrl = near.config.walletUrl; + appKeyPrefix = appKeyPrefix || near.config.contractName || 'default'; + this._keyStore = (near.connection.signer as InMemorySigner).keyStore; + this._authData = authData || { allKeys: [] }; + this._authDataKey = authDataKey; + if (!this.isSignedIn()) { + this._completeSignInPromise = this._completeSignInWithAccessKey(); + } + } + + /** + * Returns true, if this WalletConnection is authorized with the wallet. + * @example + * ```js + * const wallet = new WalletConnection(near, 'my-app'); + * wallet.isSignedIn(); + * ``` + */ + isSignedIn() { + return !!this._authData.accountId; + } + + /** + * Returns promise of completing signing in after redirecting from wallet + * @example + * ```js + * // on login callback page + * const wallet = new WalletConnection(near, 'my-app'); + * wallet.isSignedIn(); // false + * await wallet.isSignedInAsync(); // true + * ``` + */ + async isSignedInAsync() { + if (!this._completeSignInPromise) { + return this.isSignedIn(); + } + + await this._completeSignInPromise; + return this.isSignedIn(); + } + + /** + * Returns authorized Account ID. + * @example + * ```js + * const wallet = new WalletConnection(near, 'my-app'); + * wallet.getAccountId(); + * ``` + */ + getAccountId() { + return this._authData.accountId || ''; + } + + /** + * Constructs string URL to the wallet authentication page. + * @param options An optional options object + * @param options.contractId The NEAR account where the contract is deployed + * @param options.successUrl URL to redirect upon success. Default: current url + * @param options.failureUrl URL to redirect upon failure. Default: current url + * + * @example + * ```js + * const wallet = new WalletConnection(near, 'my-app'); + * // return string URL to the NEAR Wallet + * const url = await wallet.requestSignInUrl({ contractId: 'account-with-deploy-contract.near' }); + * ``` + */ + async requestSignInUrl({contractId, methodNames, successUrl, failureUrl, keyType = 'ed25519'}: SignInOptions): Promise { + const currentUrl = new URL(window.location.href); + const newUrl = new URL(this._walletBaseUrl + LOGIN_WALLET_URL_SUFFIX); + newUrl.searchParams.set('success_url', successUrl || currentUrl.href); + newUrl.searchParams.set('failure_url', failureUrl || currentUrl.href); + if (contractId) { + /* Throws exception if contract account does not exist */ + const contractAccount = await this._near.account(contractId); + await contractAccount.state(); + + newUrl.searchParams.set('contract_id', contractId); + const accessKey = KeyPair.fromRandom(keyType); + newUrl.searchParams.set('public_key', accessKey.getPublicKey().toString()); + await this._keyStore.setKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey(), accessKey); + } + + if (methodNames) { + methodNames.forEach(methodName => { + newUrl.searchParams.append('methodNames', methodName); + }); + } + + return newUrl.toString(); + } + + /** + * Redirects current page to the wallet authentication page. + * @param options An optional options object + * @param options.contractId The NEAR account where the contract is deployed + * @param options.successUrl URL to redirect upon success. Default: current url + * @param options.failureUrl URL to redirect upon failure. Default: current url + * + * @example + * ```js + * const wallet = new WalletConnection(near, 'my-app'); + * // redirects to the NEAR Wallet + * wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' }); + * ``` + */ + async requestSignIn(options: SignInOptions) { + const url = await this.requestSignInUrl(options); + + window.location.assign(url); + } + + /** + * Constructs string URL to the wallet to sign a transaction or batch of transactions. + * + * @param options A required options object + * @param options.transactions List of transactions to sign + * @param options.callbackUrl URL to redirect upon success. Default: current url + * @param options.meta Meta information the wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param + * + */ + requestSignTransactionsUrl({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): string { + const currentUrl = new URL(window.location.href); + const newUrl = new URL('sign', this._walletBaseUrl); + + newUrl.searchParams.set('transactions', transactions + .map(transaction => serialize(SCHEMA.Transaction, transaction)) + .map(serialized => Buffer.from(serialized).toString('base64')) + .join(',')); + newUrl.searchParams.set('callbackUrl', callbackUrl || currentUrl.href); + if (meta) newUrl.searchParams.set('meta', meta); + + return newUrl.toString(); + } + + /** + * Requests the user to quickly sign for a transaction or batch of transactions by redirecting to the wallet. + * + * @param options A required options object + * @param options.transactions List of transactions to sign + * @param options.callbackUrl URL to redirect upon success. Default: current url + * @param options.meta Meta information the wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param + * + */ + requestSignTransactions(options: RequestSignTransactionsOptions): void { + const url = this.requestSignTransactionsUrl(options); + + window.location.assign(url); + } + + /** + * @hidden + * Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet. + */ + async _completeSignInWithAccessKey() { + const currentUrl = new URL(window.location.href); + const publicKey = currentUrl.searchParams.get('public_key') || ''; + const allKeys = (currentUrl.searchParams.get('all_keys') || '').split(','); + const accountId = currentUrl.searchParams.get('account_id') || ''; + // TODO: Handle errors during login + if (accountId) { + const authData = { + accountId, + allKeys + }; + window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); + if (publicKey) { + await this._moveKeyFromTempToPermanent(accountId, publicKey); + } + this._authData = authData; + } + currentUrl.searchParams.delete('public_key'); + currentUrl.searchParams.delete('all_keys'); + currentUrl.searchParams.delete('account_id'); + currentUrl.searchParams.delete('meta'); + currentUrl.searchParams.delete('transactionHashes'); + + window.history.replaceState({}, document.title, currentUrl.toString()); + } + + async completeSignInWithAccessKeys({accountId,allKeys,publicKey}: {accountId: string, allKeys: string[], publicKey: string}) { + const authData = { + accountId, + allKeys + }; + window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); + if (publicKey) { + await this._moveKeyFromTempToPermanent(accountId, publicKey); + } + this._authData = authData; + } + + /** + * @hidden + * @param accountId The NEAR account owning the given public key + * @param publicKey The public key being set to the key store + */ + async _moveKeyFromTempToPermanent(accountId: string, publicKey: string) { + const keyPair = await this._keyStore.getKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); + await this._keyStore.setKey(this._networkId, accountId, keyPair); + await this._keyStore.removeKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); + } + + /** + * Sign out from the current account + * @example + * walletConnection.signOut(); + */ + signOut() { + this._authData = {}; + window.localStorage.removeItem(this._authDataKey); + } + + /** + * Returns the current connected wallet account + */ + account() { + if (!this._connectedAccount) { + this._connectedAccount = new ConnectedWalletAccount(this, this._near.connection, this._authData.accountId || ""); + } + return this._connectedAccount; + } +} \ No newline at end of file diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.ts b/packages/my-near-wallet/src/lib/my-near-wallet.ts index 5ac291a8d..9a4dd6689 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet.ts @@ -10,6 +10,8 @@ import type { } from "@near-wallet-selector/core"; import { createAction } from "@near-wallet-selector/wallet-utils"; import icon from "./icon"; +import { PublicKey } from "near-api-js/lib/utils"; +import { MyNearWalletConnection } from "./my-near-wallet-connection"; export interface MyNearWalletParams { walletUrl?: string; @@ -20,8 +22,9 @@ export interface MyNearWalletParams { } interface MyNearWalletState { - wallet: nearAPI.WalletConnection; + wallet: MyNearWalletConnection; keyStore: nearAPI.keyStores.BrowserLocalStorageKeyStore; + // resetWallet: ()=>void; } interface MyNearWalletExtraOptions { @@ -56,23 +59,23 @@ const setupWalletState = async ( headers: {}, }); - const wallet = new nearAPI.WalletConnection(near, "near_app"); - + let wallet = new MyNearWalletConnection(near, "near_app"); + // const resetWallet = () =>{wallet = new nearAPI.WalletConnection(near, "near_app")} return { wallet, keyStore, + // resetWallet }; }; const MyNearWallet: WalletBehaviourFactory< BrowserWallet, { params: MyNearWalletExtraOptions } -> = async ({ metadata, options, store, params, logger, id }) => { +> = async ({ metadata, options, store, params, logger, id, emitter, storage }) => { const _state = await setupWalletState(params, options.network); const getAccounts = async (): Promise> => { const accountId = _state.wallet.getAccountId(); const account = _state.wallet.account(); - if (!accountId || !account) { return []; } @@ -87,6 +90,13 @@ const MyNearWallet: WalletBehaviourFactory< publicKey: publicKey ? publicKey.toString() : "", }, ]; + // const {accountId,publicKey} = JSON.parse(localStorage.getItem('my-near-wallet') || "{}"); + + // if(!accountId || !publicKey){ + // return []; + // } + + // return [{accountId,publicKey}]; }; const transformTransactions = async ( @@ -138,18 +148,75 @@ const MyNearWallet: WalletBehaviourFactory< return existingAccounts; } - await _state.wallet.requestSignIn({ + // await _state.wallet.requestSignIn({ + // contractId, + // methodNames, + // successUrl, + // failureUrl, + // }); + + // return getAccounts(); + + + const url = await _state.wallet.requestSignInUrl({ contractId, methodNames, successUrl, failureUrl, }); - - return getAccounts(); + + // contractId && publishUrl.searchParams.set('contractId', contractId ); + // methodNames && publishUrl.searchParams.set('methodNames', methodNames.join(",") ); + + console.log("Url requestSignInUrl 23",url); + + // @ts-ignore + const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); + + return await new Promise((resolve, reject) => { + const checkWindowClosed = setInterval(() => { + if (childWindow?.closed) { + clearInterval(checkWindowClosed); + reject(new Error('La ventana se cerró antes de completar la transacción.')); + } + }, 500); + window.addEventListener('message', async(event) => { + if (event.data?.status === 'success') { + + console.log("check",event.data); + + const { public_key:publicKey, all_keys:allKeys, account_id:accountId } = event.data; + console.log({publicKey, allKeys, accountId}); + + // _state.keyStore.setKey(options.network.networkId, account_id,public_key); + // const keyPair = await this._keyStore.getKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); + // await this._keyStore.setKey(this._networkId, accountId, keyPair); + // await this._keyStore.removeKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); + + await _state.wallet.completeSignInWithAccessKeys({accountId,publicKey,allKeys}); + // ?account_id=maguila.testnet&all_keys=ed25519%3AAtH7GEjv2qmBVoT8qoRhWXizXM5CC12DC6tiqY9iNoRm + childWindow?.close(); + window.removeEventListener('message', () => { }); + // emitter.emit("signedIn", { + // contractId: contractId, + // methodNames: methodNames ?? [], + // accounts: await getAccounts(), + // }); + + // return await getAccounts(); + // window.location.assign(parsedUrl.toString()); + // _state.wallet.isSignedIn = () => true; + // _state.resetWallet(); + return resolve([{accountId,publicKey}]); + } + }); + }) + // return getAccounts(); }, async signOut() { if (_state.wallet.isSignedIn()) { + // _state.wallet.isSignedIn = () => false; _state.wallet.signOut(); } }, @@ -194,7 +261,7 @@ const MyNearWallet: WalletBehaviourFactory< return; }, - + async signAndSendTransaction({ signerId, receiverId, @@ -213,14 +280,69 @@ const MyNearWallet: WalletBehaviourFactory< if (!_state.wallet.isSignedIn() || !contract) { throw new Error("Wallet not signed in"); } - + // const account = _state.wallet.account(); + // console.log( "pepe",account["signAndSendTransaction"]) + // const account = _state.wallet.account(); + // // const { networkId, signer, provider } = account.connection; + // return account["signAndSendTransaction"]({ + // receiverId: receiverId || contract.contractId, + // actions: actions.map((action) => createAction(action)), + // walletCallbackUrl: callbackUrl, + // }); const account = _state.wallet.account(); - - return account["signAndSendTransaction"]({ - receiverId: receiverId || contract.contractId, - actions: actions.map((action) => createAction(action)), - walletCallbackUrl: callbackUrl, - }); + const { networkId, signer, provider } = account.connection; + + const block = await provider.block({ finality: "final" }); + + + const transactions = await nearAPI.transactions.createTransaction( + signerId || account.accountId, + PublicKey.fromString("ed25519:AtH7GEjv2qmBVoT8qoRhWXizXM5CC12DC6tiqY9iNoRm"), + receiverId || contract.contractId, + 0, + actions.map((action) => createAction(action)), + new Uint8Array(32) + ); + // @ts-ignore + // console.log({transactions,test:atob(transactions.encode())}); + // console.log("orginal",atob("DwAAAG1hZ3VpbGEudGVzdG5ldACS3ARWu0sYjX63OYbDojizriWL55RnrStWodM6c%2BbIrMrc%2F3zXjAAAGwAAAGhlbGxvLm5lYXItZXhhbXBsZXMudGVzdG5ldF6mKSLVD4Nu%2ByY53uE0fD4CCaELfzNdK18eVlDURC4QAQAAAAIMAAAAc2V0X2dyZWV0aW5nEQAAAHsiZ3JlZXRpbmciOiJoaSJ9AOBX60gbAAAAAAAAAAAAAAAAAAAAAAAA")); + console.log({signerId, + receiverId, + actions, + callbackUrl}); + + + + const publishUrl = new URL('sign', `https://localhost:1234`); + publishUrl.searchParams.set('transactions', Buffer.from(transactions.encode()).toString("base64")); + publishUrl.searchParams.set('callbackUrl', "http://localhost:3000/hello-near"); + // @ts-ignore + const childWindow = window.open(publishUrl.toString(),"My Near Wallet", "width=480,height=640"); + + // const childWindow = window.open("https://localhost:1234/sign?transactions=DwAAAG1hZ3VpbGEudGVzdG5ldACS3ARWu0sYjX63OYbDojizriWL55RnrStWodM6c%2BbIrMrc%2F3zXjAAAGwAAAGhlbGxvLm5lYXItZXhhbXBsZXMudGVzdG5ldF6mKSLVD4Nu%2ByY53uE0fD4CCaELfzNdK18eVlDURC4QAQAAAAIMAAAAc2V0X2dyZWV0aW5nEQAAAHsiZ3JlZXRpbmciOiJoaSJ9AOBX60gbAAAAAAAAAAAAAAAAAAAAAAAA&callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fhello-near", "Ventana Secundaria", "width=400,height=400"); + + + return new Promise((resolve, reject) => { + const checkWindowClosed = setInterval(() => { + if (childWindow?.closed) { + clearInterval(checkWindowClosed); + reject(new Error('La ventana se cerró antes de completar la transacción.')); + } + }, 1000); + window.addEventListener('message', async(event) => { + clearInterval(checkWindowClosed); + if (event.data?.status === 'success') { + console.log('Transacción exitosa'); + childWindow?.close(); + window.removeEventListener('message', () => { }); + + + const result = await provider.txStatus(event.data?.transactionHashes, 'unused',"NONE"); + + resolve(result); + } + }); + }) }, async signAndSendTransactions({ transactions, callbackUrl }) { @@ -274,4 +396,4 @@ export function setupMyNearWallet({ }, }; }; -} +} \ No newline at end of file From c667214631b0147c871d22d9085024db48d15bd1 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Sat, 25 Jan 2025 11:21:41 -0300 Subject: [PATCH 03/21] feat: clear and create MynearWalletConnection --- .../src/lib/my-near-wallet-connection.ts | 208 +++++++++++++++++- .../my-near-wallet/src/lib/my-near-wallet.ts | 140 +----------- 2 files changed, 209 insertions(+), 139 deletions(-) diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts index 219b2ead8..c22e6c873 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts @@ -1,7 +1,11 @@ import { serialize } from "borsh"; -import { ConnectedWalletAccount, InMemorySigner, KeyPair, Near } from "near-api-js"; +import { Account, Connection, InMemorySigner, KeyPair, Near } from "near-api-js"; +import { SignAndSendTransactionOptions } from "near-api-js/lib/account"; import { KeyStore } from "near-api-js/lib/key_stores"; -import { SCHEMA, Transaction } from "near-api-js/lib/transaction"; +import { FinalExecutionOutcome } from "near-api-js/lib/providers"; +import { Action, SCHEMA, Transaction, createTransaction } from "near-api-js/lib/transaction"; +import { PublicKey } from "near-api-js/lib/utils"; +import { base_decode } from "near-api-js/lib/utils/serialize"; const LOGIN_WALLET_URL_SUFFIX = '/login/'; const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; @@ -64,7 +68,7 @@ export class MyNearWalletConnection { _near: Near; /** @hidden */ - _connectedAccount: ConnectedWalletAccount; + _connectedAccount: MyNearConnectedWalletAccount; /** @hidden */ _completeSignInPromise: Promise; @@ -185,9 +189,31 @@ export class MyNearWalletConnection { * ``` */ async requestSignIn(options: SignInOptions) { - const url = await this.requestSignInUrl(options); - - window.location.assign(url); + + const url = await this.requestSignInUrl(options); + + // @ts-ignore + const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); + + return await new Promise((resolve, reject) => { + const checkWindowClosed = setInterval(() => { + if (childWindow?.closed) { + clearInterval(checkWindowClosed); + reject(new Error('La ventana se cerró antes de completar la transacción.')); + } + }, 500); + window.addEventListener('message', async(event) => { + if (event.data?.status === 'success') { + + const { public_key:publicKey, all_keys:allKeys, account_id:accountId } = event.data; + + await this.completeSignInWithAccessKeys({accountId,publicKey,allKeys}); + childWindow?.close(); + window.removeEventListener('message', () => { }); + return resolve([{accountId,publicKey}]); + } + }); + }) } /** @@ -228,6 +254,34 @@ export class MyNearWalletConnection { window.location.assign(url); } + + requestSignTransaction(options: RequestSignTransactionsOptions): Promise { + const url = this.requestSignTransactionsUrl(options); + // @ts-ignore + const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); + + return new Promise((resolve, reject) => { + const checkWindowClosed = setInterval(() => { + if (childWindow?.closed) { + clearInterval(checkWindowClosed); + reject(new Error('La ventana se cerró antes de completar la transacción.')); + } + }, 1000); + window.addEventListener('message', async(event) => { + clearInterval(checkWindowClosed); + if (event.data?.status === 'success') { + console.log('Transacción exitosa'); + childWindow?.close(); + window.removeEventListener('message', () => {}); + console.log("eventos",event.data?.transactionHashes); + + resolve(event.data?.transactionHashes); + } + }); + }) + + } + /** * @hidden * Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet. @@ -268,6 +322,7 @@ export class MyNearWalletConnection { await this._moveKeyFromTempToPermanent(accountId, publicKey); } this._authData = authData; + this.updateAccount() } /** @@ -296,8 +351,145 @@ export class MyNearWalletConnection { */ account() { if (!this._connectedAccount) { - this._connectedAccount = new ConnectedWalletAccount(this, this._near.connection, this._authData.accountId || ""); + this._connectedAccount = new MyNearConnectedWalletAccount(this, this._near.connection, this._authData.accountId || ""); } return this._connectedAccount; } -} \ No newline at end of file + + updateAccount() { + this._connectedAccount = new MyNearConnectedWalletAccount(this, this._near.connection, this._authData.accountId || ""); + } +} + +/** + * {@link Account} implementation which redirects to wallet using {@link WalletConnection} when no local key is available. + */ + +export class MyNearConnectedWalletAccount extends Account { + walletConnection: MyNearWalletConnection; + + constructor(walletConnection: MyNearWalletConnection, connection: Connection, accountId: string) { + super(connection, accountId); + this.walletConnection = walletConnection; + } + + // Overriding Account methods + + /** + * Sign a transaction by redirecting to the NEAR Wallet + * @see {@link WalletConnection#requestSignTransactions} + * @param options An optional options object + * @param options.receiverId The NEAR account ID of the transaction receiver. + * @param options.actions An array of transaction actions to be performed. + * @param options.walletMeta Additional metadata to be included in the wallet signing request. + * @param options.walletCallbackUrl URL to redirect upon completion of the wallet signing process. Default: current URL. + */ + async signAndSendTransaction({ receiverId, actions, walletMeta, walletCallbackUrl = window.location.href }: SignAndSendTransactionOptions): Promise { + const localKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId); + let accessKey = await this.accessKeyForTransaction(receiverId, actions, localKey); + if (!accessKey) { + throw new Error(`Cannot find matching key for transaction sent to ${receiverId}`); + } + + if (localKey && localKey.toString() === accessKey.public_key) { + try { + return await super.signAndSendTransaction({ receiverId, actions }); + } catch (e: unknown) { + if (typeof e === 'object' && e !== null && 'type' in e && (e as any).type === 'NotEnoughAllowance') { + accessKey = await this.accessKeyForTransaction(receiverId, actions); + } else { + throw e; + } + } + } + + const block = await this.connection.provider.block({ finality: 'final' }); + const blockHash = base_decode(block.header.hash); + + const publicKey = PublicKey.from(accessKey.public_key); + // TODO: Cache & listen for nonce updates for given access key + const nonce = accessKey.access_key.nonce + BigInt("1"); + const transaction = createTransaction(this.accountId, publicKey, receiverId, nonce, actions, blockHash); + const transactionHashes = await this.walletConnection.requestSignTransaction({ + transactions: [transaction], + meta: walletMeta, + callbackUrl: walletCallbackUrl + }); + + return new Promise(async(resolve, reject) => { + const result = await this.connection.provider.txStatus(transactionHashes, 'unused',"NONE"); + resolve(result); + setTimeout(() => { + reject(new Error('Failed to redirect to sign transaction')); + }, 1000); + }); + + + + // TODO: Aggregate multiple transaction request with "debounce". + // TODO: Introduce TransactionQueue which also can be used to watch for status? + } + + /** + * Check if given access key allows the function call or method attempted in transaction + * @param accessKey Array of \{access_key: AccessKey, public_key: PublicKey\} items + * @param receiverId The NEAR account attempting to have access + * @param actions The action(s) needed to be checked for access + */ + async accessKeyMatchesTransaction(accessKey: any, receiverId: string, actions: Action[]): Promise { + const { access_key: { permission } } = accessKey; + if (permission === 'FullAccess') { + return true; + } + + if (permission.FunctionCall) { + const { receiver_id: allowedReceiverId, method_names: allowedMethods } = permission.FunctionCall; + /******************************** + Accept multisig access keys and let wallets attempt to signAndSendTransaction + If an access key has itself as receiverId and method permission add_request_and_confirm, then it is being used in a wallet with multisig contract: https://github.com/near/core-contracts/blob/671c05f09abecabe7a7e58efe942550a35fc3292/multisig/src/lib.rs#L149-L153 + ********************************/ + if (allowedReceiverId === this.accountId && allowedMethods.includes(MULTISIG_HAS_METHOD)) { + return true; + } + if (allowedReceiverId === receiverId) { + if (actions.length !== 1) { + return false; + } + const [{ functionCall }] = actions; + return functionCall && + (!functionCall.deposit || functionCall.deposit.toString() === '0') && // TODO: Should support charging amount smaller than allowance? + (allowedMethods.length === 0 || allowedMethods.includes(functionCall.methodName)); + // TODO: Handle cases when allowance doesn't have enough to pay for gas + } + } + // TODO: Support other permissions than FunctionCall + + return false; + } + + /** + * Helper function returning the access key (if it exists) to the receiver that grants the designated permission + * @param receiverId The NEAR account seeking the access key for a transaction + * @param actions The action(s) sought to gain access to + * @param localKey A local public key provided to check for access + */ + async accessKeyForTransaction(receiverId: string, actions: Action[], localKey?: PublicKey): Promise { + const accessKeys = await this.getAccessKeys(); + + if (localKey) { + const accessKey = accessKeys.find(key => key.public_key.toString() === localKey.toString()); + if (accessKey && await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) { + return accessKey; + } + } + + const walletKeys = this.walletConnection._authData.allKeys; + for (const accessKey of accessKeys) { + if (walletKeys && walletKeys.indexOf(accessKey.public_key) !== -1 && await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) { + return accessKey; + } + } + + return null; + } +} diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.ts b/packages/my-near-wallet/src/lib/my-near-wallet.ts index 9a4dd6689..1a57b8ad9 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet.ts @@ -10,7 +10,6 @@ import type { } from "@near-wallet-selector/core"; import { createAction } from "@near-wallet-selector/wallet-utils"; import icon from "./icon"; -import { PublicKey } from "near-api-js/lib/utils"; import { MyNearWalletConnection } from "./my-near-wallet-connection"; export interface MyNearWalletParams { @@ -24,7 +23,6 @@ export interface MyNearWalletParams { interface MyNearWalletState { wallet: MyNearWalletConnection; keyStore: nearAPI.keyStores.BrowserLocalStorageKeyStore; - // resetWallet: ()=>void; } interface MyNearWalletExtraOptions { @@ -60,11 +58,10 @@ const setupWalletState = async ( }); let wallet = new MyNearWalletConnection(near, "near_app"); - // const resetWallet = () =>{wallet = new nearAPI.WalletConnection(near, "near_app")} + return { wallet, keyStore, - // resetWallet }; }; @@ -90,13 +87,6 @@ const MyNearWallet: WalletBehaviourFactory< publicKey: publicKey ? publicKey.toString() : "", }, ]; - // const {accountId,publicKey} = JSON.parse(localStorage.getItem('my-near-wallet') || "{}"); - - // if(!accountId || !publicKey){ - // return []; - // } - - // return [{accountId,publicKey}]; }; const transformTransactions = async ( @@ -148,75 +138,18 @@ const MyNearWallet: WalletBehaviourFactory< return existingAccounts; } - // await _state.wallet.requestSignIn({ - // contractId, - // methodNames, - // successUrl, - // failureUrl, - // }); - - // return getAccounts(); - - - const url = await _state.wallet.requestSignInUrl({ + await _state.wallet.requestSignIn({ contractId, methodNames, successUrl, failureUrl, }); - - // contractId && publishUrl.searchParams.set('contractId', contractId ); - // methodNames && publishUrl.searchParams.set('methodNames', methodNames.join(",") ); - console.log("Url requestSignInUrl 23",url); - - // @ts-ignore - const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); - - return await new Promise((resolve, reject) => { - const checkWindowClosed = setInterval(() => { - if (childWindow?.closed) { - clearInterval(checkWindowClosed); - reject(new Error('La ventana se cerró antes de completar la transacción.')); - } - }, 500); - window.addEventListener('message', async(event) => { - if (event.data?.status === 'success') { - - console.log("check",event.data); - - const { public_key:publicKey, all_keys:allKeys, account_id:accountId } = event.data; - console.log({publicKey, allKeys, accountId}); - - // _state.keyStore.setKey(options.network.networkId, account_id,public_key); - // const keyPair = await this._keyStore.getKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); - // await this._keyStore.setKey(this._networkId, accountId, keyPair); - // await this._keyStore.removeKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); - - await _state.wallet.completeSignInWithAccessKeys({accountId,publicKey,allKeys}); - // ?account_id=maguila.testnet&all_keys=ed25519%3AAtH7GEjv2qmBVoT8qoRhWXizXM5CC12DC6tiqY9iNoRm - childWindow?.close(); - window.removeEventListener('message', () => { }); - // emitter.emit("signedIn", { - // contractId: contractId, - // methodNames: methodNames ?? [], - // accounts: await getAccounts(), - // }); - - // return await getAccounts(); - // window.location.assign(parsedUrl.toString()); - // _state.wallet.isSignedIn = () => true; - // _state.resetWallet(); - return resolve([{accountId,publicKey}]); - } - }); - }) - // return getAccounts(); + return getAccounts(); }, async signOut() { if (_state.wallet.isSignedIn()) { - // _state.wallet.isSignedIn = () => false; _state.wallet.signOut(); } }, @@ -280,69 +213,14 @@ const MyNearWallet: WalletBehaviourFactory< if (!_state.wallet.isSignedIn() || !contract) { throw new Error("Wallet not signed in"); } - // const account = _state.wallet.account(); - // console.log( "pepe",account["signAndSendTransaction"]) - // const account = _state.wallet.account(); - // // const { networkId, signer, provider } = account.connection; - // return account["signAndSendTransaction"]({ - // receiverId: receiverId || contract.contractId, - // actions: actions.map((action) => createAction(action)), - // walletCallbackUrl: callbackUrl, - // }); const account = _state.wallet.account(); - const { networkId, signer, provider } = account.connection; - - const block = await provider.block({ finality: "final" }); - - - const transactions = await nearAPI.transactions.createTransaction( - signerId || account.accountId, - PublicKey.fromString("ed25519:AtH7GEjv2qmBVoT8qoRhWXizXM5CC12DC6tiqY9iNoRm"), - receiverId || contract.contractId, - 0, - actions.map((action) => createAction(action)), - new Uint8Array(32) - ); - // @ts-ignore - // console.log({transactions,test:atob(transactions.encode())}); - // console.log("orginal",atob("DwAAAG1hZ3VpbGEudGVzdG5ldACS3ARWu0sYjX63OYbDojizriWL55RnrStWodM6c%2BbIrMrc%2F3zXjAAAGwAAAGhlbGxvLm5lYXItZXhhbXBsZXMudGVzdG5ldF6mKSLVD4Nu%2ByY53uE0fD4CCaELfzNdK18eVlDURC4QAQAAAAIMAAAAc2V0X2dyZWV0aW5nEQAAAHsiZ3JlZXRpbmciOiJoaSJ9AOBX60gbAAAAAAAAAAAAAAAAAAAAAAAA")); - console.log({signerId, - receiverId, - actions, - callbackUrl}); + console.log({ receiverId, contractId: contract.contractId,actions, callbackUrl }); - - - const publishUrl = new URL('sign', `https://localhost:1234`); - publishUrl.searchParams.set('transactions', Buffer.from(transactions.encode()).toString("base64")); - publishUrl.searchParams.set('callbackUrl', "http://localhost:3000/hello-near"); - // @ts-ignore - const childWindow = window.open(publishUrl.toString(),"My Near Wallet", "width=480,height=640"); - - // const childWindow = window.open("https://localhost:1234/sign?transactions=DwAAAG1hZ3VpbGEudGVzdG5ldACS3ARWu0sYjX63OYbDojizriWL55RnrStWodM6c%2BbIrMrc%2F3zXjAAAGwAAAGhlbGxvLm5lYXItZXhhbXBsZXMudGVzdG5ldF6mKSLVD4Nu%2ByY53uE0fD4CCaELfzNdK18eVlDURC4QAQAAAAIMAAAAc2V0X2dyZWV0aW5nEQAAAHsiZ3JlZXRpbmciOiJoaSJ9AOBX60gbAAAAAAAAAAAAAAAAAAAAAAAA&callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fhello-near", "Ventana Secundaria", "width=400,height=400"); - - - return new Promise((resolve, reject) => { - const checkWindowClosed = setInterval(() => { - if (childWindow?.closed) { - clearInterval(checkWindowClosed); - reject(new Error('La ventana se cerró antes de completar la transacción.')); - } - }, 1000); - window.addEventListener('message', async(event) => { - clearInterval(checkWindowClosed); - if (event.data?.status === 'success') { - console.log('Transacción exitosa'); - childWindow?.close(); - window.removeEventListener('message', () => { }); - - - const result = await provider.txStatus(event.data?.transactionHashes, 'unused',"NONE"); - - resolve(result); - } - }); - }) + return account["signAndSendTransaction"]({ + receiverId: receiverId || contract.contractId, + actions: actions.map((action) => createAction(action)), + walletCallbackUrl: callbackUrl, + }); }, async signAndSendTransactions({ transactions, callbackUrl }) { From c734cf3ec2722035db288bbd38b72f0cd0c24e62 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Mon, 27 Jan 2025 11:39:08 -0300 Subject: [PATCH 04/21] feat: wip added singMessage --- .../src/lib/my-near-wallet-connection.ts | 172 ++++++++++++++---- .../my-near-wallet/src/lib/my-near-wallet.ts | 3 +- 2 files changed, 134 insertions(+), 41 deletions(-) diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts index c22e6c873..911e5d43d 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts @@ -12,6 +12,11 @@ const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; const LOCAL_STORAGE_KEY_SUFFIX = '_wallet_auth_key'; const PENDING_ACCESS_KEY_PREFIX = 'pending_key'; // browser storage key for a pending access key (i.e. key has been generated but we are not sure it was added yet) + +const DEFAULT_POPUP_WIDTH = 480; +const DEFAULT_POPUP_HEIGHT = 640; +const POLL_INTERVAL = 500; + interface SignInOptions { contractId?: string; methodNames?: string[]; @@ -21,6 +26,13 @@ interface SignInOptions { keyType?: 'ed25519' | 'secp256k1' } +interface WalletMessage { + status: 'success' | 'failure' | 'pending'; + transactionHashes?: string; + error?: string; + [key: string]: unknown; + } + /** * Information to send NEAR wallet for signing transactions and redirecting the browser back to the calling application */ @@ -174,6 +186,76 @@ export class MyNearWalletConnection { return newUrl.toString(); } + async handlePopupTransaction( + url: string, + callback: (result: WalletMessage) => T + ): Promise { + + const screenWidth = window.innerWidth || screen.width; + const screenHeight = window.innerHeight || screen.height; + const left = (screenWidth - DEFAULT_POPUP_WIDTH) / 2; + const top = (screenHeight - DEFAULT_POPUP_HEIGHT) / 2; + const childWindow = window.open( + url, + "My Near Wallet", + `width=${DEFAULT_POPUP_WIDTH},height=${DEFAULT_POPUP_HEIGHT},top=${top},left=${left}` + ); + + if (!childWindow) { + throw new Error('Popup window blocked. Please allow popups for this site.'); + } + + return new Promise((resolve, reject) => { + const cleanup = () => { + window.removeEventListener('message', messageHandler); + clearInterval(intervalId); + }; + + const messageHandler = this.setupMessageHandler(resolve, reject, childWindow,callback); + const intervalId = setInterval(() => { + if (childWindow.closed) { + cleanup(); + reject(new Error('User closed the wallet window')); + } + }, POLL_INTERVAL); + }); + } + + private validateMessageOrigin(event: MessageEvent): boolean { + const expectedOrigin = new URL(this._walletBaseUrl).origin; + return event.origin === expectedOrigin; + } + + private setupMessageHandler( + resolve: (value: T) => void, + reject: (reason?: unknown) => void, + childWindow: Window | null, + callback: (result: WalletMessage) => T + ): (event: MessageEvent) => Promise { + const handler = async (event: MessageEvent) => { + // if (!this.validateMessageOrigin(event)) { + // reject(new Error('Invalid message origin')); + // return; + // } + + const message = event.data as WalletMessage; + switch (message.status) { + case 'success': + childWindow?.close(); + resolve(callback(message)); + break; + case 'failure': + childWindow?.close(); + reject(new Error(message.error || 'Transaction failed')); + break; + default: + console.warn('Unhandled message status:', message.status); + } + }; + + window.addEventListener('message', handler); + return handler; + } /** * Redirects current page to the wallet authentication page. * @param options An optional options object @@ -191,29 +273,35 @@ export class MyNearWalletConnection { async requestSignIn(options: SignInOptions) { const url = await this.requestSignInUrl(options); + return await this.handlePopupTransaction(url,async(data)=>{ + const { public_key: publicKey, all_keys: allKeys, account_id: accountId } = data as any; + await this.completeSignInWithAccessKeys({ accountId, publicKey, allKeys }); + return [{ accountId, publicKey }]; + }); + - // @ts-ignore - const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); + // // @ts-ignore + // const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); - return await new Promise((resolve, reject) => { - const checkWindowClosed = setInterval(() => { - if (childWindow?.closed) { - clearInterval(checkWindowClosed); - reject(new Error('La ventana se cerró antes de completar la transacción.')); - } - }, 500); - window.addEventListener('message', async(event) => { - if (event.data?.status === 'success') { + // return await new Promise((resolve, reject) => { + // const checkWindowClosed = setInterval(() => { + // if (childWindow?.closed) { + // clearInterval(checkWindowClosed); + // reject(new Error('La ventana se cerró antes de completar la transacción.')); + // } + // }, 500); + // window.addEventListener('message', async(event) => { + // if (event.data?.status === 'success') { - const { public_key:publicKey, all_keys:allKeys, account_id:accountId } = event.data; - - await this.completeSignInWithAccessKeys({accountId,publicKey,allKeys}); - childWindow?.close(); - window.removeEventListener('message', () => { }); - return resolve([{accountId,publicKey}]); - } - }); - }) + // const { public_key:publicKey, all_keys:allKeys, account_id:accountId } = event.data; + + // await this.completeSignInWithAccessKeys({accountId,publicKey,allKeys}); + // childWindow?.close(); + // window.removeEventListener('message', () => { }); + // return resolve([{accountId,publicKey}]); + // } + // }); + // }) } /** @@ -256,29 +344,33 @@ export class MyNearWalletConnection { requestSignTransaction(options: RequestSignTransactionsOptions): Promise { + const url = this.requestSignTransactionsUrl(options); - // @ts-ignore - const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); + + console.log("it is magic",url); + + return this.handlePopupTransaction(url, (data) => data?.transactionHashes) as Promise; + // const url = this.requestSignTransactionsUrl(options); + // // @ts-ignore + // const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); - return new Promise((resolve, reject) => { - const checkWindowClosed = setInterval(() => { - if (childWindow?.closed) { - clearInterval(checkWindowClosed); - reject(new Error('La ventana se cerró antes de completar la transacción.')); - } - }, 1000); - window.addEventListener('message', async(event) => { - clearInterval(checkWindowClosed); - if (event.data?.status === 'success') { - console.log('Transacción exitosa'); - childWindow?.close(); - window.removeEventListener('message', () => {}); - console.log("eventos",event.data?.transactionHashes); + // return new Promise((resolve, reject) => { + // const checkWindowClosed = setInterval(() => { + // if (childWindow?.closed) { + // clearInterval(checkWindowClosed); + // reject(new Error('La ventana se cerró antes de completar la transacción.')); + // } + // }, 1000); + // window.addEventListener('message', async(event) => { + // clearInterval(checkWindowClosed); + // if (event.data?.status === 'success') { + // childWindow?.close(); + // window.removeEventListener('message', () => {}); - resolve(event.data?.transactionHashes); - } - }); - }) + // resolve(event.data?.transactionHashes); + // } + // }); + // }) } diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.ts b/packages/my-near-wallet/src/lib/my-near-wallet.ts index 1a57b8ad9..7a87aeeb0 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet.ts @@ -190,7 +190,8 @@ const MyNearWallet: WalletBehaviourFactory< href.searchParams.append("state", state); } - window.location.replace(href.toString()); + await _state.wallet.handlePopupTransaction(href.toString(),()=>{}); + return; }, From 88d0226252bedca66c4c274652e857d03264802d Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Tue, 28 Jan 2025 12:47:22 -0300 Subject: [PATCH 05/21] feat: wip --- .../src/lib/my-near-wallet copy.ts | 450 ------------------ .../src/lib/my-near-wallet-connection.ts | 173 +++---- .../my-near-wallet/src/lib/my-near-wallet.ts | 14 +- 3 files changed, 67 insertions(+), 570 deletions(-) delete mode 100644 packages/my-near-wallet/src/lib/my-near-wallet copy.ts diff --git a/packages/my-near-wallet/src/lib/my-near-wallet copy.ts b/packages/my-near-wallet/src/lib/my-near-wallet copy.ts deleted file mode 100644 index 746e01a6a..000000000 --- a/packages/my-near-wallet/src/lib/my-near-wallet copy.ts +++ /dev/null @@ -1,450 +0,0 @@ -import * as nearAPI from "near-api-js"; -import type { - WalletModuleFactory, - WalletBehaviourFactory, - BrowserWallet, - Transaction, - Optional, - Network, - Account, -} from "@near-wallet-selector/core"; -import { createAction } from "@near-wallet-selector/wallet-utils"; -import icon from "./icon"; -import { PublicKey } from "near-api-js/lib/utils"; - -export interface MyNearWalletParams { - walletUrl?: string; - iconUrl?: string; - deprecated?: boolean; - successUrl?: string; - failureUrl?: string; -} - -interface MyNearWalletState { - wallet: nearAPI.WalletConnection; - keyStore: nearAPI.keyStores.BrowserLocalStorageKeyStore; -} - -interface MyNearWalletExtraOptions { - walletUrl: string; -} - -const resolveWalletUrl = (network: Network, walletUrl?: string) => { - if (walletUrl) { - return walletUrl; - } - - switch (network.networkId) { - case "mainnet": - return "https://app.mynearwallet.com"; - case "testnet": - return "https://testnet.mynearwallet.com"; - default: - throw new Error("Invalid wallet url"); - } -}; - -const setupWalletState = async ( - params: MyNearWalletExtraOptions, - network: Network -): Promise => { - const keyStore = new nearAPI.keyStores.BrowserLocalStorageKeyStore(); - - const near = await nearAPI.connect({ - keyStore, - walletUrl: params.walletUrl, - ...network, - headers: {}, - }); - - const wallet = new nearAPI.WalletConnection(near, "near_app"); - - return { - wallet, - keyStore, - }; -}; - -const MyNearWallet: WalletBehaviourFactory< - BrowserWallet, - { params: MyNearWalletExtraOptions } -> = async ({ metadata, options, store, params, logger, id, emitter, storage }) => { - const _state = await setupWalletState(params, options.network); - const getAccounts = async (): Promise> => { - const accountId = _state.wallet.getAccountId(); - const account = _state.wallet.account(); - if (!accountId || !account) { - return []; - } - - const publicKey = await account.connection.signer.getPublicKey( - account.accountId, - options.network.networkId - ); - return [ - { - accountId, - publicKey: publicKey ? publicKey.toString() : "", - }, - ]; - // const {accountId,publicKey} = JSON.parse(localStorage.getItem('my-near-wallet') || "{}"); - - // if(!accountId || !publicKey){ - // return []; - // } - - // return [{accountId,publicKey}]; - }; - - const transformTransactions = async ( - transactions: Array> - ) => { - const account = _state.wallet.account(); - const { networkId, signer, provider } = account.connection; - - const localKey = await signer.getPublicKey(account.accountId, networkId); - - return Promise.all( - transactions.map(async (transaction, index) => { - const actions = transaction.actions.map((action) => - createAction(action) - ); - const accessKey = await account.accessKeyForTransaction( - transaction.receiverId, - actions, - localKey - ); - - if (!accessKey) { - throw new Error( - `Failed to find matching key for transaction sent to ${transaction.receiverId}` - ); - } - - const block = await provider.block({ finality: "final" }); - - const nonce = accessKey.access_key.nonce + BigInt(index + 1); - - return nearAPI.transactions.createTransaction( - account.accountId, - nearAPI.utils.PublicKey.from(accessKey.public_key), - transaction.receiverId, - nonce, - actions, - nearAPI.utils.serialize.base_decode(block.header.hash) - ); - }) - ); - }; - - return { - async signIn({ contractId, methodNames, successUrl, failureUrl }) { - const existingAccounts = await getAccounts(); - - if (existingAccounts.length) { - return existingAccounts; - } - - await _state.wallet.requestSignIn({ - contractId, - methodNames, - successUrl, - failureUrl, - }); - - return getAccounts(); - - - const url = await _state.wallet.requestSignInUrl({ - contractId, - methodNames, - successUrl, - failureUrl, - }); - - // contractId && publishUrl.searchParams.set('contractId', contractId ); - // methodNames && publishUrl.searchParams.set('methodNames', methodNames.join(",") ); - - console.log("Url requestSignInUrl 23",url); - - // @ts-ignore - const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); - - return await new Promise((resolve, reject) => { - const checkWindowClosed = setInterval(() => { - if (childWindow?.closed) { - clearInterval(checkWindowClosed); - reject(new Error('La ventana se cerró antes de completar la transacción.')); - } - }, 500); - window.addEventListener('message', async(event) => { - if (event.data?.status === 'success') { - - console.log("check",event.data); - - const { public_key:publicKey, all_keys:allKeys, account_id:accountId } = event.data; - console.log({publicKey, allKeys, accountId}); - - // _state.keyStore.setKey(options.network.networkId, account_id,public_key); - // const keyPair = await this._keyStore.getKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); - // await this._keyStore.setKey(this._networkId, accountId, keyPair); - // await this._keyStore.removeKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); - - localStorage.setItem('near_app_wallet_auth_key', JSON.stringify({ accountId, allKeys })); - if (publicKey) { - _state.keyStore.setKey(options.network.networkId, accountId,publicKey); - } - // ?account_id=maguila.testnet&all_keys=ed25519%3AAtH7GEjv2qmBVoT8qoRhWXizXM5CC12DC6tiqY9iNoRm - childWindow?.close(); - window.removeEventListener('message', () => { }); - // emitter.emit("signedIn", { - // contractId: contractId, - // methodNames: methodNames ?? [], - // accounts: await getAccounts(), - // }); - - // return await getAccounts(); - // window.location.assign(parsedUrl.toString()); - _state.wallet.isSignedIn = () => true; - return resolve([{accountId,publicKey}]); - } - }); - }) - // return getAccounts(); - }, - - async signOut() { - if (_state.wallet.isSignedIn()) { - // _state.wallet.isSignedIn = () => false; - _state.wallet.signOut(); - } - }, - - async getAccounts() { - return getAccounts(); - }, - - async verifyOwner() { - throw new Error(`Method not supported by ${metadata.name}`); - }, - - async signMessage({ message, nonce, recipient, callbackUrl, state }) { - logger.log("sign message", { message }); - - if (id !== "my-near-wallet") { - throw Error( - `The signMessage method is not supported by ${metadata.name}` - ); - } - - const locationUrl = - typeof window !== "undefined" ? window.location.href : ""; - - const url = callbackUrl || locationUrl; - - if (!url) { - throw new Error(`The callbackUrl is missing for ${metadata.name}`); - } - - const href = new URL(params.walletUrl); - href.pathname = "sign-message"; - href.searchParams.append("message", message); - href.searchParams.append("nonce", nonce.toString("base64")); - href.searchParams.append("recipient", recipient); - href.searchParams.append("callbackUrl", url); - if (state) { - href.searchParams.append("state", state); - } - - window.location.replace(href.toString()); - - return; - }, - - async signAndSendTransaction({ - signerId, - receiverId, - actions, - callbackUrl, - }) { - logger.log("signAndSendTransaction", { - signerId, - receiverId, - actions, - callbackUrl, - }); - - const { contract } = store.getState(); - - if (!_state.wallet.isSignedIn() || !contract) { - throw new Error("Wallet not signed in"); - } - // const account = _state.wallet.account(); - // console.log( "pepe",account["signAndSendTransaction"]) - const account = _state.wallet.account(); - // const { networkId, signer, provider } = account.connection; - return account["signAndSendTransaction"]({ - receiverId: receiverId || contract.contractId, - actions: actions.map((action) => createAction(action)), - walletCallbackUrl: callbackUrl, - }); - // const account = _state.wallet.account(); - // const { networkId, signer, provider } = account.connection; - - // const block = await provider.block({ finality: "final" }); - - - // const transactions = await nearAPI.transactions.createTransaction( - // signerId || account.accountId, - // PublicKey.fromString("ed25519:AtH7GEjv2qmBVoT8qoRhWXizXM5CC12DC6tiqY9iNoRm"), - // receiverId || contract.contractId, - // 0, - // actions.map((action) => createAction(action)), - // new Uint8Array(32) - // ); - // // @ts-ignore - // // console.log({transactions,test:atob(transactions.encode())}); - // // console.log("orginal",atob("DwAAAG1hZ3VpbGEudGVzdG5ldACS3ARWu0sYjX63OYbDojizriWL55RnrStWodM6c%2BbIrMrc%2F3zXjAAAGwAAAGhlbGxvLm5lYXItZXhhbXBsZXMudGVzdG5ldF6mKSLVD4Nu%2ByY53uE0fD4CCaELfzNdK18eVlDURC4QAQAAAAIMAAAAc2V0X2dyZWV0aW5nEQAAAHsiZ3JlZXRpbmciOiJoaSJ9AOBX60gbAAAAAAAAAAAAAAAAAAAAAAAA")); - // console.log({signerId, - // receiverId, - // actions, - // callbackUrl}); - - - - // const publishUrl = new URL('sign', `https://localhost:1234`); - // publishUrl.searchParams.set('transactions', Buffer.from(transactions.encode()).toString("base64")); - // publishUrl.searchParams.set('callbackUrl', "http://localhost:3000/hello-near"); - // // @ts-ignore - // const childWindow = window.open(publishUrl.toString(),"My Near Wallet", "width=480,height=640"); - - // const childWindow = window.open("https://localhost:1234/sign?transactions=DwAAAG1hZ3VpbGEudGVzdG5ldACS3ARWu0sYjX63OYbDojizriWL55RnrStWodM6c%2BbIrMrc%2F3zXjAAAGwAAAGhlbGxvLm5lYXItZXhhbXBsZXMudGVzdG5ldF6mKSLVD4Nu%2ByY53uE0fD4CCaELfzNdK18eVlDURC4QAQAAAAIMAAAAc2V0X2dyZWV0aW5nEQAAAHsiZ3JlZXRpbmciOiJoaSJ9AOBX60gbAAAAAAAAAAAAAAAAAAAAAAAA&callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fhello-near", "Ventana Secundaria", "width=400,height=400"); - - - // return new Promise((resolve, reject) => { - // const checkWindowClosed = setInterval(() => { - // if (childWindow?.closed) { - // clearInterval(checkWindowClosed); - // reject(new Error('La ventana se cerró antes de completar la transacción.')); - // } - // }, 500); - // window.addEventListener('message', async(event) => { - // if (event.data?.status === 'success') { - // console.log('Transacción exitosa'); - // childWindow?.close(); - // window.removeEventListener('message', () => { }); - - - // const result = await provider.txStatus(event.data?.transactionHashes, 'unused',"NONE"); - - // resolve(result); - // } - // }); - // }) - }, - - async signAndSendTransactions({ transactions, callbackUrl }) { - logger.log("signAndSendTransactions", { transactions, callbackUrl }); - - if (!_state.wallet.isSignedIn()) { - throw new Error("Wallet not signed in"); - } - - return _state.wallet.requestSignTransactions({ - transactions: await transformTransactions(transactions), - callbackUrl, - }); - }, - - buildImportAccountsUrl() { - return `${params.walletUrl}/batch-import`; - }, - }; -}; - -export function setupMyNearWallet({ - walletUrl, - iconUrl = icon, - deprecated = false, - successUrl = "", - failureUrl = "", -}: MyNearWalletParams = {}): WalletModuleFactory { - return async (moduleOptions) => { - return { - id: "my-near-wallet", - type: "browser", - metadata: { - name: "MyNearWallet", - description: - "NEAR wallet to store, buy, send and stake assets for DeFi.", - iconUrl, - deprecated, - available: true, - successUrl, - failureUrl, - walletUrl: resolveWalletUrl(moduleOptions.options.network, walletUrl), - }, - init: (options) => { - return MyNearWallet({ - ...options, - params: { - walletUrl: resolveWalletUrl(options.options.network, walletUrl), - }, - }); - }, - }; - }; -} - -// signAndSendTransaction({receiverId, actions, walletMeta, walletCallbackUrl=window.location.href}) { -// const _super = Object.create(null, { -// signAndSendTransaction: { -// get: ()=>super.signAndSendTransaction -// } -// }); -// return __awaiter(this, void 0, void 0, function*() { -// const localKey = yield this.connection.signer.getPublicKey(this.accountId, this.connection.networkId); -// let accessKey = yield this.accessKeyForTransaction(receiverId, actions, localKey); -// if (!accessKey) { -// throw new Error(`Cannot find matching key for transaction sent to ${receiverId}`); -// } -// if (localKey && localKey.toString() === accessKey.public_key) { -// try { -// return yield _super.signAndSendTransaction.call(this, { -// receiverId, -// actions -// }); -// } catch (e) { -// if (e.type === 'NotEnoughAllowance') { -// accessKey = yield this.accessKeyForTransaction(receiverId, actions); -// } else { -// throw e; -// } -// } -// } -// const block = yield this.connection.provider.block({ -// finality: 'final' -// }); -// const blockHash = (0, -// utils_1.baseDecode)(block.header.hash); -// const publicKey = crypto_1.PublicKey.from(accessKey.public_key); -// // TODO: Cache & listen for nonce updates for given access key -// const nonce = accessKey.access_key.nonce + BigInt(1); -// const transaction = (0, -// transactions_1.createTransaction)(this.accountId, publicKey, receiverId, nonce, actions, blockHash); -// yield this.walletConnection.requestSignTransactions({ -// transactions: [transaction], -// meta: walletMeta, -// callbackUrl: walletCallbackUrl -// }); -// return new Promise((resolve,reject)=>{ -// setTimeout(()=>{ -// reject(new Error('Failed to redirect to sign transaction')); -// } -// , 1000); -// } -// ); -// // TODO: Aggregate multiple transaction request with "debounce". -// // TODO: Introduce TransactionQueue which also can be used to watch for status? -// }); -// } \ No newline at end of file diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts index 911e5d43d..819209b91 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts @@ -1,3 +1,4 @@ +import { SignedMessage } from "@near-wallet-selector/core"; import { serialize } from "borsh"; import { Account, Connection, InMemorySigner, KeyPair, Near } from "near-api-js"; import { SignAndSendTransactionOptions } from "near-api-js/lib/account"; @@ -31,7 +32,10 @@ interface WalletMessage { transactionHashes?: string; error?: string; [key: string]: unknown; - } + signedRequest?: SignedMessage; + errorMessage?: string; + errorCode?: string; +} /** * Information to send NEAR wallet for signing transactions and redirecting the browser back to the calling application @@ -86,13 +90,13 @@ export class MyNearWalletConnection { _completeSignInPromise: Promise; constructor(near: Near, appKeyPrefix: string) { - if (typeof(appKeyPrefix) !== 'string') { + if (typeof (appKeyPrefix) !== 'string') { throw new Error('Please define a clear appKeyPrefix for this WalletConnection instance as the second argument to the constructor'); } this._near = near; const authDataKey = appKeyPrefix + LOCAL_STORAGE_KEY_SUFFIX; - const authData = JSON.parse(window.localStorage.getItem(authDataKey||'') || '{}'); + const authData = JSON.parse(window.localStorage.getItem(authDataKey || '') || '{}'); this._networkId = near.config.networkId; this._walletBaseUrl = near.config.walletUrl; appKeyPrefix = appKeyPrefix || near.config.contractName || 'default'; @@ -161,7 +165,7 @@ export class MyNearWalletConnection { * const url = await wallet.requestSignInUrl({ contractId: 'account-with-deploy-contract.near' }); * ``` */ - async requestSignInUrl({contractId, methodNames, successUrl, failureUrl, keyType = 'ed25519'}: SignInOptions): Promise { + async requestSignInUrl({ contractId, methodNames, successUrl, failureUrl, keyType = 'ed25519' }: SignInOptions): Promise { const currentUrl = new URL(window.location.href); const newUrl = new URL(this._walletBaseUrl + LOGIN_WALLET_URL_SUFFIX); newUrl.searchParams.set('success_url', successUrl || currentUrl.href); @@ -189,73 +193,65 @@ export class MyNearWalletConnection { async handlePopupTransaction( url: string, callback: (result: WalletMessage) => T - ): Promise { + ): Promise { const screenWidth = window.innerWidth || screen.width; const screenHeight = window.innerHeight || screen.height; const left = (screenWidth - DEFAULT_POPUP_WIDTH) / 2; const top = (screenHeight - DEFAULT_POPUP_HEIGHT) / 2; const childWindow = window.open( - url, - "My Near Wallet", - `width=${DEFAULT_POPUP_WIDTH},height=${DEFAULT_POPUP_HEIGHT},top=${top},left=${left}` + url, + "My Near Wallet", + `width=${DEFAULT_POPUP_WIDTH},height=${DEFAULT_POPUP_HEIGHT},top=${top},left=${left}` ); - + if (!childWindow) { - throw new Error('Popup window blocked. Please allow popups for this site.'); + throw new Error('Popup window blocked. Please allow popups for this site.'); } - + return new Promise((resolve, reject) => { - const cleanup = () => { - window.removeEventListener('message', messageHandler); - clearInterval(intervalId); - }; - - const messageHandler = this.setupMessageHandler(resolve, reject, childWindow,callback); - const intervalId = setInterval(() => { - if (childWindow.closed) { - cleanup(); - reject(new Error('User closed the wallet window')); - } - }, POLL_INTERVAL); - }); - } + const cleanup = () => { + window.removeEventListener('message', messageHandler); + clearInterval(intervalId); + }; - private validateMessageOrigin(event: MessageEvent): boolean { - const expectedOrigin = new URL(this._walletBaseUrl).origin; - return event.origin === expectedOrigin; - } + const messageHandler = this.setupMessageHandler(resolve, reject, childWindow, callback); + const intervalId = setInterval(() => { + if (childWindow.closed) { + cleanup(); + reject(new Error('User closed the wallet window')); + } + }, POLL_INTERVAL); + }); + } - private setupMessageHandler( + private setupMessageHandler( resolve: (value: T) => void, reject: (reason?: unknown) => void, childWindow: Window | null, callback: (result: WalletMessage) => T - ): (event: MessageEvent) => Promise { + ): (event: MessageEvent) => Promise { const handler = async (event: MessageEvent) => { - // if (!this.validateMessageOrigin(event)) { - // reject(new Error('Invalid message origin')); - // return; - // } - - const message = event.data as WalletMessage; - switch (message.status) { - case 'success': - childWindow?.close(); - resolve(callback(message)); - break; - case 'failure': - childWindow?.close(); - reject(new Error(message.error || 'Transaction failed')); - break; - default: - console.warn('Unhandled message status:', message.status); - } + + const message = event.data as WalletMessage; + + switch (message.status) { + case 'success': + childWindow?.close(); + resolve(callback(message)); + break; + case 'failure': + childWindow?.close(); + reject(new Error(message.errorMessage || 'Transaction failed')); + break; + default: + console.warn('Unhandled message status:', message.status); + } }; - + window.addEventListener('message', handler); return handler; - } + } /** * Redirects current page to the wallet authentication page. * @param options An optional options object @@ -271,37 +267,13 @@ export class MyNearWalletConnection { * ``` */ async requestSignIn(options: SignInOptions) { - - const url = await this.requestSignInUrl(options); - return await this.handlePopupTransaction(url,async(data)=>{ - const { public_key: publicKey, all_keys: allKeys, account_id: accountId } = data as any; - await this.completeSignInWithAccessKeys({ accountId, publicKey, allKeys }); - return [{ accountId, publicKey }]; - }); - - - // // @ts-ignore - // const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); - - // return await new Promise((resolve, reject) => { - // const checkWindowClosed = setInterval(() => { - // if (childWindow?.closed) { - // clearInterval(checkWindowClosed); - // reject(new Error('La ventana se cerró antes de completar la transacción.')); - // } - // }, 500); - // window.addEventListener('message', async(event) => { - // if (event.data?.status === 'success') { - - // const { public_key:publicKey, all_keys:allKeys, account_id:accountId } = event.data; - - // await this.completeSignInWithAccessKeys({accountId,publicKey,allKeys}); - // childWindow?.close(); - // window.removeEventListener('message', () => { }); - // return resolve([{accountId,publicKey}]); - // } - // }); - // }) + + const url = await this.requestSignInUrl(options); + return await this.handlePopupTransaction(url, async (data) => { + const { public_key: publicKey, all_keys: allKeys, account_id: accountId } = data as any; + await this.completeSignInWithAccessKeys({ accountId, publicKey, allKeys }); + return [{ accountId, publicKey }]; + }); } /** @@ -344,34 +316,9 @@ export class MyNearWalletConnection { requestSignTransaction(options: RequestSignTransactionsOptions): Promise { - const url = this.requestSignTransactionsUrl(options); - - console.log("it is magic",url); - - return this.handlePopupTransaction(url, (data) => data?.transactionHashes) as Promise; - // const url = this.requestSignTransactionsUrl(options); - // // @ts-ignore - // const childWindow = window.open(url,"My Near Wallet", "width=480,height=640"); - - // return new Promise((resolve, reject) => { - // const checkWindowClosed = setInterval(() => { - // if (childWindow?.closed) { - // clearInterval(checkWindowClosed); - // reject(new Error('La ventana se cerró antes de completar la transacción.')); - // } - // }, 1000); - // window.addEventListener('message', async(event) => { - // clearInterval(checkWindowClosed); - // if (event.data?.status === 'success') { - // childWindow?.close(); - // window.removeEventListener('message', () => {}); - - // resolve(event.data?.transactionHashes); - // } - // }); - // }) + return this.handlePopupTransaction(url, (data) => data?.transactionHashes) as Promise; } /** @@ -404,7 +351,7 @@ export class MyNearWalletConnection { window.history.replaceState({}, document.title, currentUrl.toString()); } - async completeSignInWithAccessKeys({accountId,allKeys,publicKey}: {accountId: string, allKeys: string[], publicKey: string}) { + async completeSignInWithAccessKeys({ accountId, allKeys, publicKey }: { accountId: string, allKeys: string[], publicKey: string }) { const authData = { accountId, allKeys @@ -507,17 +454,15 @@ export class MyNearConnectedWalletAccount extends Account { meta: walletMeta, callbackUrl: walletCallbackUrl }); - - return new Promise(async(resolve, reject) => { - const result = await this.connection.provider.txStatus(transactionHashes, 'unused',"NONE"); + + return new Promise(async (resolve, reject) => { + const result = await this.connection.provider.txStatus(transactionHashes, 'unused', "NONE"); resolve(result); setTimeout(() => { reject(new Error('Failed to redirect to sign transaction')); }, 1000); }); - - // TODO: Aggregate multiple transaction request with "debounce". // TODO: Introduce TransactionQueue which also can be used to watch for status? } diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.ts b/packages/my-near-wallet/src/lib/my-near-wallet.ts index 7a87aeeb0..9190f8ba7 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet.ts @@ -190,12 +190,15 @@ const MyNearWallet: WalletBehaviourFactory< href.searchParams.append("state", state); } - await _state.wallet.handlePopupTransaction(href.toString(),()=>{}); - - - return; + return await _state.wallet.handlePopupTransaction(href.toString(),(value)=>{ + return { + accountId: value?.signedRequest?.accountId || "" , + publicKey: value?.signedRequest?.publicKey || "", + signature: value?.signedRequest?.signature || "", + } + }); }, - + async signAndSendTransaction({ signerId, receiverId, @@ -215,7 +218,6 @@ const MyNearWallet: WalletBehaviourFactory< throw new Error("Wallet not signed in"); } const account = _state.wallet.account(); - console.log({ receiverId, contractId: contract.contractId,actions, callbackUrl }); return account["signAndSendTransaction"]({ receiverId: receiverId || contract.contractId, From 5af278e2fee4f6674618324a9d30e0f5f8ce4e34 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Tue, 28 Jan 2025 17:21:59 +0100 Subject: [PATCH 06/21] chore: minor suggestions --- .../src/lib/my-near-wallet-connection.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts index 819209b91..859a4b50d 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts @@ -16,7 +16,7 @@ const PENDING_ACCESS_KEY_PREFIX = 'pending_key'; // browser storage key for a pe const DEFAULT_POPUP_WIDTH = 480; const DEFAULT_POPUP_HEIGHT = 640; -const POLL_INTERVAL = 500; +const POLL_INTERVAL = 300; interface SignInOptions { contractId?: string; @@ -210,16 +210,13 @@ export class MyNearWalletConnection { } return new Promise((resolve, reject) => { - const cleanup = () => { - window.removeEventListener('message', messageHandler); - clearInterval(intervalId); - }; - const messageHandler = this.setupMessageHandler(resolve, reject, childWindow, callback); + const intervalId = setInterval(() => { if (childWindow.closed) { - cleanup(); - reject(new Error('User closed the wallet window')); + window.removeEventListener('message', messageHandler); + clearInterval(intervalId); + reject(new Error('User closed the window')); } }, POLL_INTERVAL); }); @@ -234,7 +231,7 @@ export class MyNearWalletConnection { const handler = async (event: MessageEvent) => { const message = event.data as WalletMessage; - + switch (message.status) { case 'success': childWindow?.close(); @@ -383,6 +380,7 @@ export class MyNearWalletConnection { signOut() { this._authData = {}; window.localStorage.removeItem(this._authDataKey); + this._keyStore.clear(); } /** From 5f3649e84473d2311673e2caf4a4765e7b31bd8b Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Tue, 28 Jan 2025 17:22:47 -0300 Subject: [PATCH 07/21] feat: added security measures --- packages/my-near-wallet/src/lib/my-near-wallet-connection.ts | 5 +++++ packages/my-near-wallet/src/lib/my-near-wallet.ts | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts index 859a4b50d..6045898bc 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts @@ -232,6 +232,11 @@ export class MyNearWalletConnection { const message = event.data as WalletMessage; + console.log('message', message); + + if (event.origin != this._walletBaseUrl) { + return; + } switch (message.status) { case 'success': childWindow?.close(); diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.ts b/packages/my-near-wallet/src/lib/my-near-wallet.ts index 9190f8ba7..723f32c96 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet.ts @@ -179,7 +179,8 @@ const MyNearWallet: WalletBehaviourFactory< if (!url) { throw new Error(`The callbackUrl is missing for ${metadata.name}`); } - + console.log("signMessage", {message, nonce, recipient, url, state}); + const href = new URL(params.walletUrl); href.pathname = "sign-message"; href.searchParams.append("message", message); From 3c2c32a1cc540964a9c6b8754b979b6c9af55efb Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Tue, 28 Jan 2025 18:21:39 -0300 Subject: [PATCH 08/21] chore: remove console.log --- packages/my-near-wallet/src/lib/my-near-wallet-connection.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts index 6045898bc..e0bc70916 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts @@ -231,8 +231,6 @@ export class MyNearWalletConnection { const handler = async (event: MessageEvent) => { const message = event.data as WalletMessage; - - console.log('message', message); if (event.origin != this._walletBaseUrl) { return; From 26472b07c9a3fcedd92d4b6337eecc627aed63ff Mon Sep 17 00:00:00 2001 From: kilavvy <140459108+kilavvy@users.noreply.github.com> Date: Mon, 3 Feb 2025 23:02:32 +0100 Subject: [PATCH 09/21] Update README.md --- packages/ethereum-wallets/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ethereum-wallets/README.md b/packages/ethereum-wallets/README.md index c0623e612..545d81dc4 100644 --- a/packages/ethereum-wallets/README.md +++ b/packages/ethereum-wallets/README.md @@ -153,7 +153,7 @@ Project ID is required, please obtain it from [walletconnect.com](https://wallet - `skipSignInAccessKey` (`boolean?`): Allows connecting Ethereum wallets without adding a Limited Access Key which would require owning NEAR to execute the transaction (for rainbowbridge.app and welcome.near.org). - `nearNodeUrl` (`string?`): NEAR node url to query the NEAR transaction status and onboarding access key. -Developent options (before the NEAR protocol upgrade to support 0x accounts natively): +Development options (before the NEAR protocol upgrade to support 0x accounts natively): - `devMode` (`boolean?`): During development NEAR protocol doesn't yet support `0x123...abc` accounts natively so in devMode the account with format `0x123...abc.eth-wallet.testnet` is used insead. Setup your devMode account at https://near-wallet-playground.testnet.aurora.dev - `devModeAccount` (`string?`): Modify the namespace of the devMode root accounts. From e331cef9d434680ecbfabefdc87ba05b457be95b Mon Sep 17 00:00:00 2001 From: kilavvy <140459108+kilavvy@users.noreply.github.com> Date: Mon, 3 Feb 2025 23:04:00 +0100 Subject: [PATCH 10/21] Update DerivationPath.tsx --- packages/modal-ui/src/lib/components/DerivationPath.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modal-ui/src/lib/components/DerivationPath.tsx b/packages/modal-ui/src/lib/components/DerivationPath.tsx index ce944b25f..a25737519 100644 --- a/packages/modal-ui/src/lib/components/DerivationPath.tsx +++ b/packages/modal-ui/src/lib/components/DerivationPath.tsx @@ -65,8 +65,8 @@ export const DerivationPath: React.FC = ({ const [customAccountId, setCustomAccountId] = useState(""); const [connecting, setConnecting] = useState(false); - const initalHeaderTitle = translate("modal.ledger.connectWithLedger"); - const [headerTitle, setHeaderTitle] = useState(initalHeaderTitle); + const initialHeaderTitle = translate("modal.ledger.connectWithLedger"); + const [headerTitle, setHeaderTitle] = useState(initialHeaderTitle); const getAccountIds = async (publicKey: string): Promise> => { const url = `${selector.options.network.indexerUrl}/public_key/ed25519:${publicKey}`; From a963f8967bf003c31385ee23f8138c11bdfd31dc Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Thu, 6 Feb 2025 15:59:34 +0100 Subject: [PATCH 11/21] fix: compare urls --- .../my-near-wallet/src/lib/my-near-wallet-connection.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts index e0bc70916..6d3e0f1c0 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts @@ -231,10 +231,15 @@ export class MyNearWalletConnection { const handler = async (event: MessageEvent) => { const message = event.data as WalletMessage; - - if (event.origin != this._walletBaseUrl) { + + // check if the URL are the same + const origin = new URL(event.origin); + const walletBaseUrl = new URL(this._walletBaseUrl); + if (origin.origin !== walletBaseUrl.origin) { + console.warn('Ignoring message from different origin', origin.origin); return; } + switch (message.status) { case 'success': childWindow?.close(); From d1ea336485c9c50bd53167893a766f50b7db7571 Mon Sep 17 00:00:00 2001 From: Matias Benary Date: Thu, 6 Feb 2025 18:26:06 -0300 Subject: [PATCH 12/21] feat: added SingAndTransactions and improved typing --- .../src/lib/my-near-wallet-connection.ts | 965 +++++++++--------- .../my-near-wallet/src/lib/my-near-wallet.ts | 58 +- 2 files changed, 515 insertions(+), 508 deletions(-) diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts index 6d3e0f1c0..ab47cb933 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts @@ -1,538 +1,557 @@ -import { SignedMessage } from "@near-wallet-selector/core"; +import type { SignedMessage } from "@near-wallet-selector/core"; import { serialize } from "borsh"; -import { Account, Connection, InMemorySigner, KeyPair, Near } from "near-api-js"; -import { SignAndSendTransactionOptions } from "near-api-js/lib/account"; -import { KeyStore } from "near-api-js/lib/key_stores"; -import { FinalExecutionOutcome } from "near-api-js/lib/providers"; -import { Action, SCHEMA, Transaction, createTransaction } from "near-api-js/lib/transaction"; +import type { Connection, InMemorySigner, Near } from "near-api-js"; +import { Account, KeyPair } from "near-api-js"; +import type { SignAndSendTransactionOptions } from "near-api-js/lib/account"; +import type { KeyStore } from "near-api-js/lib/key_stores"; +import type { FinalExecutionOutcome } from "near-api-js/lib/providers"; +import type { AccessKeyInfoView } from "near-api-js/lib/providers/provider"; +import type { Action, Transaction } from "near-api-js/lib/transaction"; +import { SCHEMA, createTransaction } from "near-api-js/lib/transaction"; import { PublicKey } from "near-api-js/lib/utils"; import { base_decode } from "near-api-js/lib/utils/serialize"; -const LOGIN_WALLET_URL_SUFFIX = '/login/'; -const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; -const LOCAL_STORAGE_KEY_SUFFIX = '_wallet_auth_key'; -const PENDING_ACCESS_KEY_PREFIX = 'pending_key'; // browser storage key for a pending access key (i.e. key has been generated but we are not sure it was added yet) - +const LOGIN_WALLET_URL_SUFFIX = "/login/"; +const MULTISIG_HAS_METHOD = "add_request_and_confirm"; +const LOCAL_STORAGE_KEY_SUFFIX = "_wallet_auth_key"; +const PENDING_ACCESS_KEY_PREFIX = "pending_key"; // browser storage key for a pending access key (i.e. key has been generated but we are not sure it was added yet) const DEFAULT_POPUP_WIDTH = 480; const DEFAULT_POPUP_HEIGHT = 640; const POLL_INTERVAL = 300; interface SignInOptions { - contractId?: string; - methodNames?: string[]; - // TODO: Replace following with single callbackUrl - successUrl?: string; - failureUrl?: string; - keyType?: 'ed25519' | 'secp256k1' + contractId?: string; + methodNames?: Array; + successUrl?: string; + failureUrl?: string; + keyType?: "ed25519" | "secp256k1"; } interface WalletMessage { - status: 'success' | 'failure' | 'pending'; - transactionHashes?: string; - error?: string; - [key: string]: unknown; - signedRequest?: SignedMessage; - errorMessage?: string; - errorCode?: string; + status: "success" | "failure" | "pending"; + transactionHashes?: string; + error?: string; + [key: string]: unknown; + signedRequest?: SignedMessage; + errorMessage?: string; + errorCode?: string; } /** * Information to send NEAR wallet for signing transactions and redirecting the browser back to the calling application */ interface RequestSignTransactionsOptions { - /** list of transactions to sign */ - transactions: Transaction[]; - /** url NEAR Wallet will redirect to after transaction signing is complete */ - callbackUrl?: string; - /** meta information NEAR Wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param */ - meta?: string; + /** list of transactions to sign */ + transactions: Array; + /** url NEAR Wallet will redirect to after transaction signing is complete */ + callbackUrl?: string; + /** meta information NEAR Wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param */ + meta?: string; } -/** - * This class is not intended for use outside the browser. Without `window` (i.e. in server contexts), it will instantiate but will throw a clear error when used. - * - * @see [https://docs.near.org/tools/near-api-js/quick-reference#wallet](https://docs.near.org/tools/near-api-js/quick-reference#wallet) - * @example - * ```js - * // create new WalletConnection instance - * const wallet = new WalletConnection(near, 'my-app'); - * - * // If not signed in redirect to the NEAR wallet to sign in - * // keys will be stored in the BrowserLocalStorageKeyStore - * if(!wallet.isSignedIn()) return wallet.requestSignIn() - * ``` - */ -export class MyNearWalletConnection { - /** @hidden */ - _walletBaseUrl: string; - - /** @hidden */ - _authDataKey: string; - - /** @hidden */ - _keyStore: KeyStore; - - /** @hidden */ - _authData: { accountId?: string; allKeys?: string[] }; - - /** @hidden */ - _networkId: string; - - /** @hidden */ - // _near: Near; - _near: Near; - - /** @hidden */ - _connectedAccount: MyNearConnectedWalletAccount; - - /** @hidden */ - _completeSignInPromise: Promise; - - constructor(near: Near, appKeyPrefix: string) { - if (typeof (appKeyPrefix) !== 'string') { - throw new Error('Please define a clear appKeyPrefix for this WalletConnection instance as the second argument to the constructor'); - } - - this._near = near; - const authDataKey = appKeyPrefix + LOCAL_STORAGE_KEY_SUFFIX; - const authData = JSON.parse(window.localStorage.getItem(authDataKey || '') || '{}'); - this._networkId = near.config.networkId; - this._walletBaseUrl = near.config.walletUrl; - appKeyPrefix = appKeyPrefix || near.config.contractName || 'default'; - this._keyStore = (near.connection.signer as InMemorySigner).keyStore; - this._authData = authData || { allKeys: [] }; - this._authDataKey = authDataKey; - if (!this.isSignedIn()) { - this._completeSignInPromise = this._completeSignInWithAccessKey(); - } - } - - /** - * Returns true, if this WalletConnection is authorized with the wallet. - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * wallet.isSignedIn(); - * ``` - */ - isSignedIn() { - return !!this._authData.accountId; - } - - /** - * Returns promise of completing signing in after redirecting from wallet - * @example - * ```js - * // on login callback page - * const wallet = new WalletConnection(near, 'my-app'); - * wallet.isSignedIn(); // false - * await wallet.isSignedInAsync(); // true - * ``` - */ - async isSignedInAsync() { - if (!this._completeSignInPromise) { - return this.isSignedIn(); - } +interface AuthData { + accountId?: string; + allKeys?: Array; +} - await this._completeSignInPromise; - return this.isSignedIn(); - } +interface WalletResponseData extends WalletMessage { + public_key?: string; + all_keys?: Array; + account_id?: string; +} - /** - * Returns authorized Account ID. - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * wallet.getAccountId(); - * ``` - */ - getAccountId() { - return this._authData.accountId || ''; +export class MyNearWalletConnection { + _walletBaseUrl: string; + _authDataKey: string; + _keyStore: KeyStore; + _authData: AuthData; + _networkId: string; + _near: Near; + _connectedAccount?: MyNearConnectedWalletAccount | null; + _completeSignInPromise?: Promise; + + constructor(near: Near, appKeyPrefix: string) { + if (typeof appKeyPrefix !== "string") { + throw new Error( + "Please define a clear appKeyPrefix for this WalletConnection instance" + ); } - /** - * Constructs string URL to the wallet authentication page. - * @param options An optional options object - * @param options.contractId The NEAR account where the contract is deployed - * @param options.successUrl URL to redirect upon success. Default: current url - * @param options.failureUrl URL to redirect upon failure. Default: current url - * - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * // return string URL to the NEAR Wallet - * const url = await wallet.requestSignInUrl({ contractId: 'account-with-deploy-contract.near' }); - * ``` - */ - async requestSignInUrl({ contractId, methodNames, successUrl, failureUrl, keyType = 'ed25519' }: SignInOptions): Promise { - const currentUrl = new URL(window.location.href); - const newUrl = new URL(this._walletBaseUrl + LOGIN_WALLET_URL_SUFFIX); - newUrl.searchParams.set('success_url', successUrl || currentUrl.href); - newUrl.searchParams.set('failure_url', failureUrl || currentUrl.href); - if (contractId) { - /* Throws exception if contract account does not exist */ - const contractAccount = await this._near.account(contractId); - await contractAccount.state(); - - newUrl.searchParams.set('contract_id', contractId); - const accessKey = KeyPair.fromRandom(keyType); - newUrl.searchParams.set('public_key', accessKey.getPublicKey().toString()); - await this._keyStore.setKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey(), accessKey); - } + this._near = near; + const authDataKey = appKeyPrefix + LOCAL_STORAGE_KEY_SUFFIX; + const authData = JSON.parse( + window.localStorage.getItem(authDataKey) || "{}" + ) as AuthData; - if (methodNames) { - methodNames.forEach(methodName => { - newUrl.searchParams.append('methodNames', methodName); - }); - } + this._networkId = near.config.networkId; + this._walletBaseUrl = near.config.walletUrl; + this._keyStore = (near.connection.signer as InMemorySigner).keyStore; + this._authData = authData; + this._authDataKey = authDataKey; - return newUrl.toString(); + if (!this.isSignedIn()) { + this._completeSignInPromise = this._completeSignInWithAccessKey(); } + } - async handlePopupTransaction( - url: string, - callback: (result: WalletMessage) => T - ): Promise { - - const screenWidth = window.innerWidth || screen.width; - const screenHeight = window.innerHeight || screen.height; - const left = (screenWidth - DEFAULT_POPUP_WIDTH) / 2; - const top = (screenHeight - DEFAULT_POPUP_HEIGHT) / 2; - const childWindow = window.open( - url, - "My Near Wallet", - `width=${DEFAULT_POPUP_WIDTH},height=${DEFAULT_POPUP_HEIGHT},top=${top},left=${left}` - ); - - if (!childWindow) { - throw new Error('Popup window blocked. Please allow popups for this site.'); - } - - return new Promise((resolve, reject) => { - const messageHandler = this.setupMessageHandler(resolve, reject, childWindow, callback); - - const intervalId = setInterval(() => { - if (childWindow.closed) { - window.removeEventListener('message', messageHandler); - clearInterval(intervalId); - reject(new Error('User closed the window')); - } - }, POLL_INTERVAL); - }); - } + isSignedIn(): boolean { + return !!this._authData.accountId; + } - private setupMessageHandler( - resolve: (value: T) => void, - reject: (reason?: unknown) => void, - childWindow: Window | null, - callback: (result: WalletMessage) => T - ): (event: MessageEvent) => Promise { - const handler = async (event: MessageEvent) => { - - const message = event.data as WalletMessage; - - // check if the URL are the same - const origin = new URL(event.origin); - const walletBaseUrl = new URL(this._walletBaseUrl); - if (origin.origin !== walletBaseUrl.origin) { - console.warn('Ignoring message from different origin', origin.origin); - return; - } - - switch (message.status) { - case 'success': - childWindow?.close(); - resolve(callback(message)); - break; - case 'failure': - childWindow?.close(); - reject(new Error(message.errorMessage || 'Transaction failed')); - break; - default: - console.warn('Unhandled message status:', message.status); - } - }; - - window.addEventListener('message', handler); - return handler; - } - /** - * Redirects current page to the wallet authentication page. - * @param options An optional options object - * @param options.contractId The NEAR account where the contract is deployed - * @param options.successUrl URL to redirect upon success. Default: current url - * @param options.failureUrl URL to redirect upon failure. Default: current url - * - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * // redirects to the NEAR Wallet - * wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' }); - * ``` - */ - async requestSignIn(options: SignInOptions) { - - const url = await this.requestSignInUrl(options); - return await this.handlePopupTransaction(url, async (data) => { - const { public_key: publicKey, all_keys: allKeys, account_id: accountId } = data as any; - await this.completeSignInWithAccessKeys({ accountId, publicKey, allKeys }); - return [{ accountId, publicKey }]; - }); + async isSignedInAsync(): Promise { + if (!this._completeSignInPromise) { + return this.isSignedIn(); } - /** - * Constructs string URL to the wallet to sign a transaction or batch of transactions. - * - * @param options A required options object - * @param options.transactions List of transactions to sign - * @param options.callbackUrl URL to redirect upon success. Default: current url - * @param options.meta Meta information the wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param - * - */ - requestSignTransactionsUrl({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): string { - const currentUrl = new URL(window.location.href); - const newUrl = new URL('sign', this._walletBaseUrl); - - newUrl.searchParams.set('transactions', transactions - .map(transaction => serialize(SCHEMA.Transaction, transaction)) - .map(serialized => Buffer.from(serialized).toString('base64')) - .join(',')); - newUrl.searchParams.set('callbackUrl', callbackUrl || currentUrl.href); - if (meta) newUrl.searchParams.set('meta', meta); - - return newUrl.toString(); + await this._completeSignInPromise; + return this.isSignedIn(); + } + + getAccountId(): string { + return this._authData.accountId || ""; + } + + async requestSignInUrl({ + contractId, + methodNames, + successUrl, + failureUrl, + keyType = "ed25519", + }: SignInOptions): Promise { + const currentUrl = new URL(window.location.href); + const newUrl = new URL(this._walletBaseUrl + LOGIN_WALLET_URL_SUFFIX); + newUrl.searchParams.set("success_url", successUrl || currentUrl.href); + newUrl.searchParams.set("failure_url", failureUrl || currentUrl.href); + if (contractId) { + /* Throws exception if contract account does not exist */ + const contractAccount = await this._near.account(contractId); + await contractAccount.state(); + + newUrl.searchParams.set("contract_id", contractId); + const accessKey = KeyPair.fromRandom(keyType); + newUrl.searchParams.set( + "public_key", + accessKey.getPublicKey().toString() + ); + await this._keyStore.setKey( + this._networkId, + PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey(), + accessKey + ); } - /** - * Requests the user to quickly sign for a transaction or batch of transactions by redirecting to the wallet. - * - * @param options A required options object - * @param options.transactions List of transactions to sign - * @param options.callbackUrl URL to redirect upon success. Default: current url - * @param options.meta Meta information the wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param - * - */ - requestSignTransactions(options: RequestSignTransactionsOptions): void { - const url = this.requestSignTransactionsUrl(options); - - window.location.assign(url); + if (methodNames) { + methodNames.forEach((methodName) => { + newUrl.searchParams.append("methodNames", methodName); + }); } - - requestSignTransaction(options: RequestSignTransactionsOptions): Promise { - const url = this.requestSignTransactionsUrl(options); - - return this.handlePopupTransaction(url, (data) => data?.transactionHashes) as Promise; + return newUrl.toString(); + } + + async handlePopupTransaction( + url: string, + callback: (result: WalletMessage) => T + ): Promise { + const screenWidth = window.innerWidth || screen.width; + const screenHeight = window.innerHeight || screen.height; + const left = (screenWidth - DEFAULT_POPUP_WIDTH) / 2; + const top = (screenHeight - DEFAULT_POPUP_HEIGHT) / 2; + const childWindow = window.open( + url, + "My Near Wallet", + `width=${DEFAULT_POPUP_WIDTH},height=${DEFAULT_POPUP_HEIGHT},top=${top},left=${left}` + ); + + if (!childWindow) { + throw new Error( + "Popup window blocked. Please allow popups for this site." + ); } - /** - * @hidden - * Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet. - */ - async _completeSignInWithAccessKey() { - const currentUrl = new URL(window.location.href); - const publicKey = currentUrl.searchParams.get('public_key') || ''; - const allKeys = (currentUrl.searchParams.get('all_keys') || '').split(','); - const accountId = currentUrl.searchParams.get('account_id') || ''; - // TODO: Handle errors during login - if (accountId) { - const authData = { - accountId, - allKeys - }; - window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); - if (publicKey) { - await this._moveKeyFromTempToPermanent(accountId, publicKey); - } - this._authData = authData; + return new Promise((resolve, reject) => { + const messageHandler = this.setupMessageHandler( + resolve, + reject, + childWindow, + callback + ); + + const intervalId = setInterval(() => { + if (childWindow.closed) { + window.removeEventListener("message", messageHandler); + clearInterval(intervalId); + reject(new Error("User closed the window")); } - currentUrl.searchParams.delete('public_key'); - currentUrl.searchParams.delete('all_keys'); - currentUrl.searchParams.delete('account_id'); - currentUrl.searchParams.delete('meta'); - currentUrl.searchParams.delete('transactionHashes'); - - window.history.replaceState({}, document.title, currentUrl.toString()); + }, POLL_INTERVAL); + }); + } + + private setupMessageHandler( + resolve: (value: T) => void, + reject: (reason?: unknown) => void, + childWindow: Window | null, + callback: (result: WalletMessage) => T + ): (event: MessageEvent) => Promise { + const handler = async (event: MessageEvent) => { + const message = event.data as WalletMessage; + + // check if the URL are the same + const origin = new URL(event.origin); + const walletBaseUrl = new URL(this._walletBaseUrl); + if (origin.origin !== walletBaseUrl.origin) { + // eslint-disable-next-line no-console + console.warn("Ignoring message from different origin", origin.origin); + return; + } + + switch (message.status) { + case "success": + childWindow?.close(); + resolve(callback(message)); + break; + case "failure": + childWindow?.close(); + reject(new Error(message.errorMessage || "Transaction failed")); + break; + default: + // eslint-disable-next-line no-console + console.warn("Unhandled message status:", message.status); + } + }; + + window.addEventListener("message", handler); + return handler; + } + + async requestSignIn( + options: SignInOptions + ): Promise> { + const url = await this.requestSignInUrl(options); + return await this.handlePopupTransaction(url, async (data) => { + const responseData = data as WalletResponseData; + const { + public_key: publicKey, + all_keys: allKeys, + account_id: accountId, + } = responseData; + + if (accountId && publicKey && allKeys) { + await this.completeSignInWithAccessKeys({ + accountId, + publicKey, + allKeys, + }); + return [{ accountId, publicKey }]; + } + throw new Error("Invalid response data from wallet"); + }); + } + + requestSignTransactionsUrl(options: RequestSignTransactionsOptions): string { + const { transactions, meta, callbackUrl } = options; + const currentUrl = new URL(window.location.href); + const newUrl = new URL("sign", this._walletBaseUrl); + + newUrl.searchParams.set( + "transactions", + transactions + .map((transaction) => serialize(SCHEMA.Transaction, transaction)) + .map((serialized) => Buffer.from(serialized).toString("base64")) + .join(",") + ); + newUrl.searchParams.set("callbackUrl", callbackUrl || currentUrl.href); + + if (meta) { + newUrl.searchParams.set("meta", meta); } - async completeSignInWithAccessKeys({ accountId, allKeys, publicKey }: { accountId: string, allKeys: string[], publicKey: string }) { - const authData = { - accountId, - allKeys - }; - window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); - if (publicKey) { - await this._moveKeyFromTempToPermanent(accountId, publicKey); - } - this._authData = authData; - this.updateAccount() - } + return newUrl.toString(); + } - /** - * @hidden - * @param accountId The NEAR account owning the given public key - * @param publicKey The public key being set to the key store - */ - async _moveKeyFromTempToPermanent(accountId: string, publicKey: string) { - const keyPair = await this._keyStore.getKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); - await this._keyStore.setKey(this._networkId, accountId, keyPair); - await this._keyStore.removeKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); - } + async requestSignTransactions( + options: RequestSignTransactionsOptions + ): Promise> { + const url = this.requestSignTransactionsUrl(options); + const transactionsHashes = ( + await this.handlePopupTransaction(url, (data) => data.transactionHashes) + )?.split(","); - /** - * Sign out from the current account - * @example - * walletConnection.signOut(); - */ - signOut() { - this._authData = {}; - window.localStorage.removeItem(this._authDataKey); - this._keyStore.clear(); + if (!transactionsHashes) { + throw new Error("No transaction hashes received"); } - /** - * Returns the current connected wallet account - */ - account() { - if (!this._connectedAccount) { - this._connectedAccount = new MyNearConnectedWalletAccount(this, this._near.connection, this._authData.accountId || ""); - } - return this._connectedAccount; + return Promise.all( + transactionsHashes.map((hash) => + this._near.connection.provider.txStatus(hash, "unused", "NONE") + ) + ); + } + + requestSignTransaction( + options: RequestSignTransactionsOptions + ): Promise { + const url = this.requestSignTransactionsUrl(options); + + return this.handlePopupTransaction( + url, + (data) => data?.transactionHashes + ) as Promise; + } + + async _completeSignInWithAccessKey() { + const currentUrl = new URL(window.location.href); + const publicKey = currentUrl.searchParams.get("public_key") || ""; + const allKeys = (currentUrl.searchParams.get("all_keys") || "").split(","); + const accountId = currentUrl.searchParams.get("account_id") || ""; + // TODO: Handle errors during login + if (accountId) { + const authData = { + accountId, + allKeys, + }; + window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); + if (publicKey) { + await this._moveKeyFromTempToPermanent(accountId, publicKey); + } + this._authData = authData; } - - updateAccount() { - this._connectedAccount = new MyNearConnectedWalletAccount(this, this._near.connection, this._authData.accountId || ""); + currentUrl.searchParams.delete("public_key"); + currentUrl.searchParams.delete("all_keys"); + currentUrl.searchParams.delete("account_id"); + currentUrl.searchParams.delete("meta"); + currentUrl.searchParams.delete("transactionHashes"); + + window.history.replaceState({}, document.title, currentUrl.toString()); + } + + async completeSignInWithAccessKeys({ + accountId, + allKeys, + publicKey, + }: { + accountId: string; + allKeys: Array; + publicKey: string; + }) { + const authData = { + accountId, + allKeys, + }; + window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); + if (publicKey) { + await this._moveKeyFromTempToPermanent(accountId, publicKey); } + this._authData = authData; + this.updateAccount(); + } + + async _moveKeyFromTempToPermanent(accountId: string, publicKey: string) { + const keyPair = await this._keyStore.getKey( + this._networkId, + PENDING_ACCESS_KEY_PREFIX + publicKey + ); + await this._keyStore.setKey(this._networkId, accountId, keyPair); + await this._keyStore.removeKey( + this._networkId, + PENDING_ACCESS_KEY_PREFIX + publicKey + ); + } + + signOut() { + this._authData = {}; + window.localStorage.removeItem(this._authDataKey); + this._keyStore.clear(); + } + + /* eslint-disable @typescript-eslint/no-use-before-define */ + account() { + if (!this._connectedAccount) { + this._connectedAccount = new MyNearConnectedWalletAccount( + this, + this._near.connection, + this._authData.accountId || "" + ); + } + return this._connectedAccount; + } + + updateAccount() { + this._connectedAccount = new MyNearConnectedWalletAccount( + this, + this._near.connection, + this._authData.accountId || "" + ); + } + /* eslint-enable @typescript-eslint/no-use-before-define */ } -/** - * {@link Account} implementation which redirects to wallet using {@link WalletConnection} when no local key is available. - */ - export class MyNearConnectedWalletAccount extends Account { - walletConnection: MyNearWalletConnection; - - constructor(walletConnection: MyNearWalletConnection, connection: Connection, accountId: string) { - super(connection, accountId); - this.walletConnection = walletConnection; + walletConnection: MyNearWalletConnection; + + constructor( + walletConnection: MyNearWalletConnection, + connection: Connection, + accountId: string + ) { + super(connection, accountId); + this.walletConnection = walletConnection; + } + + async signAndSendTransaction({ + receiverId, + actions, + walletMeta, + walletCallbackUrl = window.location.href, + }: SignAndSendTransactionOptions): Promise { + const localKey = await this.connection.signer.getPublicKey( + this.accountId, + this.connection.networkId + ); + let accessKey = await this.accessKeyForTransaction( + receiverId, + actions, + localKey + ); + if (!accessKey) { + throw new Error( + `Cannot find matching key for transaction sent to ${receiverId}` + ); } - // Overriding Account methods - - /** - * Sign a transaction by redirecting to the NEAR Wallet - * @see {@link WalletConnection#requestSignTransactions} - * @param options An optional options object - * @param options.receiverId The NEAR account ID of the transaction receiver. - * @param options.actions An array of transaction actions to be performed. - * @param options.walletMeta Additional metadata to be included in the wallet signing request. - * @param options.walletCallbackUrl URL to redirect upon completion of the wallet signing process. Default: current URL. - */ - async signAndSendTransaction({ receiverId, actions, walletMeta, walletCallbackUrl = window.location.href }: SignAndSendTransactionOptions): Promise { - const localKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId); - let accessKey = await this.accessKeyForTransaction(receiverId, actions, localKey); - if (!accessKey) { - throw new Error(`Cannot find matching key for transaction sent to ${receiverId}`); + if (localKey && localKey.toString() === accessKey.public_key) { + try { + return await super.signAndSendTransaction({ receiverId, actions }); + } catch (e: unknown) { + /* eslint-disable @typescript-eslint/no-use-before-define */ + if ( + typeof e === "object" && + e !== null && + "type" in e && + (e as any).type === "NotEnoughAllowance" // eslint-disable-line @typescript-eslint/no-explicit-any + ) { + accessKey = await this.accessKeyForTransaction(receiverId, actions); + } else { + throw e; } + /* eslint-enable @typescript-eslint/no-use-before-define */ + } + } - if (localKey && localKey.toString() === accessKey.public_key) { - try { - return await super.signAndSendTransaction({ receiverId, actions }); - } catch (e: unknown) { - if (typeof e === 'object' && e !== null && 'type' in e && (e as any).type === 'NotEnoughAllowance') { - accessKey = await this.accessKeyForTransaction(receiverId, actions); - } else { - throw e; - } - } - } - - const block = await this.connection.provider.block({ finality: 'final' }); - const blockHash = base_decode(block.header.hash); - - const publicKey = PublicKey.from(accessKey.public_key); - // TODO: Cache & listen for nonce updates for given access key - const nonce = accessKey.access_key.nonce + BigInt("1"); - const transaction = createTransaction(this.accountId, publicKey, receiverId, nonce, actions, blockHash); - const transactionHashes = await this.walletConnection.requestSignTransaction({ - transactions: [transaction], - meta: walletMeta, - callbackUrl: walletCallbackUrl - }); - - return new Promise(async (resolve, reject) => { - const result = await this.connection.provider.txStatus(transactionHashes, 'unused', "NONE"); - resolve(result); - setTimeout(() => { - reject(new Error('Failed to redirect to sign transaction')); - }, 1000); - }); + const block = await this.connection.provider.block({ finality: "final" }); + const blockHash = base_decode(block.header.hash); - // TODO: Aggregate multiple transaction request with "debounce". - // TODO: Introduce TransactionQueue which also can be used to watch for status? + if (!accessKey) { + throw new Error("No matching key found for transaction"); + } + const publicKey = PublicKey.from(accessKey.public_key); + // TODO: Cache & listen for nonce updates for given access key + const nonce = accessKey.access_key.nonce + BigInt("1"); + const transaction = createTransaction( + this.accountId, + publicKey, + receiverId, + nonce, + actions, + blockHash + ); + const transactionHashes = + await this.walletConnection.requestSignTransaction({ + transactions: [transaction], + meta: walletMeta, + callbackUrl: walletCallbackUrl, + }); + + return new Promise((resolve, reject) => { + this.connection.provider + .txStatus(transactionHashes, "unused", "NONE") + .then(resolve) + .catch(reject); + + setTimeout(() => { + reject(new Error("Failed to redirect to sign transaction")); + }, 1000); + }); + + // TODO: Aggregate multiple transaction request with "debounce". + // TODO: Introduce TransactionQueue which also can be used to watch for status? + } + + async accessKeyMatchesTransaction( + accessKey: AccessKeyInfoView, + receiverId: string, + actions: Array + ): Promise { + const { + access_key: { permission }, + } = accessKey; + if (permission === "FullAccess") { + return true; } - /** - * Check if given access key allows the function call or method attempted in transaction - * @param accessKey Array of \{access_key: AccessKey, public_key: PublicKey\} items - * @param receiverId The NEAR account attempting to have access - * @param actions The action(s) needed to be checked for access - */ - async accessKeyMatchesTransaction(accessKey: any, receiverId: string, actions: Action[]): Promise { - const { access_key: { permission } } = accessKey; - if (permission === 'FullAccess') { - return true; - } - - if (permission.FunctionCall) { - const { receiver_id: allowedReceiverId, method_names: allowedMethods } = permission.FunctionCall; - /******************************** + if (permission.FunctionCall) { + const { receiver_id: allowedReceiverId, method_names: allowedMethods } = + permission.FunctionCall; + /******************************** Accept multisig access keys and let wallets attempt to signAndSendTransaction If an access key has itself as receiverId and method permission add_request_and_confirm, then it is being used in a wallet with multisig contract: https://github.com/near/core-contracts/blob/671c05f09abecabe7a7e58efe942550a35fc3292/multisig/src/lib.rs#L149-L153 ********************************/ - if (allowedReceiverId === this.accountId && allowedMethods.includes(MULTISIG_HAS_METHOD)) { - return true; - } - if (allowedReceiverId === receiverId) { - if (actions.length !== 1) { - return false; - } - const [{ functionCall }] = actions; - return functionCall && - (!functionCall.deposit || functionCall.deposit.toString() === '0') && // TODO: Should support charging amount smaller than allowance? - (allowedMethods.length === 0 || allowedMethods.includes(functionCall.methodName)); - // TODO: Handle cases when allowance doesn't have enough to pay for gas - } + if ( + allowedReceiverId === this.accountId && + allowedMethods.includes(MULTISIG_HAS_METHOD) + ) { + return true; + } + if (allowedReceiverId === receiverId) { + if (actions.length !== 1) { + return false; } - // TODO: Support other permissions than FunctionCall - - return false; + const [{ functionCall }] = actions; + return !!( + functionCall && + (!functionCall.deposit || functionCall.deposit.toString() === "0") && // TODO: Should support charging amount smaller than allowance? + (allowedMethods.length === 0 || + allowedMethods.includes(functionCall.methodName)) + ); + // TODO: Handle cases when allowance doesn't have enough to pay for gas + } + } + // TODO: Support other permissions than FunctionCall + + return false; + } + + async accessKeyForTransaction( + receiverId: string, + actions: Array, + localKey?: PublicKey + ): Promise { + const accessKeys = await this.getAccessKeys(); + + if (localKey) { + const accessKey = accessKeys.find( + (key) => key.public_key.toString() === localKey.toString() + ); + if ( + accessKey && + (await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) + ) { + return accessKey; + } } - /** - * Helper function returning the access key (if it exists) to the receiver that grants the designated permission - * @param receiverId The NEAR account seeking the access key for a transaction - * @param actions The action(s) sought to gain access to - * @param localKey A local public key provided to check for access - */ - async accessKeyForTransaction(receiverId: string, actions: Action[], localKey?: PublicKey): Promise { - const accessKeys = await this.getAccessKeys(); - - if (localKey) { - const accessKey = accessKeys.find(key => key.public_key.toString() === localKey.toString()); - if (accessKey && await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) { - return accessKey; - } - } - - const walletKeys = this.walletConnection._authData.allKeys; - for (const accessKey of accessKeys) { - if (walletKeys && walletKeys.indexOf(accessKey.public_key) !== -1 && await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) { - return accessKey; - } - } - - return null; + const walletKeys = this.walletConnection._authData.allKeys; + for (const accessKey of accessKeys) { + if ( + walletKeys && + walletKeys.indexOf(accessKey.public_key) !== -1 && + (await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) + ) { + return accessKey; + } } + + return null; + } } diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.ts b/packages/my-near-wallet/src/lib/my-near-wallet.ts index 723f32c96..0956222f3 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet.ts @@ -2,11 +2,11 @@ import * as nearAPI from "near-api-js"; import type { WalletModuleFactory, WalletBehaviourFactory, - BrowserWallet, Transaction, Optional, Network, Account, + InjectedWallet, } from "@near-wallet-selector/core"; import { createAction } from "@near-wallet-selector/wallet-utils"; import icon from "./icon"; @@ -57,7 +57,7 @@ const setupWalletState = async ( headers: {}, }); - let wallet = new MyNearWalletConnection(near, "near_app"); + const wallet = new MyNearWalletConnection(near, "near_app"); return { wallet, @@ -66,9 +66,9 @@ const setupWalletState = async ( }; const MyNearWallet: WalletBehaviourFactory< - BrowserWallet, + InjectedWallet, { params: MyNearWalletExtraOptions } -> = async ({ metadata, options, store, params, logger, id, emitter, storage }) => { +> = async ({ metadata, options, store, params, logger, id }) => { const _state = await setupWalletState(params, options.network); const getAccounts = async (): Promise> => { const accountId = _state.wallet.getAccountId(); @@ -131,7 +131,7 @@ const MyNearWallet: WalletBehaviourFactory< }; return { - async signIn({ contractId, methodNames, successUrl, failureUrl }) { + async signIn({ contractId, methodNames }) { const existingAccounts = await getAccounts(); if (existingAccounts.length) { @@ -141,8 +141,6 @@ const MyNearWallet: WalletBehaviourFactory< await _state.wallet.requestSignIn({ contractId, methodNames, - successUrl, - failureUrl, }); return getAccounts(); @@ -179,8 +177,7 @@ const MyNearWallet: WalletBehaviourFactory< if (!url) { throw new Error(`The callbackUrl is missing for ${metadata.name}`); } - console.log("signMessage", {message, nonce, recipient, url, state}); - + const href = new URL(params.walletUrl); href.pathname = "sign-message"; href.searchParams.append("message", message); @@ -191,26 +188,23 @@ const MyNearWallet: WalletBehaviourFactory< href.searchParams.append("state", state); } - return await _state.wallet.handlePopupTransaction(href.toString(),(value)=>{ - return { - accountId: value?.signedRequest?.accountId || "" , - publicKey: value?.signedRequest?.publicKey || "", - signature: value?.signedRequest?.signature || "", + return await _state.wallet.handlePopupTransaction( + href.toString(), + (value) => { + return { + accountId: value?.signedRequest?.accountId || "", + publicKey: value?.signedRequest?.publicKey || "", + signature: value?.signedRequest?.signature || "", + }; } - }); + ); }, - async signAndSendTransaction({ - signerId, - receiverId, - actions, - callbackUrl, - }) { + async signAndSendTransaction({ signerId, receiverId, actions }) { logger.log("signAndSendTransaction", { signerId, receiverId, actions, - callbackUrl, }); const { contract } = store.getState(); @@ -219,16 +213,15 @@ const MyNearWallet: WalletBehaviourFactory< throw new Error("Wallet not signed in"); } const account = _state.wallet.account(); - + return account["signAndSendTransaction"]({ receiverId: receiverId || contract.contractId, actions: actions.map((action) => createAction(action)), - walletCallbackUrl: callbackUrl, }); }, - async signAndSendTransactions({ transactions, callbackUrl }) { - logger.log("signAndSendTransactions", { transactions, callbackUrl }); + async signAndSendTransactions({ transactions }) { + logger.log("signAndSendTransactions", { transactions }); if (!_state.wallet.isSignedIn()) { throw new Error("Wallet not signed in"); @@ -236,7 +229,6 @@ const MyNearWallet: WalletBehaviourFactory< return _state.wallet.requestSignTransactions({ transactions: await transformTransactions(transactions), - callbackUrl, }); }, @@ -250,13 +242,11 @@ export function setupMyNearWallet({ walletUrl, iconUrl = icon, deprecated = false, - successUrl = "", - failureUrl = "", -}: MyNearWalletParams = {}): WalletModuleFactory { +}: MyNearWalletParams = {}): WalletModuleFactory { return async (moduleOptions) => { return { id: "my-near-wallet", - type: "browser", + type: "injected", metadata: { name: "MyNearWallet", description: @@ -264,9 +254,7 @@ export function setupMyNearWallet({ iconUrl, deprecated, available: true, - successUrl, - failureUrl, - walletUrl: resolveWalletUrl(moduleOptions.options.network, walletUrl), + downloadUrl: resolveWalletUrl(moduleOptions.options.network, walletUrl), }, init: (options) => { return MyNearWallet({ @@ -278,4 +266,4 @@ export function setupMyNearWallet({ }, }; }; -} \ No newline at end of file +} From 32faf3d3ef33e9686223f67c40fb6e894eaedc2e Mon Sep 17 00:00:00 2001 From: trechriron Date: Mon, 10 Feb 2025 13:42:22 -0800 Subject: [PATCH 13/21] Added script for easy publish of packages to set scope public --- scripts/release-package-public.bash | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 scripts/release-package-public.bash diff --git a/scripts/release-package-public.bash b/scripts/release-package-public.bash new file mode 100644 index 000000000..6bd16a1e8 --- /dev/null +++ b/scripts/release-package-public.bash @@ -0,0 +1,5 @@ +OTP=112233 +TAG=latest + +npm publish dist/packages/hot-wallet --tag "${TAG}" --otp "${OTP}" --access public +npm publish dist/packages/react-hook --tag "${TAG}" --otp "${OTP}" --access public From 7386314669797f9a16e3ccbce5dc5f825dca55e6 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Tue, 11 Feb 2025 15:42:42 +0100 Subject: [PATCH 14/21] fix: package dependencies --- packages/react-hook/package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/react-hook/package.json b/packages/react-hook/package.json index 58fad83b1..89aec082e 100644 --- a/packages/react-hook/package.json +++ b/packages/react-hook/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/react-hook", - "version": "8.9.15", + "version": "8.10.0", "description": "React Hook and Context for Wallet Selector", "keywords": [ "near", @@ -20,14 +20,14 @@ }, "homepage": "https://github.com/near/wallet-selector/tree/main/packages/react-hook", "peerDependencies": { - "@near-wallet-selector/core": "8.9.15", - "@near-wallet-selector/modal-ui": "8.9.15", + "@near-wallet-selector/core": ">=8.10", + "@near-wallet-selector/modal-ui": ">=8.10", "react": "18.2.0", - "near-api-js": ">=4.0.0 <6.0.0" + "near-api-js": ">=4.0.0" }, "devDependencies": { - "@near-wallet-selector/core": "8.9.15", - "@near-wallet-selector/modal-ui": "8.9.15", - "near-api-js": ">=4.0.0 <6.0.0" + "@near-wallet-selector/core": ">=8.10", + "@near-wallet-selector/modal-ui": ">=8.10", + "near-api-js": ">=4.0.0" } } From 6481e84e55b45b578103b089600d509409d7c9fd Mon Sep 17 00:00:00 2001 From: Guille Date: Tue, 11 Feb 2025 16:06:54 +0100 Subject: [PATCH 15/21] Update package.json --- packages/react-hook/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-hook/package.json b/packages/react-hook/package.json index 89aec082e..6c63bfc1d 100644 --- a/packages/react-hook/package.json +++ b/packages/react-hook/package.json @@ -20,14 +20,14 @@ }, "homepage": "https://github.com/near/wallet-selector/tree/main/packages/react-hook", "peerDependencies": { - "@near-wallet-selector/core": ">=8.10", - "@near-wallet-selector/modal-ui": ">=8.10", + "@near-wallet-selector/core": ">=8.9.15", + "@near-wallet-selector/modal-ui": ">=8.9.15", "react": "18.2.0", "near-api-js": ">=4.0.0" }, "devDependencies": { - "@near-wallet-selector/core": ">=8.10", - "@near-wallet-selector/modal-ui": ">=8.10", + "@near-wallet-selector/core": ">=8.9.15", + "@near-wallet-selector/modal-ui": ">=8.9.15", "near-api-js": ">=4.0.0" } } From 33b166041a17216974a487c4077c9317be2b9d67 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Thu, 13 Feb 2025 14:26:09 +0100 Subject: [PATCH 16/21] fix: signandsendtxs hook returns results --- packages/react-hook/src/lib/WalletSelectorProvider.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/react-hook/src/lib/WalletSelectorProvider.tsx b/packages/react-hook/src/lib/WalletSelectorProvider.tsx index c9c67f8fc..d6f121d33 100644 --- a/packages/react-hook/src/lib/WalletSelectorProvider.tsx +++ b/packages/react-hook/src/lib/WalletSelectorProvider.tsx @@ -237,12 +237,18 @@ export function WalletSelectorProvider({ * @returns {Promise} - the resulting transactions */ const signAndSendTransactions = useCallback( - ({ transactions }: { transactions: Array }) => { + async ({ transactions }: { transactions: Array }) => { if (!wallet) { throw new WalletError("No wallet connected"); } - return wallet.signAndSendTransactions({ transactions }); + const sentTxs = (await wallet.signAndSendTransactions({ + transactions, + })) as Array; + + return sentTxs.map((tx: FinalExecutionOutcome) => + providers.getTransactionLastResult(tx) + ); }, [wallet] ); From 09c3dc39edb85a93d994162a7d5ffcaa9f2219ca Mon Sep 17 00:00:00 2001 From: Jordi Parra Crespo Date: Tue, 18 Feb 2025 13:35:14 +0100 Subject: [PATCH 17/21] chore: bump @peersyst/near-mobile-signer to v1.1.4 --- package.json | 2 +- yarn.lock | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 6300a66a5..5c0ea94dc 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "@near-snap/sdk": "0.6.0", "@noble/ciphers": "0.5.3", "@noble/hashes": "1.4.0", - "@peersyst/near-mobile-signer": "1.1.2", + "@peersyst/near-mobile-signer": "1.1.4", "@ramper/near": "0.0.30", "@tanstack/react-query": "5.24.8", "@walletconnect/modal": "2.6.2", diff --git a/yarn.lock b/yarn.lock index c06f108ea..0c202f481 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6398,10 +6398,10 @@ "@parcel/utils" "2.12.0" nullthrows "^1.1.1" -"@peersyst/near-mobile-signer@1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@peersyst/near-mobile-signer/-/near-mobile-signer-1.1.2.tgz#2eff1eb8dae9468089240e167a21ca840bfd95c9" - integrity sha512-KLTIHvZ0mpRmhBxC8mWTvFbS4tRsnt+B5N6nbisXQYRGdOnmtakd8URxnmMGFhxmNzlihcUZ/xoU1m9OiyW6Wg== +"@peersyst/near-mobile-signer@1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@peersyst/near-mobile-signer/-/near-mobile-signer-1.1.4.tgz#50e86b03bb220b6aaf943cf2684140ad50971e8a" + integrity sha512-e2q56YaODntFo+oX7u4HCh2G7/eTf+kfluhc+dkT0q+3h5iH6MqkpIvpgbMrJCht2qxcgyVwbEpQ94ZLC8O/8A== dependencies: bn.js "^5.2.1" borsh "^0.7.0" @@ -21956,7 +21956,16 @@ string-range@~1.2, string-range@~1.2.1: resolved "https://registry.yarnpkg.com/string-range/-/string-range-1.2.2.tgz#a893ed347e72299bc83befbbf2a692a8d239d5dd" integrity sha512-tYft6IFi8SjplJpxCUxyqisD3b+R2CSkomrtJYCkvuf1KuCAWgz7YXt4O0jip7efpfCemwHEzTEAO8EuOYgh3w== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -22055,7 +22064,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -23868,7 +23884,7 @@ wordwrap@^1.0.0: resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -23886,6 +23902,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From d5392bca38dc1d4c6698bf6016056f4bb4a8f6c3 Mon Sep 17 00:00:00 2001 From: Guille Date: Tue, 4 Mar 2025 11:30:13 +0100 Subject: [PATCH 18/21] Update packages/core/src/lib/locale/es.json --- packages/core/src/lib/locale/es.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/lib/locale/es.json b/packages/core/src/lib/locale/es.json index d09827f16..02919bcbc 100644 --- a/packages/core/src/lib/locale/es.json +++ b/packages/core/src/lib/locale/es.json @@ -77,7 +77,7 @@ "disclaimer": "No podrás transferir cuentas que nunca hayan sido financiadas o utilizadas en NEAR.", "getPassphrase": { "button": "Continuar", - "checkLabel": "Copié o anoté la contraseña", + "checkLabel": "Copie o anote la contraseña", "desc": "Necesitarás ingresar esta contraseña cuando comiences a exportar tus cuentas a otra billetera.", "label": "Haga clic para Copiar", "title": "Copiar Contraseña Temporal", From 99a8320d00ff475554ca279787d63d92c0b10a11 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Thu, 6 Mar 2025 18:19:55 +0100 Subject: [PATCH 19/21] copied tests from meteor --- .../src/lib/my-near-wallet-connection.ts | 30 ---- .../src/lib/my-near-wallet.spec.ts | 150 +++++------------- 2 files changed, 44 insertions(+), 136 deletions(-) diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts index ab47cb933..bc6e969d2 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts @@ -89,10 +89,6 @@ export class MyNearWalletConnection { this._keyStore = (near.connection.signer as InMemorySigner).keyStore; this._authData = authData; this._authDataKey = authDataKey; - - if (!this.isSignedIn()) { - this._completeSignInPromise = this._completeSignInWithAccessKey(); - } } isSignedIn(): boolean { @@ -300,32 +296,6 @@ export class MyNearWalletConnection { ) as Promise; } - async _completeSignInWithAccessKey() { - const currentUrl = new URL(window.location.href); - const publicKey = currentUrl.searchParams.get("public_key") || ""; - const allKeys = (currentUrl.searchParams.get("all_keys") || "").split(","); - const accountId = currentUrl.searchParams.get("account_id") || ""; - // TODO: Handle errors during login - if (accountId) { - const authData = { - accountId, - allKeys, - }; - window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); - if (publicKey) { - await this._moveKeyFromTempToPermanent(accountId, publicKey); - } - this._authData = authData; - } - currentUrl.searchParams.delete("public_key"); - currentUrl.searchParams.delete("all_keys"); - currentUrl.searchParams.delete("account_id"); - currentUrl.searchParams.delete("meta"); - currentUrl.searchParams.delete("transactionHashes"); - - window.history.replaceState({}, document.title, currentUrl.toString()); - } - async completeSignInWithAccessKeys({ accountId, allKeys, diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.spec.ts b/packages/my-near-wallet/src/lib/my-near-wallet.spec.ts index 9c84817b6..701f689cc 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet.spec.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet.spec.ts @@ -1,59 +1,26 @@ /* eslint-disable @nx/enforce-module-boundaries */ -import type { - Near, - WalletConnection, - ConnectedWalletAccount, -} from "near-api-js"; -import type { AccountView } from "near-api-js/lib/providers/provider"; import { mock } from "jest-mock-extended"; - import { mockWallet } from "../../../core/src/lib/testUtils"; + import type { MockWalletDependencies } from "../../../core/src/lib/testUtils"; -import type { BrowserWallet } from "../../../core/src/lib/wallet"; +import type { InjectedWallet } from "../../../core/src/lib/wallet"; +import { setupMyNearWallet } from "./my-near-wallet"; +import type { MyNearWalletConnection } from "./my-near-wallet-connection"; -const createMyNearWallet = async (deps: MockWalletDependencies = {}) => { - const walletConnection = mock(); - const account = mock({ - connection: { - signer: { - getPublicKey: jest.fn().mockReturnValue(""), - }, - }, - }); +const accountId = "amirsaran.testnet"; +const publicKey = "GF7tLvSzcxX4EtrMFtGvGTb2yUj2DhL8hWzc97BwUkyC"; - jest.mock("near-api-js", () => { - const module = jest.requireActual("near-api-js"); - return { - ...module, - connect: jest.fn().mockResolvedValue(mock()), - WalletConnection: jest.fn().mockReturnValue(walletConnection), - }; - }); +const createMyNearWallet = async (deps: MockWalletDependencies = {}) => { + const walletConnection = mock(); - walletConnection.isSignedIn.calledWith().mockReturnValue(true); - walletConnection.getAccountId - .calledWith() - .mockReturnValue("test-account.testnet"); - walletConnection.account.calledWith().mockReturnValue(account); - // @ts-ignore - // near-api-js marks this method as protected. - // TODO: return value instead of null - account.signAndSendTransaction.calledWith().mockReturnValue(null); - account.state.calledWith().mockResolvedValue( - mock({ - amount: "1000000000000000000000000", - }) + const { wallet } = await mockWallet( + setupMyNearWallet(), + deps ); - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { setupMyNearWallet } = require("./my-near-wallet"); - const { wallet } = await mockWallet(setupMyNearWallet(), deps); - return { - nearApiJs: require("near-api-js"), - wallet, walletConnection, - account, + wallet, }; }; @@ -62,17 +29,17 @@ afterEach(() => { }); describe("signIn", () => { - it("sign into near wallet", async () => { - const { wallet, nearApiJs } = await createMyNearWallet(); + it.skip("sign into mynearwallet", async () => { + const { wallet, walletConnection } = await createMyNearWallet(); await wallet.signIn({ contractId: "test.testnet" }); - expect(nearApiJs.connect).toHaveBeenCalled(); + expect(walletConnection.requestSignIn).toHaveBeenCalled(); }); }); describe("signOut", () => { - it("sign out of near wallet", async () => { + it.skip("sign out of mynearwallet", async () => { const { wallet, walletConnection } = await createMyNearWallet(); await wallet.signIn({ contractId: "test.testnet" }); @@ -83,83 +50,54 @@ describe("signOut", () => { }); describe("getAccounts", () => { - it("returns array of accounts", async () => { - const { wallet, walletConnection } = await createMyNearWallet(); + it.skip("returns array of accounts", async () => { + const { wallet } = await createMyNearWallet(); await wallet.signIn({ contractId: "test.testnet" }); const result = await wallet.getAccounts(); - expect(walletConnection.getAccountId).toHaveBeenCalled(); - expect(result).toEqual([ - { accountId: "test-account.testnet", publicKey: "" }, - ]); + expect(result).toEqual([{ accountId, publicKey }]); }); }); describe("signAndSendTransaction", () => { - // TODO: Figure out why imports to core are returning undefined. - it("signs and sends transaction", async () => { - const { wallet, walletConnection, account } = await createMyNearWallet(); + it.skip("sign transaction in mynearwallet", async () => { + const { wallet, walletConnection } = await createMyNearWallet(); await wallet.signIn({ contractId: "test.testnet" }); - const result = await wallet.signAndSendTransaction({ - receiverId: "guest-book.testnet", + await wallet.signAndSendTransaction({ + signerId: accountId, + receiverId: "test.testnet", actions: [], }); expect(walletConnection.account).toHaveBeenCalled(); - // near-api-js marks this method as protected. - // @ts-ignore - expect(account.signAndSendTransaction).toHaveBeenCalled(); - // @ts-ignore - expect(account.signAndSendTransaction).toBeCalledWith({ - actions: [], - receiverId: "guest-book.testnet", - }); - expect(result).toEqual(null); }); }); -describe("buildImportAccountsUrl", () => { - it("returns import url", async () => { - const { wallet } = await createMyNearWallet(); - - expect(typeof wallet.buildImportAccountsUrl).toBe("function"); - - // @ts-ignore - expect(wallet?.buildImportAccountsUrl()).toEqual( - "https://testnet.mynearwallet.com/batch-import" - ); - }); -}); - -describe("signMessage", () => { - it("sign message", async () => { - const { wallet } = await createMyNearWallet(); +describe("signAndSendTransactions", () => { + it.skip("sign transactions in mynearwallet", async () => { + const { wallet, walletConnection } = await createMyNearWallet(); - const replace = window.location.replace; + const transactions = [ + { + signerId: accountId, + receiverId: "test.testnet", + actions: [], + }, + { + signerId: accountId, + receiverId: "test.testnet", + actions: [], + }, + ]; - Object.defineProperty(window, "location", { - value: { replace: jest.fn() }, + await wallet.signIn({ contractId: "test.testnet" }); + const result = await wallet.signAndSendTransactions({ + transactions, }); - const attributes = { - message: "test message", - nonce: Buffer.from("30990309-30990309-390A303-292090"), - recipient: "test.app", - callbackUrl: "https://test.app", - }; - - const result = await wallet.signMessage!(attributes); - - const nonceBase64 = attributes.nonce.toString("base64"); - const urlParams = `https://testnet.mynearwallet.com/sign-message?message=test+message&nonce=${encodeURIComponent( - nonceBase64 - )}&recipient=test.app&callbackUrl=https%3A%2F%2Ftest.app`; - - expect(result).toBe(undefined); - expect(window.location.replace).toHaveBeenCalledWith(urlParams); - - window.location.replace = replace; + expect(walletConnection.account).toHaveBeenCalled(); + expect(result.length).toEqual(transactions.length); }); }); From 79fae3aee5e19cb5560d9c573aa7b09975e1783f Mon Sep 17 00:00:00 2001 From: Trentin C Bergeron Date: Fri, 7 Mar 2025 07:11:22 -0800 Subject: [PATCH 20/21] Revert "feat: My near wallet has been modified to open a modal window" --- .../src/lib/my-near-wallet-connection.ts | 527 ------------------ .../src/lib/my-near-wallet.spec.ts | 150 +++-- .../my-near-wallet/src/lib/my-near-wallet.ts | 52 +- 3 files changed, 136 insertions(+), 593 deletions(-) delete mode 100644 packages/my-near-wallet/src/lib/my-near-wallet-connection.ts diff --git a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts b/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts deleted file mode 100644 index bc6e969d2..000000000 --- a/packages/my-near-wallet/src/lib/my-near-wallet-connection.ts +++ /dev/null @@ -1,527 +0,0 @@ -import type { SignedMessage } from "@near-wallet-selector/core"; -import { serialize } from "borsh"; -import type { Connection, InMemorySigner, Near } from "near-api-js"; -import { Account, KeyPair } from "near-api-js"; -import type { SignAndSendTransactionOptions } from "near-api-js/lib/account"; -import type { KeyStore } from "near-api-js/lib/key_stores"; -import type { FinalExecutionOutcome } from "near-api-js/lib/providers"; -import type { AccessKeyInfoView } from "near-api-js/lib/providers/provider"; -import type { Action, Transaction } from "near-api-js/lib/transaction"; -import { SCHEMA, createTransaction } from "near-api-js/lib/transaction"; -import { PublicKey } from "near-api-js/lib/utils"; -import { base_decode } from "near-api-js/lib/utils/serialize"; - -const LOGIN_WALLET_URL_SUFFIX = "/login/"; -const MULTISIG_HAS_METHOD = "add_request_and_confirm"; -const LOCAL_STORAGE_KEY_SUFFIX = "_wallet_auth_key"; -const PENDING_ACCESS_KEY_PREFIX = "pending_key"; // browser storage key for a pending access key (i.e. key has been generated but we are not sure it was added yet) - -const DEFAULT_POPUP_WIDTH = 480; -const DEFAULT_POPUP_HEIGHT = 640; -const POLL_INTERVAL = 300; - -interface SignInOptions { - contractId?: string; - methodNames?: Array; - successUrl?: string; - failureUrl?: string; - keyType?: "ed25519" | "secp256k1"; -} - -interface WalletMessage { - status: "success" | "failure" | "pending"; - transactionHashes?: string; - error?: string; - [key: string]: unknown; - signedRequest?: SignedMessage; - errorMessage?: string; - errorCode?: string; -} - -/** - * Information to send NEAR wallet for signing transactions and redirecting the browser back to the calling application - */ -interface RequestSignTransactionsOptions { - /** list of transactions to sign */ - transactions: Array; - /** url NEAR Wallet will redirect to after transaction signing is complete */ - callbackUrl?: string; - /** meta information NEAR Wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param */ - meta?: string; -} - -interface AuthData { - accountId?: string; - allKeys?: Array; -} - -interface WalletResponseData extends WalletMessage { - public_key?: string; - all_keys?: Array; - account_id?: string; -} - -export class MyNearWalletConnection { - _walletBaseUrl: string; - _authDataKey: string; - _keyStore: KeyStore; - _authData: AuthData; - _networkId: string; - _near: Near; - _connectedAccount?: MyNearConnectedWalletAccount | null; - _completeSignInPromise?: Promise; - - constructor(near: Near, appKeyPrefix: string) { - if (typeof appKeyPrefix !== "string") { - throw new Error( - "Please define a clear appKeyPrefix for this WalletConnection instance" - ); - } - - this._near = near; - const authDataKey = appKeyPrefix + LOCAL_STORAGE_KEY_SUFFIX; - const authData = JSON.parse( - window.localStorage.getItem(authDataKey) || "{}" - ) as AuthData; - - this._networkId = near.config.networkId; - this._walletBaseUrl = near.config.walletUrl; - this._keyStore = (near.connection.signer as InMemorySigner).keyStore; - this._authData = authData; - this._authDataKey = authDataKey; - } - - isSignedIn(): boolean { - return !!this._authData.accountId; - } - - async isSignedInAsync(): Promise { - if (!this._completeSignInPromise) { - return this.isSignedIn(); - } - - await this._completeSignInPromise; - return this.isSignedIn(); - } - - getAccountId(): string { - return this._authData.accountId || ""; - } - - async requestSignInUrl({ - contractId, - methodNames, - successUrl, - failureUrl, - keyType = "ed25519", - }: SignInOptions): Promise { - const currentUrl = new URL(window.location.href); - const newUrl = new URL(this._walletBaseUrl + LOGIN_WALLET_URL_SUFFIX); - newUrl.searchParams.set("success_url", successUrl || currentUrl.href); - newUrl.searchParams.set("failure_url", failureUrl || currentUrl.href); - if (contractId) { - /* Throws exception if contract account does not exist */ - const contractAccount = await this._near.account(contractId); - await contractAccount.state(); - - newUrl.searchParams.set("contract_id", contractId); - const accessKey = KeyPair.fromRandom(keyType); - newUrl.searchParams.set( - "public_key", - accessKey.getPublicKey().toString() - ); - await this._keyStore.setKey( - this._networkId, - PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey(), - accessKey - ); - } - - if (methodNames) { - methodNames.forEach((methodName) => { - newUrl.searchParams.append("methodNames", methodName); - }); - } - - return newUrl.toString(); - } - - async handlePopupTransaction( - url: string, - callback: (result: WalletMessage) => T - ): Promise { - const screenWidth = window.innerWidth || screen.width; - const screenHeight = window.innerHeight || screen.height; - const left = (screenWidth - DEFAULT_POPUP_WIDTH) / 2; - const top = (screenHeight - DEFAULT_POPUP_HEIGHT) / 2; - const childWindow = window.open( - url, - "My Near Wallet", - `width=${DEFAULT_POPUP_WIDTH},height=${DEFAULT_POPUP_HEIGHT},top=${top},left=${left}` - ); - - if (!childWindow) { - throw new Error( - "Popup window blocked. Please allow popups for this site." - ); - } - - return new Promise((resolve, reject) => { - const messageHandler = this.setupMessageHandler( - resolve, - reject, - childWindow, - callback - ); - - const intervalId = setInterval(() => { - if (childWindow.closed) { - window.removeEventListener("message", messageHandler); - clearInterval(intervalId); - reject(new Error("User closed the window")); - } - }, POLL_INTERVAL); - }); - } - - private setupMessageHandler( - resolve: (value: T) => void, - reject: (reason?: unknown) => void, - childWindow: Window | null, - callback: (result: WalletMessage) => T - ): (event: MessageEvent) => Promise { - const handler = async (event: MessageEvent) => { - const message = event.data as WalletMessage; - - // check if the URL are the same - const origin = new URL(event.origin); - const walletBaseUrl = new URL(this._walletBaseUrl); - if (origin.origin !== walletBaseUrl.origin) { - // eslint-disable-next-line no-console - console.warn("Ignoring message from different origin", origin.origin); - return; - } - - switch (message.status) { - case "success": - childWindow?.close(); - resolve(callback(message)); - break; - case "failure": - childWindow?.close(); - reject(new Error(message.errorMessage || "Transaction failed")); - break; - default: - // eslint-disable-next-line no-console - console.warn("Unhandled message status:", message.status); - } - }; - - window.addEventListener("message", handler); - return handler; - } - - async requestSignIn( - options: SignInOptions - ): Promise> { - const url = await this.requestSignInUrl(options); - return await this.handlePopupTransaction(url, async (data) => { - const responseData = data as WalletResponseData; - const { - public_key: publicKey, - all_keys: allKeys, - account_id: accountId, - } = responseData; - - if (accountId && publicKey && allKeys) { - await this.completeSignInWithAccessKeys({ - accountId, - publicKey, - allKeys, - }); - return [{ accountId, publicKey }]; - } - throw new Error("Invalid response data from wallet"); - }); - } - - requestSignTransactionsUrl(options: RequestSignTransactionsOptions): string { - const { transactions, meta, callbackUrl } = options; - const currentUrl = new URL(window.location.href); - const newUrl = new URL("sign", this._walletBaseUrl); - - newUrl.searchParams.set( - "transactions", - transactions - .map((transaction) => serialize(SCHEMA.Transaction, transaction)) - .map((serialized) => Buffer.from(serialized).toString("base64")) - .join(",") - ); - newUrl.searchParams.set("callbackUrl", callbackUrl || currentUrl.href); - - if (meta) { - newUrl.searchParams.set("meta", meta); - } - - return newUrl.toString(); - } - - async requestSignTransactions( - options: RequestSignTransactionsOptions - ): Promise> { - const url = this.requestSignTransactionsUrl(options); - const transactionsHashes = ( - await this.handlePopupTransaction(url, (data) => data.transactionHashes) - )?.split(","); - - if (!transactionsHashes) { - throw new Error("No transaction hashes received"); - } - - return Promise.all( - transactionsHashes.map((hash) => - this._near.connection.provider.txStatus(hash, "unused", "NONE") - ) - ); - } - - requestSignTransaction( - options: RequestSignTransactionsOptions - ): Promise { - const url = this.requestSignTransactionsUrl(options); - - return this.handlePopupTransaction( - url, - (data) => data?.transactionHashes - ) as Promise; - } - - async completeSignInWithAccessKeys({ - accountId, - allKeys, - publicKey, - }: { - accountId: string; - allKeys: Array; - publicKey: string; - }) { - const authData = { - accountId, - allKeys, - }; - window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); - if (publicKey) { - await this._moveKeyFromTempToPermanent(accountId, publicKey); - } - this._authData = authData; - this.updateAccount(); - } - - async _moveKeyFromTempToPermanent(accountId: string, publicKey: string) { - const keyPair = await this._keyStore.getKey( - this._networkId, - PENDING_ACCESS_KEY_PREFIX + publicKey - ); - await this._keyStore.setKey(this._networkId, accountId, keyPair); - await this._keyStore.removeKey( - this._networkId, - PENDING_ACCESS_KEY_PREFIX + publicKey - ); - } - - signOut() { - this._authData = {}; - window.localStorage.removeItem(this._authDataKey); - this._keyStore.clear(); - } - - /* eslint-disable @typescript-eslint/no-use-before-define */ - account() { - if (!this._connectedAccount) { - this._connectedAccount = new MyNearConnectedWalletAccount( - this, - this._near.connection, - this._authData.accountId || "" - ); - } - return this._connectedAccount; - } - - updateAccount() { - this._connectedAccount = new MyNearConnectedWalletAccount( - this, - this._near.connection, - this._authData.accountId || "" - ); - } - /* eslint-enable @typescript-eslint/no-use-before-define */ -} - -export class MyNearConnectedWalletAccount extends Account { - walletConnection: MyNearWalletConnection; - - constructor( - walletConnection: MyNearWalletConnection, - connection: Connection, - accountId: string - ) { - super(connection, accountId); - this.walletConnection = walletConnection; - } - - async signAndSendTransaction({ - receiverId, - actions, - walletMeta, - walletCallbackUrl = window.location.href, - }: SignAndSendTransactionOptions): Promise { - const localKey = await this.connection.signer.getPublicKey( - this.accountId, - this.connection.networkId - ); - let accessKey = await this.accessKeyForTransaction( - receiverId, - actions, - localKey - ); - if (!accessKey) { - throw new Error( - `Cannot find matching key for transaction sent to ${receiverId}` - ); - } - - if (localKey && localKey.toString() === accessKey.public_key) { - try { - return await super.signAndSendTransaction({ receiverId, actions }); - } catch (e: unknown) { - /* eslint-disable @typescript-eslint/no-use-before-define */ - if ( - typeof e === "object" && - e !== null && - "type" in e && - (e as any).type === "NotEnoughAllowance" // eslint-disable-line @typescript-eslint/no-explicit-any - ) { - accessKey = await this.accessKeyForTransaction(receiverId, actions); - } else { - throw e; - } - /* eslint-enable @typescript-eslint/no-use-before-define */ - } - } - - const block = await this.connection.provider.block({ finality: "final" }); - const blockHash = base_decode(block.header.hash); - - if (!accessKey) { - throw new Error("No matching key found for transaction"); - } - const publicKey = PublicKey.from(accessKey.public_key); - // TODO: Cache & listen for nonce updates for given access key - const nonce = accessKey.access_key.nonce + BigInt("1"); - const transaction = createTransaction( - this.accountId, - publicKey, - receiverId, - nonce, - actions, - blockHash - ); - const transactionHashes = - await this.walletConnection.requestSignTransaction({ - transactions: [transaction], - meta: walletMeta, - callbackUrl: walletCallbackUrl, - }); - - return new Promise((resolve, reject) => { - this.connection.provider - .txStatus(transactionHashes, "unused", "NONE") - .then(resolve) - .catch(reject); - - setTimeout(() => { - reject(new Error("Failed to redirect to sign transaction")); - }, 1000); - }); - - // TODO: Aggregate multiple transaction request with "debounce". - // TODO: Introduce TransactionQueue which also can be used to watch for status? - } - - async accessKeyMatchesTransaction( - accessKey: AccessKeyInfoView, - receiverId: string, - actions: Array - ): Promise { - const { - access_key: { permission }, - } = accessKey; - if (permission === "FullAccess") { - return true; - } - - if (permission.FunctionCall) { - const { receiver_id: allowedReceiverId, method_names: allowedMethods } = - permission.FunctionCall; - /******************************** - Accept multisig access keys and let wallets attempt to signAndSendTransaction - If an access key has itself as receiverId and method permission add_request_and_confirm, then it is being used in a wallet with multisig contract: https://github.com/near/core-contracts/blob/671c05f09abecabe7a7e58efe942550a35fc3292/multisig/src/lib.rs#L149-L153 - ********************************/ - if ( - allowedReceiverId === this.accountId && - allowedMethods.includes(MULTISIG_HAS_METHOD) - ) { - return true; - } - if (allowedReceiverId === receiverId) { - if (actions.length !== 1) { - return false; - } - const [{ functionCall }] = actions; - return !!( - functionCall && - (!functionCall.deposit || functionCall.deposit.toString() === "0") && // TODO: Should support charging amount smaller than allowance? - (allowedMethods.length === 0 || - allowedMethods.includes(functionCall.methodName)) - ); - // TODO: Handle cases when allowance doesn't have enough to pay for gas - } - } - // TODO: Support other permissions than FunctionCall - - return false; - } - - async accessKeyForTransaction( - receiverId: string, - actions: Array, - localKey?: PublicKey - ): Promise { - const accessKeys = await this.getAccessKeys(); - - if (localKey) { - const accessKey = accessKeys.find( - (key) => key.public_key.toString() === localKey.toString() - ); - if ( - accessKey && - (await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) - ) { - return accessKey; - } - } - - const walletKeys = this.walletConnection._authData.allKeys; - for (const accessKey of accessKeys) { - if ( - walletKeys && - walletKeys.indexOf(accessKey.public_key) !== -1 && - (await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) - ) { - return accessKey; - } - } - - return null; - } -} diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.spec.ts b/packages/my-near-wallet/src/lib/my-near-wallet.spec.ts index 701f689cc..9c84817b6 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet.spec.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet.spec.ts @@ -1,26 +1,59 @@ /* eslint-disable @nx/enforce-module-boundaries */ +import type { + Near, + WalletConnection, + ConnectedWalletAccount, +} from "near-api-js"; +import type { AccountView } from "near-api-js/lib/providers/provider"; import { mock } from "jest-mock-extended"; -import { mockWallet } from "../../../core/src/lib/testUtils"; +import { mockWallet } from "../../../core/src/lib/testUtils"; import type { MockWalletDependencies } from "../../../core/src/lib/testUtils"; -import type { InjectedWallet } from "../../../core/src/lib/wallet"; -import { setupMyNearWallet } from "./my-near-wallet"; -import type { MyNearWalletConnection } from "./my-near-wallet-connection"; - -const accountId = "amirsaran.testnet"; -const publicKey = "GF7tLvSzcxX4EtrMFtGvGTb2yUj2DhL8hWzc97BwUkyC"; +import type { BrowserWallet } from "../../../core/src/lib/wallet"; const createMyNearWallet = async (deps: MockWalletDependencies = {}) => { - const walletConnection = mock(); + const walletConnection = mock(); + const account = mock({ + connection: { + signer: { + getPublicKey: jest.fn().mockReturnValue(""), + }, + }, + }); + + jest.mock("near-api-js", () => { + const module = jest.requireActual("near-api-js"); + return { + ...module, + connect: jest.fn().mockResolvedValue(mock()), + WalletConnection: jest.fn().mockReturnValue(walletConnection), + }; + }); - const { wallet } = await mockWallet( - setupMyNearWallet(), - deps + walletConnection.isSignedIn.calledWith().mockReturnValue(true); + walletConnection.getAccountId + .calledWith() + .mockReturnValue("test-account.testnet"); + walletConnection.account.calledWith().mockReturnValue(account); + // @ts-ignore + // near-api-js marks this method as protected. + // TODO: return value instead of null + account.signAndSendTransaction.calledWith().mockReturnValue(null); + account.state.calledWith().mockResolvedValue( + mock({ + amount: "1000000000000000000000000", + }) ); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { setupMyNearWallet } = require("./my-near-wallet"); + const { wallet } = await mockWallet(setupMyNearWallet(), deps); + return { - walletConnection, + nearApiJs: require("near-api-js"), wallet, + walletConnection, + account, }; }; @@ -29,17 +62,17 @@ afterEach(() => { }); describe("signIn", () => { - it.skip("sign into mynearwallet", async () => { - const { wallet, walletConnection } = await createMyNearWallet(); + it("sign into near wallet", async () => { + const { wallet, nearApiJs } = await createMyNearWallet(); await wallet.signIn({ contractId: "test.testnet" }); - expect(walletConnection.requestSignIn).toHaveBeenCalled(); + expect(nearApiJs.connect).toHaveBeenCalled(); }); }); describe("signOut", () => { - it.skip("sign out of mynearwallet", async () => { + it("sign out of near wallet", async () => { const { wallet, walletConnection } = await createMyNearWallet(); await wallet.signIn({ contractId: "test.testnet" }); @@ -50,54 +83,83 @@ describe("signOut", () => { }); describe("getAccounts", () => { - it.skip("returns array of accounts", async () => { - const { wallet } = await createMyNearWallet(); + it("returns array of accounts", async () => { + const { wallet, walletConnection } = await createMyNearWallet(); await wallet.signIn({ contractId: "test.testnet" }); const result = await wallet.getAccounts(); - expect(result).toEqual([{ accountId, publicKey }]); + expect(walletConnection.getAccountId).toHaveBeenCalled(); + expect(result).toEqual([ + { accountId: "test-account.testnet", publicKey: "" }, + ]); }); }); describe("signAndSendTransaction", () => { - it.skip("sign transaction in mynearwallet", async () => { - const { wallet, walletConnection } = await createMyNearWallet(); + // TODO: Figure out why imports to core are returning undefined. + it("signs and sends transaction", async () => { + const { wallet, walletConnection, account } = await createMyNearWallet(); await wallet.signIn({ contractId: "test.testnet" }); - await wallet.signAndSendTransaction({ - signerId: accountId, - receiverId: "test.testnet", + const result = await wallet.signAndSendTransaction({ + receiverId: "guest-book.testnet", actions: [], }); expect(walletConnection.account).toHaveBeenCalled(); + // near-api-js marks this method as protected. + // @ts-ignore + expect(account.signAndSendTransaction).toHaveBeenCalled(); + // @ts-ignore + expect(account.signAndSendTransaction).toBeCalledWith({ + actions: [], + receiverId: "guest-book.testnet", + }); + expect(result).toEqual(null); }); }); -describe("signAndSendTransactions", () => { - it.skip("sign transactions in mynearwallet", async () => { - const { wallet, walletConnection } = await createMyNearWallet(); +describe("buildImportAccountsUrl", () => { + it("returns import url", async () => { + const { wallet } = await createMyNearWallet(); - const transactions = [ - { - signerId: accountId, - receiverId: "test.testnet", - actions: [], - }, - { - signerId: accountId, - receiverId: "test.testnet", - actions: [], - }, - ]; + expect(typeof wallet.buildImportAccountsUrl).toBe("function"); - await wallet.signIn({ contractId: "test.testnet" }); - const result = await wallet.signAndSendTransactions({ - transactions, + // @ts-ignore + expect(wallet?.buildImportAccountsUrl()).toEqual( + "https://testnet.mynearwallet.com/batch-import" + ); + }); +}); + +describe("signMessage", () => { + it("sign message", async () => { + const { wallet } = await createMyNearWallet(); + + const replace = window.location.replace; + + Object.defineProperty(window, "location", { + value: { replace: jest.fn() }, }); - expect(walletConnection.account).toHaveBeenCalled(); - expect(result.length).toEqual(transactions.length); + const attributes = { + message: "test message", + nonce: Buffer.from("30990309-30990309-390A303-292090"), + recipient: "test.app", + callbackUrl: "https://test.app", + }; + + const result = await wallet.signMessage!(attributes); + + const nonceBase64 = attributes.nonce.toString("base64"); + const urlParams = `https://testnet.mynearwallet.com/sign-message?message=test+message&nonce=${encodeURIComponent( + nonceBase64 + )}&recipient=test.app&callbackUrl=https%3A%2F%2Ftest.app`; + + expect(result).toBe(undefined); + expect(window.location.replace).toHaveBeenCalledWith(urlParams); + + window.location.replace = replace; }); }); diff --git a/packages/my-near-wallet/src/lib/my-near-wallet.ts b/packages/my-near-wallet/src/lib/my-near-wallet.ts index 0956222f3..5ac291a8d 100644 --- a/packages/my-near-wallet/src/lib/my-near-wallet.ts +++ b/packages/my-near-wallet/src/lib/my-near-wallet.ts @@ -2,15 +2,14 @@ import * as nearAPI from "near-api-js"; import type { WalletModuleFactory, WalletBehaviourFactory, + BrowserWallet, Transaction, Optional, Network, Account, - InjectedWallet, } from "@near-wallet-selector/core"; import { createAction } from "@near-wallet-selector/wallet-utils"; import icon from "./icon"; -import { MyNearWalletConnection } from "./my-near-wallet-connection"; export interface MyNearWalletParams { walletUrl?: string; @@ -21,7 +20,7 @@ export interface MyNearWalletParams { } interface MyNearWalletState { - wallet: MyNearWalletConnection; + wallet: nearAPI.WalletConnection; keyStore: nearAPI.keyStores.BrowserLocalStorageKeyStore; } @@ -57,7 +56,7 @@ const setupWalletState = async ( headers: {}, }); - const wallet = new MyNearWalletConnection(near, "near_app"); + const wallet = new nearAPI.WalletConnection(near, "near_app"); return { wallet, @@ -66,13 +65,14 @@ const setupWalletState = async ( }; const MyNearWallet: WalletBehaviourFactory< - InjectedWallet, + BrowserWallet, { params: MyNearWalletExtraOptions } > = async ({ metadata, options, store, params, logger, id }) => { const _state = await setupWalletState(params, options.network); const getAccounts = async (): Promise> => { const accountId = _state.wallet.getAccountId(); const account = _state.wallet.account(); + if (!accountId || !account) { return []; } @@ -131,7 +131,7 @@ const MyNearWallet: WalletBehaviourFactory< }; return { - async signIn({ contractId, methodNames }) { + async signIn({ contractId, methodNames, successUrl, failureUrl }) { const existingAccounts = await getAccounts(); if (existingAccounts.length) { @@ -141,6 +141,8 @@ const MyNearWallet: WalletBehaviourFactory< await _state.wallet.requestSignIn({ contractId, methodNames, + successUrl, + failureUrl, }); return getAccounts(); @@ -188,23 +190,22 @@ const MyNearWallet: WalletBehaviourFactory< href.searchParams.append("state", state); } - return await _state.wallet.handlePopupTransaction( - href.toString(), - (value) => { - return { - accountId: value?.signedRequest?.accountId || "", - publicKey: value?.signedRequest?.publicKey || "", - signature: value?.signedRequest?.signature || "", - }; - } - ); + window.location.replace(href.toString()); + + return; }, - async signAndSendTransaction({ signerId, receiverId, actions }) { + async signAndSendTransaction({ + signerId, + receiverId, + actions, + callbackUrl, + }) { logger.log("signAndSendTransaction", { signerId, receiverId, actions, + callbackUrl, }); const { contract } = store.getState(); @@ -212,16 +213,18 @@ const MyNearWallet: WalletBehaviourFactory< if (!_state.wallet.isSignedIn() || !contract) { throw new Error("Wallet not signed in"); } + const account = _state.wallet.account(); return account["signAndSendTransaction"]({ receiverId: receiverId || contract.contractId, actions: actions.map((action) => createAction(action)), + walletCallbackUrl: callbackUrl, }); }, - async signAndSendTransactions({ transactions }) { - logger.log("signAndSendTransactions", { transactions }); + async signAndSendTransactions({ transactions, callbackUrl }) { + logger.log("signAndSendTransactions", { transactions, callbackUrl }); if (!_state.wallet.isSignedIn()) { throw new Error("Wallet not signed in"); @@ -229,6 +232,7 @@ const MyNearWallet: WalletBehaviourFactory< return _state.wallet.requestSignTransactions({ transactions: await transformTransactions(transactions), + callbackUrl, }); }, @@ -242,11 +246,13 @@ export function setupMyNearWallet({ walletUrl, iconUrl = icon, deprecated = false, -}: MyNearWalletParams = {}): WalletModuleFactory { + successUrl = "", + failureUrl = "", +}: MyNearWalletParams = {}): WalletModuleFactory { return async (moduleOptions) => { return { id: "my-near-wallet", - type: "injected", + type: "browser", metadata: { name: "MyNearWallet", description: @@ -254,7 +260,9 @@ export function setupMyNearWallet({ iconUrl, deprecated, available: true, - downloadUrl: resolveWalletUrl(moduleOptions.options.network, walletUrl), + successUrl, + failureUrl, + walletUrl: resolveWalletUrl(moduleOptions.options.network, walletUrl), }, init: (options) => { return MyNearWallet({ From ae722e7d64293ce7dc4d09ab06ad108af2934d2a Mon Sep 17 00:00:00 2001 From: trechriron Date: Fri, 7 Mar 2025 07:32:02 -0800 Subject: [PATCH 21/21] updating versions for minor publish --- package.json | 2 +- packages/account-export/package.json | 2 +- packages/arepa-wallet/package.json | 2 +- packages/bitget-wallet/package.json | 2 +- packages/bitte-wallet/package.json | 2 +- packages/coin98-wallet/package.json | 2 +- packages/core/package.json | 2 +- packages/ethereum-wallets/package.json | 2 +- packages/here-wallet/package.json | 2 +- packages/hot-wallet/package.json | 2 +- packages/ledger/package.json | 2 +- packages/math-wallet/package.json | 2 +- packages/meteor-wallet-app/package.json | 2 +- packages/meteor-wallet/package.json | 2 +- packages/mintbase-wallet/package.json | 2 +- packages/modal-ui-js/package.json | 2 +- packages/modal-ui/package.json | 2 +- packages/my-near-wallet/package.json | 2 +- packages/narwallets/package.json | 2 +- packages/near-mobile-wallet/package.json | 2 +- packages/near-snap/package.json | 2 +- packages/nearfi/package.json | 2 +- packages/neth/package.json | 2 +- packages/nightly/package.json | 2 +- packages/okx-wallet/package.json | 2 +- packages/ramper-wallet/package.json | 2 +- packages/react-hook/package.json | 2 +- packages/sender/package.json | 2 +- packages/wallet-connect/package.json | 2 +- packages/wallet-utils/package.json | 2 +- packages/welldone-wallet/package.json | 2 +- packages/xdefi/package.json | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 5c0ea94dc..3758971b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "near-wallet-selector", - "version": "8.10.0", + "version": "8.10.1", "description": "NEAR Wallet Selector makes it easy for users to interact with your dApp by providing an abstraction over various wallets within the NEAR ecosystem", "keywords": [ "near", diff --git a/packages/account-export/package.json b/packages/account-export/package.json index 64564286d..e6c0f8c49 100644 --- a/packages/account-export/package.json +++ b/packages/account-export/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/account-export", - "version": "8.10.0", + "version": "8.10.1", "description": "This is the Export Selector UI package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/arepa-wallet/package.json b/packages/arepa-wallet/package.json index 1857961fc..e05e4afc7 100644 --- a/packages/arepa-wallet/package.json +++ b/packages/arepa-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/arepa-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "Arepa Wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/bitget-wallet/package.json b/packages/bitget-wallet/package.json index 18b0de937..8f42c411d 100644 --- a/packages/bitget-wallet/package.json +++ b/packages/bitget-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/bitget-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "Bitget wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/bitte-wallet/package.json b/packages/bitte-wallet/package.json index aa70a989f..8503f88c0 100644 --- a/packages/bitte-wallet/package.json +++ b/packages/bitte-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/bitte-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "Bitte wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/coin98-wallet/package.json b/packages/coin98-wallet/package.json index 8939b13af..b50974303 100644 --- a/packages/coin98-wallet/package.json +++ b/packages/coin98-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/coin98-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "Coin 98 wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/core/package.json b/packages/core/package.json index 6987e8ccb..3ab2ab75e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/core", - "version": "8.10.0", + "version": "8.10.1", "description": "This is the core package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/ethereum-wallets/package.json b/packages/ethereum-wallets/package.json index c384538fd..e75050247 100644 --- a/packages/ethereum-wallets/package.json +++ b/packages/ethereum-wallets/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/ethereum-wallets", - "version": "8.10.0", + "version": "8.10.1", "description": "Ethereum wallets package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/here-wallet/package.json b/packages/here-wallet/package.json index 9bbcc728a..909ef152b 100644 --- a/packages/here-wallet/package.json +++ b/packages/here-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/here-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "Here wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/hot-wallet/package.json b/packages/hot-wallet/package.json index 37f52de2f..04717169e 100644 --- a/packages/hot-wallet/package.json +++ b/packages/hot-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/hot-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "HOT Wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/ledger/package.json b/packages/ledger/package.json index 28239c967..ef1182d17 100644 --- a/packages/ledger/package.json +++ b/packages/ledger/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/ledger", - "version": "8.10.0", + "version": "8.10.1", "description": "Ledger package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/math-wallet/package.json b/packages/math-wallet/package.json index 2a16d4979..942519de5 100644 --- a/packages/math-wallet/package.json +++ b/packages/math-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/math-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "Math wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/meteor-wallet-app/package.json b/packages/meteor-wallet-app/package.json index 52ae89882..4793ad329 100644 --- a/packages/meteor-wallet-app/package.json +++ b/packages/meteor-wallet-app/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/meteor-wallet-app", - "version": "8.10.0", + "version": "8.10.1", "description": "Meteor wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/meteor-wallet/package.json b/packages/meteor-wallet/package.json index 3186941a7..4c1496404 100644 --- a/packages/meteor-wallet/package.json +++ b/packages/meteor-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/meteor-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "Meteor wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/mintbase-wallet/package.json b/packages/mintbase-wallet/package.json index 9976f9a92..66143661d 100644 --- a/packages/mintbase-wallet/package.json +++ b/packages/mintbase-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/mintbase-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "Mintbase wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/modal-ui-js/package.json b/packages/modal-ui-js/package.json index add012453..ddef575c0 100644 --- a/packages/modal-ui-js/package.json +++ b/packages/modal-ui-js/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/modal-ui-js", - "version": "8.10.0", + "version": "8.10.1", "description": "Modal UI package for NEAR wallet Selector", "keywords": [ "near", diff --git a/packages/modal-ui/package.json b/packages/modal-ui/package.json index 4ed9f667a..99056c0c6 100644 --- a/packages/modal-ui/package.json +++ b/packages/modal-ui/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/modal-ui", - "version": "8.10.0", + "version": "8.10.1", "description": "Modal UI package for NEAR wallet Selector", "keywords": [ "near", diff --git a/packages/my-near-wallet/package.json b/packages/my-near-wallet/package.json index ecd87a814..6e6e8b214 100644 --- a/packages/my-near-wallet/package.json +++ b/packages/my-near-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/my-near-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "My Near Wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/narwallets/package.json b/packages/narwallets/package.json index 58702e295..ac4daa1b9 100644 --- a/packages/narwallets/package.json +++ b/packages/narwallets/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/narwallets", - "version": "8.10.0", + "version": "8.10.1", "description": "This is the Narwallets package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/near-mobile-wallet/package.json b/packages/near-mobile-wallet/package.json index f0a2384ed..429be8627 100644 --- a/packages/near-mobile-wallet/package.json +++ b/packages/near-mobile-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/near-mobile-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "NEAR Mobile wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/near-snap/package.json b/packages/near-snap/package.json index 3adad76b5..ff2cb2e36 100644 --- a/packages/near-snap/package.json +++ b/packages/near-snap/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/near-snap", - "version": "8.10.0", + "version": "8.10.1", "description": "Metamask snap to interact with Near dapps.", "keywords": [ "near", diff --git a/packages/nearfi/package.json b/packages/nearfi/package.json index 3bf4c40af..d76e75aa7 100644 --- a/packages/nearfi/package.json +++ b/packages/nearfi/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/nearfi", - "version": "8.10.0", + "version": "8.10.1", "description": "Nearfi package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/neth/package.json b/packages/neth/package.json index 2cdcb3a56..7d5af4595 100644 --- a/packages/neth/package.json +++ b/packages/neth/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/neth", - "version": "8.10.0", + "version": "8.10.1", "description": "Control NEAR accounts with ETH accounts", "author": "mattlockyer", "keywords": [ diff --git a/packages/nightly/package.json b/packages/nightly/package.json index db583c214..3811e6b99 100644 --- a/packages/nightly/package.json +++ b/packages/nightly/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/nightly", - "version": "8.10.0", + "version": "8.10.1", "description": "Nightly wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/okx-wallet/package.json b/packages/okx-wallet/package.json index 4c9bcb9b1..17b651216 100644 --- a/packages/okx-wallet/package.json +++ b/packages/okx-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/okx-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "OKX Wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/ramper-wallet/package.json b/packages/ramper-wallet/package.json index 8f6d4b85f..54a15eff2 100644 --- a/packages/ramper-wallet/package.json +++ b/packages/ramper-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/ramper-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "Ramper wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/react-hook/package.json b/packages/react-hook/package.json index 6c63bfc1d..16c826700 100644 --- a/packages/react-hook/package.json +++ b/packages/react-hook/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/react-hook", - "version": "8.10.0", + "version": "8.10.1", "description": "React Hook and Context for Wallet Selector", "keywords": [ "near", diff --git a/packages/sender/package.json b/packages/sender/package.json index 3a9ed92d7..16b434268 100644 --- a/packages/sender/package.json +++ b/packages/sender/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/sender", - "version": "8.10.0", + "version": "8.10.1", "description": "Sender wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/wallet-connect/package.json b/packages/wallet-connect/package.json index 2b107afd7..94c5d6fa0 100644 --- a/packages/wallet-connect/package.json +++ b/packages/wallet-connect/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/wallet-connect", - "version": "8.10.0", + "version": "8.10.1", "description": "Wallet Connect package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/wallet-utils/package.json b/packages/wallet-utils/package.json index 8d247b9cf..81dc2f99b 100644 --- a/packages/wallet-utils/package.json +++ b/packages/wallet-utils/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/wallet-utils", - "version": "8.10.0", + "version": "8.10.1", "description": "Wallet utils package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/welldone-wallet/package.json b/packages/welldone-wallet/package.json index 214c7248d..19de38c1e 100644 --- a/packages/welldone-wallet/package.json +++ b/packages/welldone-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/welldone-wallet", - "version": "8.10.0", + "version": "8.10.1", "description": "Welldone wallet package for NEAR Wallet Selector.", "keywords": [ "near", diff --git a/packages/xdefi/package.json b/packages/xdefi/package.json index be487e964..4860f988e 100644 --- a/packages/xdefi/package.json +++ b/packages/xdefi/package.json @@ -1,6 +1,6 @@ { "name": "@near-wallet-selector/xdefi", - "version": "8.10.0", + "version": "8.10.1", "description": "This is the XDEFI package for NEAR Wallet Selector.", "keywords": [ "near",