From 5dd4fd74e4c9220df83f8b7dc2a102ce43daa098 Mon Sep 17 00:00:00 2001 From: siandreev Date: Fri, 20 Dec 2024 19:41:08 +0100 Subject: [PATCH 1/9] chore: add assets images --- apps/web/public/img/trx.svg | 9 +++++++++ apps/web/public/img/usdt-ton.png | Bin 0 -> 6272 bytes apps/web/public/img/usdt-trc20.png | Bin 0 -> 6808 bytes 3 files changed, 9 insertions(+) create mode 100644 apps/web/public/img/trx.svg create mode 100644 apps/web/public/img/usdt-ton.png create mode 100644 apps/web/public/img/usdt-trc20.png diff --git a/apps/web/public/img/trx.svg b/apps/web/public/img/trx.svg new file mode 100644 index 000000000..c21ce3b77 --- /dev/null +++ b/apps/web/public/img/trx.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/web/public/img/usdt-ton.png b/apps/web/public/img/usdt-ton.png new file mode 100644 index 0000000000000000000000000000000000000000..45c9f83219446656e49698ed29da33e4c54e65e1 GIT binary patch literal 6272 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 z z^Rp=6DM!fz&yh%K&Q4}`Xlr{FVxQ;-dyh_VewBNX0KITfpJskln0z9>;dl+8lS4a3 zu`y_Rd>txjSYP`1xEgmkZta1FpVFXMcB5qASj60SJK{6a;oiOn#Se_2{7^X@U6Wz! zel6^ONQV7}uEIJ=v7SqEQM^}t>!0BKKH%tVk-m+_oz4Li>GJ`KT1%uc1sCs ztf)l!%#RTF;xmvEn~*0}^)5+hspc4&s5?S`rIyv;qNmRT$m&}cw zT-MVU-Ny;U;?$=g|E9Ef=73vjngHhlN}T>}8cGMhjC1Yp#+gfQ#JO7rkzlq8@sz*+ z8Oo--3za15>%yu~${m&^@>+dQ4l6cq^yS-dP}1;Bt3JZY)TZhAZ6vf$asGujyxz}< z;Cj<%kSWuBuJ>b<(@@_+$C&Gd{cee(uJCGhDp#z9L@tRl)U}aXG=~%)3;A@BZ}D8! zLX6CCt^_6hM+GGw@q8Esizq#P0HSOnngN8Bc;!40$3L9Msg3=b*5a0HX5NsRB%sm4 znIyDJQS#KwRBJniU@`vMkm>0>WXM2$M3UNg*%_9Ac$i*Jv5@1=n?(Mn#(!?9TnN~G z@VM9KHu#U1M9r-1g(+~Y{|O;b5SfdMavUJbT)~gZvPa0c-CovQ%V7^NDn9v2+{Qe5 zPmTSy_KKj~hc6aT+_)i#9WhAM!OGwH6rmwSNI(pCnc#I$5xAs6#IV`AdD$EGMQz+$ zcZu>v?4$SAe7Ux*L`+lLHSu(i25-JXC6KQ}D56$J-Wu7<8}pO~m#YWrOI*Yq!gxel z18rI zG95%!XRIhDrTQP9si8Rt7s^amWY0&;gQ<|3D@M-NA&K0V=Te|lrEl=T>%syR4;%w` z{!xTU9<8x_N9hQy`eI0J&1ZpX!^8c$=++#L8s&4vjY*meBq~1mLS&|6hkB+M zHEe+RDKAoi^(UMkKLuJ~gPdKpz|hH?d$Ze9gqTd&5WNOGE79#p7@(Vm{!) zs(WwPRg!AR1DavaMM=-PC!pHQoY~zYq!ik^slL!isPM&3cyqb7A|_@bMvg2%2q;Yq zWRvuwR2C6>y$AIX7M0tbBx+M^p_D7xu**mi_sc$}nR48sP(Z&cSH$69p7%Q(d7+BM z7v>O=Z}EtGVPR@K-OAXV8J)V|m}fkRC)?kHECEFUp1_*tlqbOuL Date: Fri, 20 Dec 2024 19:42:52 +0100 Subject: [PATCH 2/9] chore: compress png --- apps/web/public/img/usdt-ton.png | Bin 6272 -> 2878 bytes apps/web/public/img/usdt-trc20.png | Bin 6808 -> 3132 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/web/public/img/usdt-ton.png b/apps/web/public/img/usdt-ton.png index 45c9f83219446656e49698ed29da33e4c54e65e1..cbf39b04f3a323e5b27907aa0e6f7961bebe2882 100644 GIT binary patch literal 2878 zcmV-E3&He>P)j0PO0GR3km+AnIj{unI0F#pdm6ZUO?*NpP0F;yflam0L=m44M0GR0jkBHwGL0F#pdm+1hKk^q;N0FsgbnCk$OlK_#C0F#pdl9B+Dk^qyF z0GH|jnCbwMk^qyF0F#pdl9B+Dk^q_O0Fsgbl9B+ElmGw!0GR4PuC7?PxZm;d{{H_0 zl$4v+_GrTMsoUFj$MfU!^LWVfDW#?Nb4N3yawtE(-hrtkRp==S#U`S}W$mj{-XXTQHp zw6yE@{sx=tiqQ7$_xB8#nD_hp%<1VMp`q06?dA0JaKpnqtgIHH>;jqUx8wTZ^ZjJL zzC^IFbHv1n&CQU|(Ea}Z3!UrQ@cicW{c*K*4Dq|@`~D)P?NYk&uHX6a`Tm8@_0;bC#^>jR%gfsD?|;e3HLmWW z+W4v5_&%<#ddc)zL_t(|UhLM( zYQ!)MhS4sy=^_~l0UHum3wuo5cmI2Ry+XHU$VIl)Ma~m^L_*HfzjnD?T$z^`#u0zU zF~nsqFGekMTJgY|vToHaC&mVf*(ue`0XqeSrYYc%Sh|kNGY+HdY;$UyoN-RAeUfL0 zkZ-%n1To6(rWH}KoLga33_sHCj)s;&+T#dKk#KC8(HM)y=Fx%R0KB4$McjmMtPZ}cy#=F*$S6$Rj~tVS*iTdHIw66!;8 zt19i0K_gjc0Q0he*cc2pU>otqVUgGdvcZqpBxx-Nubp_2P5T255c2n0RZ0eid(Ygt zGvD6Q=kwy8J9hxG6ed(%%T{zB+h9u*?F0Ih=^uBi}7{iudDu)}tg z3X#MNYP|+~9B&XIl9)j$u*;AL5$Bcz`P?SKE*+w-SFE1vH}YVgE8@g&wdZ5t0QEF- zS+Z8r{suTf0lBytLi|mBa%h-xl{GQ$VFv9LnKSW>sfH2c+d?rmVV2huB4^?mYn_2Z zLeNJZ-&yFf9ylgc4-k8-=u}L{ofCpj!n5&vd#@I81ZqWA3(1AI$m|zj{?xzmH99_r@rkru=`l&$(*@Wv(3&B3J5vu8vXW+Py zuS`}oee~!hoEL(3iolxhMi%8Rn2|^}cBb0O*VMp_)2cw5{eTRV&p^<o+$+;B$9ET+L92v-SbE4o0z>UqLR)OEp)D@T3xbWUy*__VC|uLOzO4 zP=wnfPRhL*oM(|Z-;?M1LfR0FqOMTfn+;oQv<)mu>{|y~sF-E<$d}n4hI51S}dwGa;NO%Kqm<|7SBc_@I$(?|VBuR}k@|CcHM93Hciwp+NsG zC^UyUy&}G13U!MTLinIDg>pOMgRwqdV+m;?lo3*{Sw@%dtW4^&978CWQlx}XIbs-v zPKL%1?T$*^a}Oj1Up?3(U-a$9ZpQ%9fH^T(Ta$N9o; z=^>O0pSSQUYS-BC{RxT){fJy6Fc2T4QIO0-5)6!y$D-)X$Md-Xd3qnJ1<-_+>dDXj zWwMW4Rt?7wKI2oELyHu86*aTPi2r0OQ@-dAb{GW~9}9mOawBDw

kVv$=b9IhBQ2 zX#dZ5od&1a;~pe%8;-CP!=w{iM?7ZXOVt9B#Nzga>NzAaJXS@TT->hEiwe>(7Ddvi zX;0|I1=2jV`ZA|-U`MF_x7BwAZZtQN7s^&Ff5cxvQ>xptLfJ6t&^4{|Gnx`j$_X_D ze3qr2cd1Pop+*$VS(W?xTm8qnkT)v#4rB>M2zzNEA)jx5dF1+!M~hRlW`VdkCyDM@ z6S}Dx_EXo&(o_^yT#NeJN;E!YG#`cauf)MXM8zo{yBOaz7F^55jL|uxvqlq{j?Xgp zz0hyVdd8SJqp3wekiS$SI_9jPAgZ9zv$F2d@pWvteIrih6_&D}|1|dD(Yeb01NW}H1qtQ`iqgzm5;b!~4s-bj(ETo*A`u|hq z_g~=34fYxOY*!~j?TpweCDj)Jrd+Fb|04Ue&MDRrq&-K(jJs5AVM3mp(G=NYM4cn@ zZ?tet{uQQ2$bsyD*NB*jJFw;Ml+PEP@d_lEoM?LxG)Re5054Ks{S|`0&$m5y zrAd*n_r7MMv({Nv(?dmZ@s-K&CCO} zmQZmM$O#`K@U>Do73&p3l}%v&qtH8GwjqQPnhQOHJ{J<=BzO$e@=c+94R}m~xaLOE zdn57DAus*;rqDc4ReaKy(;GiaBq5TloW}YFP@2Q?3Q01W0zF6kTxJQ9lz;+I14<-0 z#sUF{#EX$(lH?08DdfAZ7}G!KwTVJmlH_2rLoO8yzHa@UBPu=xfXxa?Mp!^eX{hV< zF|Bh%TLAFHb3m-rbuW#{i1=~;fzuB~b-o()uojsjNqMnUCUQPD)MJXN>coakA1#w5 z1tMIq?wv+mYd3UYj0e*?DKMb<;`6a1whQN99~c%pwPEE+gHNmGC!=D0eXWM+i#0zP z#V*3gk5BNNbnK&uu6=Sc2HBFzX!}9fE}pQqPh>_q>V&lYBcqF(WP)|}&nccTn#mZw calPH<1CqA%DqwYIbN~PV07*qoM6N<$f-aNC+W-In literal 6272 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 z z^Rp=6DM!fz&yh%K&Q4}`Xlr{FVxQ;-dyh_VewBNX0KITfpJskln0z9>;dl+8lS4a3 zu`y_Rd>txjSYP`1xEgmkZta1FpVFXMcB5qASj60SJK{6a;oiOn#Se_2{7^X@U6Wz! zel6^ONQV7}uEIJ=v7SqEQM^}t>!0BKKH%tVk-m+_oz4Li>GJ`KT1%uc1sCs ztf)l!%#RTF;xmvEn~*0}^)5+hspc4&s5?S`rIyv;qNmRT$m&}cw zT-MVU-Ny;U;?$=g|E9Ef=73vjngHhlN}T>}8cGMhjC1Yp#+gfQ#JO7rkzlq8@sz*+ z8Oo--3za15>%yu~${m&^@>+dQ4l6cq^yS-dP}1;Bt3JZY)TZhAZ6vf$asGujyxz}< z;Cj<%kSWuBuJ>b<(@@_+$C&Gd{cee(uJCGhDp#z9L@tRl)U}aXG=~%)3;A@BZ}D8! zLX6CCt^_6hM+GGw@q8Esizq#P0HSOnngN8Bc;!40$3L9Msg3=b*5a0HX5NsRB%sm4 znIyDJQS#KwRBJniU@`vMkm>0>WXM2$M3UNg*%_9Ac$i*Jv5@1=n?(Mn#(!?9TnN~G z@VM9KHu#U1M9r-1g(+~Y{|O;b5SfdMavUJbT)~gZvPa0c-CovQ%V7^NDn9v2+{Qe5 zPmTSy_KKj~hc6aT+_)i#9WhAM!OGwH6rmwSNI(pCnc#I$5xAs6#IV`AdD$EGMQz+$ zcZu>v?4$SAe7Ux*L`+lLHSu(i25-JXC6KQ}D56$J-Wu7<8}pO~m#YWrOI*Yq!gxel z18rI zG95%!XRIhDrTQP9si8Rt7s^amWY0&;gQ<|3D@M-NA&K0V=Te|lrEl=T>%syR4;%w` z{!xTU9<8x_N9hQy`eI0J&1ZpX!^8c$=++#L8s&4vjY*meBq~1mLS&|6hkB+M zHEe+RDKAoi^(UMkKLuJ~gPdKpz|hH?d$Ze9gqTd&5WNOGE79#p7@(Vm{!) zs(WwPRg!AR1DavaMM=-PC!pHQoY~zYq!ik^slL!isPM&3cyqb7A|_@bMvg2%2q;Yq zWRvuwR2C6>y$AIX7M0tbBx+M^p_D7xu**mi_sc$}nR48sP(Z&cSH$69p7%Q(d7+BM z7v>O=Z}EtGVPR@K-OAXV8J)V|m}fkRC)?kHECEFUp1_*tlqbOuL Date: Tue, 24 Dec 2024 13:54:10 +0100 Subject: [PATCH 3/9] feat: support ledger ton proof --- packages/core/src/AppSdk.ts | 6 +- packages/core/src/service/ledger/connector.ts | 15 +++ .../sender/ledger-message-sender.ts | 1 - .../src/service/tonConnect/connectService.ts | 8 +- .../components/ConnectLedgerNotification.tsx | 92 ++++++++++----- .../connect/TonConnectNotification.tsx | 10 +- packages/uikit/src/state/mnemonic.ts | 109 +++++++++++++----- packages/uikit/src/state/tonConnect.ts | 75 ++++++++---- 8 files changed, 221 insertions(+), 95 deletions(-) diff --git a/packages/core/src/AppSdk.ts b/packages/core/src/AppSdk.ts index 0c29413ac..d40cdcfdd 100644 --- a/packages/core/src/AppSdk.ts +++ b/packages/core/src/AppSdk.ts @@ -6,7 +6,7 @@ import { NFT } from './entries/nft'; import { FavoriteSuggestion, LatestSuggestion } from './entries/suggestion'; import { TonContract, TonWalletStandard } from './entries/wallet'; import { KeystoneMessageType, KeystonePathInfo } from './service/keystone/types'; -import { LedgerTransaction } from './service/ledger/connector'; +import { LedgerTonProofRequest, LedgerTransaction } from './service/ledger/connector'; import { TonTransferParams } from './service/deeplinkingService'; export type GetPasswordType = 'confirm' | 'unlock'; @@ -38,7 +38,9 @@ export interface UIEvents { boc: string; wallet: TonWalletStandard; }; - ledger: { path: number[]; transaction: LedgerTransaction }; + ledger: + | { path: number[]; transaction: LedgerTransaction } + | { path: number[]; tonProof: LedgerTonProofRequest }; keystone: { message: Buffer; messageType: KeystoneMessageType; pathInfo?: KeystonePathInfo }; loading: void; transfer: TransferInitParams; diff --git a/packages/core/src/service/ledger/connector.ts b/packages/core/src/service/ledger/connector.ts index e84e0bdcc..1ff94db95 100644 --- a/packages/core/src/service/ledger/connector.ts +++ b/packages/core/src/service/ledger/connector.ts @@ -14,6 +14,21 @@ const withDeadline = (p: Promise, ms: number): Promise => export type LedgerTonTransport = TonTransport; export type LedgerTransaction = Parameters[1]; +export type LedgerTonProofRequest = { + domain: string; + timestamp: number; + payload: Buffer; + testOnly?: boolean; + bounceable?: boolean; + chain?: number; + subwalletId?: number; + walletVersion?: 'v3r2' | 'v4'; +}; + +export type LedgerTonProofResponse = { + signature: Buffer; + hash: Buffer; +}; export const connectLedger = async () => { let transport: Transport; diff --git a/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts b/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts index cdadb1b8b..f01ad43fe 100644 --- a/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts +++ b/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts @@ -25,7 +25,6 @@ import { MessagePayloadParam, serializePayload } from '../encoder/types'; import { TonPayloadFormat } from '@ton-community/ton-ledger/dist/TonTransport'; import { TON_ASSET } from '../../../entries/crypto/asset/constants'; import { TonEstimation } from '../../../entries/send'; -import { Network } from '../../../entries/network'; export class LedgerMessageSender { constructor( diff --git a/packages/core/src/service/tonConnect/connectService.ts b/packages/core/src/service/tonConnect/connectService.ts index b64c49db6..f6e0806fc 100644 --- a/packages/core/src/service/tonConnect/connectService.ts +++ b/packages/core/src/service/tonConnect/connectService.ts @@ -292,11 +292,12 @@ export const tonConnectProofPayload = ( origin: string, wallet: string, payload: string -): ConnectProofPayload => { +): ConnectProofPayload & { domain: string } => { const timestampBuffer = Buffer.allocUnsafe(8); timestampBuffer.writeBigInt64LE(BigInt(timestamp)); - const domainBuffer = Buffer.from(new URL(origin).host); + const domain = new URL(origin).host; + const domainBuffer = Buffer.from(domain); const domainLengthBuffer = Buffer.allocUnsafe(4); domainLengthBuffer.writeInt32LE(domainBuffer.byteLength); @@ -329,7 +330,8 @@ export const tonConnectProofPayload = ( domainBuffer, payload, origin, - messageBuffer + messageBuffer, + domain }; }; diff --git a/packages/uikit/src/components/ConnectLedgerNotification.tsx b/packages/uikit/src/components/ConnectLedgerNotification.tsx index be0554542..92fdde018 100644 --- a/packages/uikit/src/components/ConnectLedgerNotification.tsx +++ b/packages/uikit/src/components/ConnectLedgerNotification.tsx @@ -3,7 +3,11 @@ import { useAppSdk } from '../hooks/appSdk'; import { useTranslation } from '../hooks/translation'; import { Notification } from './Notification'; import { Button } from './fields/Button'; -import { LedgerTransaction } from '@tonkeeper/core/dist/service/ledger/connector'; +import { + LedgerTonProofRequest, + LedgerTonProofResponse, + LedgerTransaction +} from '@tonkeeper/core/dist/service/ledger/connector'; import { useConnectLedgerMutation } from '../state/ledger'; import styled from 'styled-components'; import { Cell } from '@ton/core'; @@ -31,11 +35,18 @@ const ButtonsBlock = styled.div` } `; +type LedgerContentLedgerParams = + | { path: number[]; transaction: LedgerTransaction; onSubmit: (result: Cell) => void } + | { + path: number[]; + tonProof: LedgerTonProofRequest; + onSubmit: (result: LedgerTonProofResponse) => void; + }; + export const LedgerContent: FC<{ - ledgerParams: { path: number[]; transaction: LedgerTransaction }; + ledgerParams: LedgerContentLedgerParams; onClose: (reason?: unknown) => void; - onSubmit: (result: Cell) => void; -}> = ({ ledgerParams, onClose, onSubmit }) => { +}> = ({ ledgerParams, onClose }) => { const { t } = useTranslation(); const [isCompleted, setIsCompleted] = useState(false); @@ -47,29 +58,41 @@ export const LedgerContent: FC<{ reset: resetConnection } = useConnectLedgerMutation(); - const connect = () => { - connectLedger() - .then(transport => - transport - .signTransaction(ledgerParams.path, ledgerParams.transaction) - .then(val => { - setIsCompleted(true); - setTimeout(() => onSubmit(val), 500); - }) - .catch(e => { - console.error(e); - if ( - typeof e === 'object' && - 'message' in e && - e.message.includes('0x6985') - ) { - onClose(new UserCancelledError('Cancel auth request')); - } else { - onClose(e); - } - }) - ) - .catch(console.debug); + const connect = async () => { + try { + const transport = await connectLedger(); + try { + if ('transaction' in ledgerParams) { + const val = await transport.signTransaction( + ledgerParams.path, + ledgerParams.transaction + ); + setIsCompleted(true); + setTimeout(() => ledgerParams.onSubmit(val), 500); + } else { + const val = await transport.getAddressProof( + ledgerParams.path, + ledgerParams.tonProof + ); + setIsCompleted(true); + setTimeout(() => ledgerParams.onSubmit(val), 500); + } + } catch (e) { + console.error(e); + if ( + typeof e === 'object' && + e && + 'message' in e && + (e.message as string).includes('0x6985') + ) { + onClose(new UserCancelledError('Cancel auth request')); + } else { + onClose(e); + } + } + } catch (error) { + console.debug(error); + } }; useEffect(() => { @@ -119,7 +142,9 @@ const ConnectLedgerNotification = () => { const { t } = useTranslation(); const [ledgerParams, setLedgerParams] = useState< - { path: number[]; transaction: LedgerTransaction } | undefined + | { path: number[]; transaction: LedgerTransaction } + | { path: number[]; tonProof: LedgerTonProofRequest } + | undefined >(undefined); const [requestId, setId] = useState(undefined); @@ -158,7 +183,9 @@ const ConnectLedgerNotification = () => { const handler = (options: { method: 'ledger'; id?: number | undefined; - params: { path: number[]; transaction: LedgerTransaction }; + params: + | { path: number[]; transaction: LedgerTransaction } + | { path: number[]; tonProof: LedgerTonProofRequest }; }) => { setLedgerParams(options.params!); setId(options.id); @@ -171,7 +198,12 @@ const ConnectLedgerNotification = () => { const Content = useCallback(() => { if (!ledgerParams || !requestId) return undefined; - return ; + return ( + + ); }, [sdk, ledgerParams, requestId, onCancel, onSubmit]); return ( diff --git a/packages/uikit/src/components/connect/TonConnectNotification.tsx b/packages/uikit/src/components/connect/TonConnectNotification.tsx index 90aca9726..ab4a35f96 100644 --- a/packages/uikit/src/components/connect/TonConnectNotification.tsx +++ b/packages/uikit/src/components/connect/TonConnectNotification.tsx @@ -142,8 +142,8 @@ const ConnectContent: FC<{ } const tonProofRequested = params.items.some(item => item.name === 'ton_proof'); - const cantConnectLedger = - selectedAccountAndWallet.account.type === 'ledger' && tonProofRequested; + const cantConnectProof = + selectedAccountAndWallet.account.type === 'ton-multisig' && tonProofRequested; return ( @@ -187,15 +187,13 @@ const ConnectContent: FC<{ fullWidth primary loading={isLoading} - disabled={isLoading || cantConnectLedger || isReadOnly} + disabled={isLoading || cantConnectProof || isReadOnly} type="submit" > {t('ton_login_connect_button')} )} - {cantConnectLedger && ( - {t('ledger_operation_not_supported')} - )} + {cantConnectProof && {t('operation_not_supported')}} {isReadOnly && {t('operation_not_supported')}} {t('ton_login_notice')} diff --git a/packages/uikit/src/state/mnemonic.ts b/packages/uikit/src/state/mnemonic.ts index eb24973d6..24d4132f6 100644 --- a/packages/uikit/src/state/mnemonic.ts +++ b/packages/uikit/src/state/mnemonic.ts @@ -8,7 +8,11 @@ import { CellSigner, Signer } from '@tonkeeper/core/dist/entries/signer'; import { TonWalletStandard, WalletId } from '@tonkeeper/core/dist/entries/wallet'; import { accountsStorage } from '@tonkeeper/core/dist/service/accountsStorage'; import { KeystoneMessageType } from '@tonkeeper/core/dist/service/keystone/types'; -import { LedgerTransaction } from '@tonkeeper/core/dist/service/ledger/connector'; +import { + LedgerTonProofRequest, + LedgerTonProofResponse, + LedgerTransaction +} from '@tonkeeper/core/dist/service/ledger/connector'; import { decryptWalletMnemonic, mnemonicToKeypair @@ -191,7 +195,7 @@ export const getSigner = async ( )!; const path = getLedgerAccountPathByIndex(derivation.index); const callback = async (transaction: LedgerTransaction) => - pairLedgerByNotification(sdk, path, transaction); + pairLedgerByNotification<'transaction'>(sdk, path, { transaction }); callback.type = 'ledger' as const; return callback; } @@ -417,40 +421,83 @@ const pairKeystoneByNotification = async ( }); }; -const pairLedgerByNotification = async ( +export const getLedgerTonProofSigner = async ( sdk: IAppSdk, - path: number[], - transaction: LedgerTransaction -): Promise => { - const id = Date.now(); - return new Promise((resolve, reject) => { - sdk.uiEvents.emit('ledger', { - method: 'ledger', - id, - params: { path, transaction } - }); + accountId: AccountId, + { + walletId + }: { + walletId?: WalletId; + } = {} +): Promise<(request: LedgerTonProofRequest) => Promise> => { + const account = await accountsStorage(sdk.storage).getAccount(accountId); + if (!account) { + throw new Error('Account not found'); + } - const onCallback = (message: { - method: 'response'; - id?: number | undefined; - params: unknown; - }) => { - if (message.id === id) { - const { params } = message; - sdk.uiEvents.off('response', onCallback); + if (account.type !== 'ledger') { + throw new Error('Unexpected account type'); + } - if (params && typeof params === 'object' && params instanceof Cell) { - resolve(params as Cell); - } else { - if (params instanceof Error) { - reject(params); + const wallet = + walletId !== undefined ? account.getTonWallet(walletId) : account.activeTonWallet; + + const derivation = account.allAvailableDerivations.find( + d => d.activeTonWalletId === wallet!.id + )!; + const path = getLedgerAccountPathByIndex(derivation.index); + const callback = async (tonProof: LedgerTonProofRequest) => + pairLedgerByNotification<'ton-proof'>(sdk, path, { tonProof }); + callback.type = 'ledger' as const; + return callback; +}; + +const pairLedgerByNotification = async ( + sdk: IAppSdk, + path: number[], + request: T extends 'transaction' + ? { + transaction: LedgerTransaction; + } + : { + tonProof: LedgerTonProofRequest; + } +): Promise => { + const id = Date.now(); + return new Promise( + (resolve, reject) => { + sdk.uiEvents.emit('ledger', { + method: 'ledger', + id, + params: { path, ...request } + }); + + const onCallback = (message: { + method: 'response'; + id?: number | undefined; + params: unknown; + }) => { + if (message.id === id) { + const { params } = message; + sdk.uiEvents.off('response', onCallback); + + if ( + params && + typeof params === 'object' && + (params instanceof Cell || 'signature' in params) + ) { + resolve(params as T extends 'transaction' ? Cell : LedgerTonProofResponse); } else { - reject(new Error(params?.toString())); + if (params instanceof Error) { + reject(params); + } else { + reject(new Error(params?.toString())); + } } } - } - }; + }; - sdk.uiEvents.on('response', onCallback); - }); + sdk.uiEvents.on('response', onCallback); + } + ); }; diff --git a/packages/uikit/src/state/tonConnect.ts b/packages/uikit/src/state/tonConnect.ts index 5ba3f378d..8dcd77e8c 100644 --- a/packages/uikit/src/state/tonConnect.ts +++ b/packages/uikit/src/state/tonConnect.ts @@ -5,11 +5,12 @@ import { DAppManifest } from '@tonkeeper/core/dist/entries/tonConnect'; import { + createTonProofItem, getAppConnections, getTonConnectParams, + tonConnectProofPayload, toTonAddressItemReply, - toTonProofItemReply, - tonConnectProofPayload + toTonProofItemReply } from '@tonkeeper/core/dist/service/tonConnect/connectService'; import { AccountConnection, @@ -22,11 +23,12 @@ import { useAppSdk } from '../hooks/appSdk'; import { useTranslation } from '../hooks/translation'; import { subject } from '../libs/atom'; import { QueryKey } from '../libs/queryKey'; -import { signTonConnectOver } from './mnemonic'; +import { getLedgerTonProofSigner, signTonConnectOver } from './mnemonic'; import { isStandardTonWallet, TonWalletStandard, - WalletId + WalletId, + WalletVersion } from '@tonkeeper/core/dist/entries/wallet'; import { IStorage } from '@tonkeeper/core/dist/Storage'; import { @@ -109,7 +111,7 @@ export const useConnectTonConnectAppMutation = () => { walletId: WalletId; } >(async ({ request, manifest, webViewUrl, account, walletId }) => { - const selectedIsLedget = account.type === 'ledger'; + const selectedIsLedger = account.type === 'ledger'; const network = getNetworkByAccount(account); const api = getContextApiByNetwork(appContext, network); @@ -127,32 +129,61 @@ export const useConnectTonConnectAppMutation = () => { result.push(toTonAddressItemReply(wallet, network)); } if (item.name === 'ton_proof') { - if (!isStandardTonWallet(wallet) || selectedIsLedget) { + if (!isStandardTonWallet(wallet)) { throw new TxConfirmationCustomError( "Current wallet doesn't support connection to the service" ); } - const signTonConnect = signTonConnectOver({ - sdk, - accountId: account.id, - t, - checkTouchId - }); - const timestamp = await getServerTime(api); + const proof = tonConnectProofPayload( - timestamp, + await getServerTime(api), webViewUrl ?? manifest.url, wallet.rawAddress, item.payload ); - result.push( - await toTonProofItemReply({ - storage: sdk.storage, - account, - signTonConnect, - proof - }) - ); + + if (selectedIsLedger) { + const ledgerTonProofSigner = await getLedgerTonProofSigner(sdk, account.id, { + walletId: wallet.id + }); + + if ( + wallet.version !== WalletVersion.V3R2 && + wallet.version !== WalletVersion.V4R2 + ) { + throw new TxConfirmationCustomError( + "Current wallet doesn't support connection to the service" + ); + } + + const { signature } = await ledgerTonProofSigner({ + domain: proof.domain, + timestamp: proof.timestamp, + payload: Buffer.from(proof.payload), + walletVersion: wallet.version === WalletVersion.V3R2 ? 'v3r2' : 'v4' + }); + + result.push({ name: 'ton_proof', proof: createTonProofItem(signature, proof) }); + } else { + const signTonConnect = signTonConnectOver({ + sdk, + accountId: account.id, + wallet, + t, + checkTouchId + }); + result.push( + await toTonProofItemReply({ + storage: sdk.storage, + account, + signTonConnect, + proof + }) + ); + } + + console.log(result); + debugger } } From ef463f421e3fe77c76e4061a969f90ff7cfc31be Mon Sep 17 00:00:00 2001 From: siandreev Date: Tue, 24 Dec 2024 15:07:33 +0100 Subject: [PATCH 4/9] fix: update ton ledger package version --- packages/core/package.json | 2 +- packages/uikit/src/state/tonConnect.ts | 3 --- yarn.lock | 10 +++++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 32e0ac903..aa3b6cc03 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -24,7 +24,7 @@ "@keystonehq/keystone-sdk": "0.7.2", "@ledgerhq/hw-transport-webhid": "^6.28.6", "@ledgerhq/hw-transport-webusb": "^6.28.6", - "@ton-community/ton-ledger": "^7.2.0-pre.2", + "@ton-community/ton-ledger": "^7.2.0-pre.3", "@ton-keychain/core": "^0.0.4", "@ton/core": "0.56.0", "@ton/crypto": "3.2.0", diff --git a/packages/uikit/src/state/tonConnect.ts b/packages/uikit/src/state/tonConnect.ts index 8dcd77e8c..5ab2a4753 100644 --- a/packages/uikit/src/state/tonConnect.ts +++ b/packages/uikit/src/state/tonConnect.ts @@ -181,9 +181,6 @@ export const useConnectTonConnectAppMutation = () => { }) ); } - - console.log(result); - debugger } } diff --git a/yarn.lock b/yarn.lock index bae24abac..9820b601b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7290,16 +7290,16 @@ __metadata: languageName: node linkType: hard -"@ton-community/ton-ledger@npm:^7.2.0-pre.2": - version: 7.2.0-pre.2 - resolution: "@ton-community/ton-ledger@npm:7.2.0-pre.2" +"@ton-community/ton-ledger@npm:^7.2.0-pre.3": + version: 7.2.0-pre.3 + resolution: "@ton-community/ton-ledger@npm:7.2.0-pre.3" dependencies: "@ledgerhq/hw-transport": "npm:^6.28.4" "@ton/crypto": "npm:^3.2.0" teslabot: "npm:^1.5.0" peerDependencies: "@ton/core": ">=0.52.2" - checksum: 10/9ddd536a8484b0afe34975f2e7b82c998134612c394ba94aa64140398d795daf2f734368300c28d09f2d697166b30387f7542bcfcd3fc73031ba233b7e09630d + checksum: 10/332ae8a7208471dbaca7f335c63d92b6eb9b0a617ef8ed267a7ea5ff51a1e6a765911a92f416fdbc8e7a29b2b927c1b62690c3464d3667332a1455176c3ef218 languageName: node linkType: hard @@ -7378,7 +7378,7 @@ __metadata: "@keystonehq/keystone-sdk": "npm:0.7.2" "@ledgerhq/hw-transport-webhid": "npm:^6.28.6" "@ledgerhq/hw-transport-webusb": "npm:^6.28.6" - "@ton-community/ton-ledger": "npm:^7.2.0-pre.2" + "@ton-community/ton-ledger": "npm:^7.2.0-pre.3" "@ton-keychain/core": "npm:^0.0.4" "@ton/core": "npm:0.56.0" "@ton/crypto": "npm:3.2.0" From d328edecb1afbc33e56ce6bb17a54718e8ac68f4 Mon Sep 17 00:00:00 2001 From: siandreev Date: Tue, 24 Dec 2024 18:28:50 +0100 Subject: [PATCH 5/9] feat: support ledger multiply messages --- packages/core/src/AppSdk.ts | 2 +- packages/core/src/entries/signer.ts | 2 +- .../sender/ledger-message-sender.ts | 93 +++++++++++++------ .../src/service/tonConnect/connectService.ts | 2 +- packages/locales/src/tonkeeper-web/en.json | 35 ++++++- packages/locales/src/tonkeeper-web/ru-RU.json | 35 ++++++- .../components/ConnectLedgerNotification.tsx | 42 ++++++--- .../connect/TonTransactionNotification.tsx | 7 +- .../ledger/LedgerConnectionSteps.tsx | 54 +++++++++-- packages/uikit/src/state/mnemonic.ts | 17 ++-- 10 files changed, 218 insertions(+), 71 deletions(-) diff --git a/packages/core/src/AppSdk.ts b/packages/core/src/AppSdk.ts index d40cdcfdd..7c93a2655 100644 --- a/packages/core/src/AppSdk.ts +++ b/packages/core/src/AppSdk.ts @@ -39,7 +39,7 @@ export interface UIEvents { wallet: TonWalletStandard; }; ledger: - | { path: number[]; transaction: LedgerTransaction } + | { path: number[]; transactions: LedgerTransaction[] } | { path: number[]; tonProof: LedgerTonProofRequest }; keystone: { message: Buffer; messageType: KeystoneMessageType; pathInfo?: KeystonePathInfo }; loading: void; diff --git a/packages/core/src/entries/signer.ts b/packages/core/src/entries/signer.ts index 750747f7b..1e500d52a 100644 --- a/packages/core/src/entries/signer.ts +++ b/packages/core/src/entries/signer.ts @@ -5,7 +5,7 @@ export type BaseSigner = (message: Cell) => Promise; export type CellSigner = BaseSigner & { type: 'cell' }; -export type LedgerSigner = ((message: LedgerTransaction) => Promise) & { +export type LedgerSigner = ((messages: LedgerTransaction[]) => Promise) & { type: 'ledger'; }; diff --git a/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts b/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts index f01ad43fe..123869f4f 100644 --- a/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts +++ b/packages/core/src/service/ton-blockchain/sender/ledger-message-sender.ts @@ -12,7 +12,8 @@ import { getTTL, getWalletSeqNo, tonConnectAddressIsBounceable, - toStateInit + toStateInit, + estimationSigner } from '../utils'; import { AssetAmount } from '../../../entries/crypto/asset/asset-amount'; import { TonAsset, tonAssetAddressToString } from '../../../entries/crypto/asset/ton-asset'; @@ -25,6 +26,10 @@ import { MessagePayloadParam, serializePayload } from '../encoder/types'; import { TonPayloadFormat } from '@ton-community/ton-ledger/dist/TonTransport'; import { TON_ASSET } from '../../../entries/crypto/asset/constants'; import { TonEstimation } from '../../../entries/send'; +import { LedgerTransaction } from '../../ledger/connector'; +import { WalletMessageSender } from './wallet-message-sender'; +import { TonConnectEncoder } from '../encoder/ton-connect-encoder'; +import { UserCancelledError } from '@tonkeeper/uikit/dist/libs/errors/UserCancelledError'; export class LedgerMessageSender { constructor( @@ -33,6 +38,17 @@ export class LedgerMessageSender { private readonly signer: LedgerSigner ) {} + private async sign( + tx: T + ): Promise { + if (Array.isArray(tx)) { + return this.signer(tx) as Promise; + } else { + const res = await this.signer([tx]); + return res[0] as T extends LedgerTransaction ? Cell : Cell[]; + } + } + tonRawTransfer = async ({ to, value, @@ -48,7 +64,7 @@ export class LedgerMessageSender { }) => { const { timestamp, seqno, contract } = await this.getTransferParameters(); - const transfer = await this.signer({ + const transfer = await this.sign({ to, bounce, amount: value, @@ -79,7 +95,7 @@ export class LedgerMessageSender { }) => { const { timestamp, seqno, contract } = await this.getTransferParameters(); - const transfer = await this.signer({ + const transfer = await this.sign({ to: Address.parse(to), bounce: await userInputAddressIsBounceable(this.api, to), amount: BigInt(weiAmount.toFixed(0)), @@ -134,7 +150,7 @@ export class LedgerMessageSender { const { customPayload, stateInit, jettonWalletAddress } = await jettonEncoder.jettonCustomPayload(tonAssetAddressToString(amount.asset.address)); - const transfer = await this.signer({ + const transfer = await this.sign({ to: Address.parse(jettonWalletAddress), bounce: true, amount: JettonEncoder.jettonTransferAmount, @@ -171,7 +187,7 @@ export class LedgerMessageSender { }) => { const { timestamp, seqno, contract } = await this.getTransferParameters(); - const transfer = await this.signer({ + const transfer = await this.sign({ to: Address.parse(nftAddress), bounce: true, amount: nftTransferAmount, @@ -193,32 +209,34 @@ export class LedgerMessageSender { }; tonConnectTransfer = async (transfer: TonConnectTransactionPayload) => { - if (transfer.messages.length !== 1) { - throw new LedgerError('Ledger signer does not support multiple messages'); - } - const { timestamp, seqno, contract } = await this.getTransferParameters(); - const message = transfer.messages[0]; - let transferCell: Cell; + let transferCells: Cell[]; try { - transferCell = await this.signer({ - to: Address.parse(message.address), - bounce: await tonConnectAddressIsBounceable(this.api, message.address), - amount: BigInt(message.amount), - seqno, - timeout: getTTL(timestamp), - sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, - payload: message.payload - ? { - type: 'unsafe', - message: Cell.fromBase64(message.payload) - } - : undefined, - stateInit: toStateInit(message.stateInit) - }); + transferCells = await this.sign( + await Promise.all( + transfer.messages.map(async (message, index) => ({ + to: Address.parse(message.address), + bounce: await tonConnectAddressIsBounceable(this.api, message.address), + amount: BigInt(message.amount), + seqno: seqno + index, + timeout: getTTL(timestamp + index * 60), + sendMode: SendMode.PAY_GAS_SEPARATELY + SendMode.IGNORE_ERRORS, + payload: message.payload + ? { + type: 'unsafe' as const, + message: Cell.fromBase64(message.payload) + } + : undefined, + stateInit: toStateInit(message.stateInit) + })) + ) + ); } catch (e) { console.error(e); + if (e instanceof UserCancelledError) { + throw e; + } throw new LedgerError( typeof e === 'string' ? e @@ -228,7 +246,28 @@ export class LedgerMessageSender { ); } - return this.toSenderObject(externalMessage(contract, seqno, transferCell)); + const externalMessages = transferCells.map((cell, index) => + externalMessage(contract, seqno + index, cell) + ); + + return { + send: async () => { + await new BlockchainApi(this.api.tonApiV2).sendBlockchainMessage({ + sendBlockchainMessageRequest: { + batch: externalMessages.map(message => message.toBoc().toString('base64')) + } + }); + return externalMessages[0]; + }, + estimate: async (): Promise => { + return new WalletMessageSender(this.api, this.wallet, estimationSigner).estimate( + await new TonConnectEncoder(this.api, this.wallet.rawAddress).encodeTransfer({ + ...transfer, + variant: 'standard' + }) + ); + } + }; }; private async getTransferParameters() { diff --git a/packages/core/src/service/tonConnect/connectService.ts b/packages/core/src/service/tonConnect/connectService.ts index f6e0806fc..761e91a63 100644 --- a/packages/core/src/service/tonConnect/connectService.ts +++ b/packages/core/src/service/tonConnect/connectService.ts @@ -388,7 +388,7 @@ export const tonDisconnectRequest = async (options: { storage: IStorage; webView const getMaxMessages = (account: Account) => { if (account.type === 'ledger') { - return 1; + return 4; } const wallet = account.activeTonWallet; diff --git a/packages/locales/src/tonkeeper-web/en.json b/packages/locales/src/tonkeeper-web/en.json index f8b966fa9..00b0b79e9 100644 --- a/packages/locales/src/tonkeeper-web/en.json +++ b/packages/locales/src/tonkeeper-web/en.json @@ -8,6 +8,7 @@ "accounts_manage_folder_name": "Folder Name", "accounts_new_folder": "New Folder", "actionTitle": "Open the wallet", + "activate": "Activate", "add": "Add", "add_dns_address": "Add wallet address that domain %1% will link to.", "add_wallet_existing_multisig_description": "%{number} wallets managed by your wallet list", @@ -57,6 +58,9 @@ "check_words_caption": "To check whether you’ve written down your recovery phrase correctly, please enter the %1%, %2%, and  %3% words.", "close": "Close", "collectibles_empty_header": "Your collectibles will be shown here", + "confirm_disable_two_fa_description": "Disabling 2FA reduces wallet security. Anyone with your recovery phrase could gain full control over your wallet.", + "confirm_disable_two_fa_ok_button": "Send 2FA disable request to your Telegram", + "confirm_disable_two_fa_title": "Disable 2FA?", "confirm_discard_btn_continue_editing": "Continue Editing", "confirm_discard_btn_discard": "Discard Changes", "confirm_discard_description": "You have unsaved changes. If you close this window, your progress will be lost. Do you want to continue?", @@ -107,6 +111,7 @@ "Enable_storing_config": "Enable storing config", "enter_password": "Enter password", "export_dot_csv": "Export .CSV", + "export_trc_20_wallet": "Export TRC20 Wallet", "force_reload": "Force Reload", "help": "Help", "hide": "Hide", @@ -165,6 +170,8 @@ "ledger_operation_not_supported": "The operation is not available for Ledger wallets. Select another wallet and try again.", "ledger_pair_subtitle": "Hardware module, Bluetooth or USB-C, limited TON features", "ledger_pair_title": "Pair with Ledger", + "ledger_steps_confirm_num_tx": "Confirm transaction #{number} on Ledger", + "ledger_steps_confirm_proof": "Confirm action on Ledger", "ledger_steps_confirm_tx": "Confirm your transaction on Ledger", "ledger_steps_connect": "Connect Ledger to your device", "ledger_steps_install_ton": "Install TON App ", @@ -179,6 +186,7 @@ "Manage_wallets": "Manage Wallets", "Minimize": "Minimize", "MinPassword": "Must be at least 6 characters.", + "multichain": "Multichain", "multi_send_about_w5": "About W5", "multi_send_add_more": "Add More", "multisend_confirm_error_insufficient_ton_for_fee": "Wallet balance %balance% is not enough to cover the blockchain fees. Minimum balance required: %required%. Unused TON will be returned to your wallet after the transaction.", @@ -293,6 +301,8 @@ "receive_ton_description": "Send only Toncoin TON and tokens\nin TON network to this address, or you\nmight lose your funds.", "receive_trc20": "Receive USDT TRC20", "receive_trc20_description": "Send only USDT TRC20\nto this address, or you might\nlose your funds.", + "receive_trx": "Receive Tron TRX", + "receive_trx_description": "Send only Tron TRX to this address, or you might lose your funds.", "recipients": "Recipients", "Redo": "Redo", "Reload": "Reload", @@ -372,21 +382,39 @@ "transaction_call_date": "Contract Call %{date}", "transaction_type_mint": "Mint", "transaction_type_purchase": "Purchase", + "tron_account_export_warning_explanation": "This phrase is for TRC20 only. It cannot restore your TON wallet. Use your TON recovery phrase for TON wallet recovery.", + "tron_top_up_trx_button": "Top Up TRX", + "tron_top_up_trx_description": "You need TRX to pay transaction fees. Balance: {balance}", + "tron_top_up_trx_title": "TRX is required for USD₮ TRC20", "try_again": "Try Again", "two_fa_confirm_tg_cannot_access_tg": "Can’t access your Telegram account?", "two_fa_confirm_tg_description": "Go to the @tonkeeper_2fa_bot and tap «Confirm» to complete the transaction.", "two_fa_confirm_tg_title": "Confirm Transaction in Telegram", + "two_fa_long": "Two-Factor Authentication", + "two_fa_recovery_cancelled_toast": "Recovery cancelled. Now transfer your funds to a new wallet for security. ", "two_fa_send_continue_with_tg": "Continue with Telegram", - "two_fa_settings_heading_description": "This experimental feature allows you to enable two-factor authentication (2FA) for added wallet security. It will prompt you for confirmation in Telegram during large transactions and help restore your wallet if you lose your mnemonic. Installation steps:", + "two_fa_settings_cancel_recovery_button": "Cancel Recovery", + "two_fa_settings_change_tg_button": "Change Linked Telegram Account", + "two_fa_settings_disable_button": "Disable 2FA", + "two_fa_settings_heading_active_description": "Your wallet is secured. You can disable two-factor authentication if necessary, but it will cause a decrease of the security.", + "two_fa_settings_heading_active_title": "Two-Factor Authentication Enabled", + "two_fa_settings_heading_description": "This experimental feature allows you to enable two-factor authentication (2FA) for added wallet security. It will prompt you for confirmation in Telegram during transactions. Installation steps:", + "two_fa_settings_heading_recovery_description": "2FA will be disabled on {date}. If you didn't initiate the recovery, cancel it and transfer your funds to a new wallet for security.", + "two_fa_settings_heading_recovery_title": "Wallet Recovery Process Started", "two_fa_settings_heading_title": "Two-Factor Authentication", + "two_fa_settings_reconnect_tg_connection_modal_description": "Scan the QR code or open Telegram to connect a new account. If you have access to your previous account, the change will be instant. Without access, it will take 14 days.", + "two_fa_settings_reconnect_tg_connection_modal_heading": "Connect a New Telegram Account", "two_fa_settings_set_up_deploy_step_button": "Activate 2FA", "two_fa_settings_set_up_deploy_step_description": "Confirm transaction to install 2FA extension", "two_fa_settings_set_up_tg_connection_modal_copy_button": "Copy Link", - "two_fa_settings_set_up_tg_connection_modal_heading": "Scan QR code below with your mobile phone or open Telegram on this device to connect.", + "two_fa_settings_set_up_tg_connection_modal_heading": "Scan the QR code or open Telegram to connect a new account.", "two_fa_settings_set_up_tg_connection_modal_open_button": "Open Telegram", "two_fa_settings_set_up_tg_step_description": "Confirm your connection in your Telegram ", - "two_fa_settings_warning_balance_required": "A TON balance is required to install or uninstall the extension.", + "two_fa_settings_tg_recovery_button": "Link new account", + "two_fa_settings_tg_recovery_text": "Can’t access your Telegram account? Linking a new account will take 14 days.", + "two_fa_settings_warning_balance_required": "0.5 TON is required to install or uninstall 2FA.", "two_fa_settings_warning_battery_gasless": "Battery mode and gasless transactions are not compatible with 2FA.", + "two_fa_settings_warning_can_not_recover": "2FA can't recover your secret phrase.", "two_fa_settings_warning_wallet_will_stop": "The same wallet will stop working on your other devices.", "two_fa_short": "2FA", "txActions_USDT_transfer": "USDT Transfer", @@ -398,6 +426,7 @@ "upload_file": "Upload file", "View": "View", "view_on_tonviewer": "View on Tonviewer", + "wallet_2fa_recovery_started": "Recovery Process Started", "wallet_address": "Wallet address", "wallet_aside_collectibles": "Collectibles", "wallet_aside_domains": "Domains", diff --git a/packages/locales/src/tonkeeper-web/ru-RU.json b/packages/locales/src/tonkeeper-web/ru-RU.json index c98481eff..fe623984b 100644 --- a/packages/locales/src/tonkeeper-web/ru-RU.json +++ b/packages/locales/src/tonkeeper-web/ru-RU.json @@ -8,6 +8,7 @@ "accounts_manage_folder_name": "Имя папки", "accounts_new_folder": "Новая папка", "actionTitle": "Открыть Кошелек", + "activate": "Активировать", "add": "Добавить", "add_dns_address": "Добавьте адрес кошелька, на который будет ссылаться домен %1%", "add_wallet_existing_multisig_description": "%{number} кошельков управляются вашим списком кошельков", @@ -56,6 +57,9 @@ "check_words_caption": "Чтобы убедиться, что вы записали секретный ключ правильно, введите слова %1%, %2% и %3%.", "close": "Закрыть", "collectibles_empty_header": "Здесь будут ваши коллекции", + "confirm_disable_two_fa_description": "Отключение 2FA снижает безопасность кошелька. Любой, кто знает вашу фразу восстановления, может получить полный контроль над вашим кошельком.", + "confirm_disable_two_fa_ok_button": "Отправить запрос на отключение 2FA в ваш Telegram", + "confirm_disable_two_fa_title": "Отключить 2FA?", "confirm_discard_btn_continue_editing": "Продолжить редактирование", "confirm_discard_btn_discard": "Сбросить изменения и выйти", "confirm_discard_description": "У вас есть не сохраненные изменения. Если вы закроете это окно, ваш прогресс будет потерян. Вы хотите продолжить?", @@ -105,6 +109,7 @@ "Enable_storing_config": "Cохранение конфигурации", "enter_password": "Введите пароль", "export_dot_csv": "Экспорт в .CSV", + "export_trc_20_wallet": "Экспортировать TRC20 кошелек", "force_reload": "Принудительная перезагрузка", "help": "Помощь", "hide": "Скрыть", @@ -158,6 +163,8 @@ "ledger_operation_not_supported": "Операция недоступна для кошельков Ledger. Выберите другой кошелек и повторите попытку.", "ledger_pair_subtitle": "Более высокий уровень безопасности благодаря аппаратному кошельку", "ledger_pair_title": "Подключить Ledger", + "ledger_steps_confirm_num_tx": "Подтвердите транзакцию #{number} в Ledger", + "ledger_steps_confirm_proof": "Подтвердите действие в Ledger", "ledger_steps_confirm_tx": "Подтвердите транзакцию в Ledger", "ledger_steps_connect": "Подключите Ledger к своему устройству", "ledger_steps_install_ton": "Установить приложение ", @@ -172,6 +179,7 @@ "Manage_wallets": "Управление кошельками", "Minimize": "Свернуть", "MinPassword": "Должно быть не менее 6 символов.", + "multichain": "Мультичейн", "multi_send_about_w5": "Подробнее о W5", "multi_send_add_more": "Добавить еще", "multisend_confirm_error_insufficient_ton_for_fee": "Баланса кошелька %balance% недостаточно для покрытия комиссий блокчейна. Требуемый минимальный баланс: %required%. Неиспользованный остаток TON после транзакции будет возвращен на ваш кошелек.", @@ -282,6 +290,8 @@ "receive_ton_description": "Отправляйте на этот адрес только Toncoin TON и токены в сети TON, иначе вы можете потерять свои средства.", "receive_trc20": "Получить USDT TRC20", "receive_trc20_description": "Отправляйте на этот адрес только USDT TRC20, иначе вы можете потерять свои средства.", + "receive_trx": "Получить Tron TRX", + "receive_trx_description": "Отправляйте на этот адрес только Tron TRX, иначе вы можете потерять свои средства.", "recipients": "Получатели", "Redo": "Повторить", "Reload": "Перезагрузить", @@ -359,21 +369,39 @@ "transaction_call_date": "Вызов контракта %{date}", "transaction_type_mint": "Создание", "transaction_type_purchase": "Покупка", + "tron_account_export_warning_explanation": "Эта фраза предназначена только для TRC20. Она не может восстановить ваш кошелек TON. Используйте фразу восстановления TON для восстановления кошелька TON.", + "tron_top_up_trx_button": "Пополнить TRX", + "tron_top_up_trx_description": "Вам нужны TRX для оплаты комиссий за транзакции. Баланс: {balance}", + "tron_top_up_trx_title": "TRX требуется для операций с USD₮ TRC20", "try_again": "Повторить", "two_fa_confirm_tg_cannot_access_tg": "Не можете получить доступ к своему Telegram аккаунту?", "two_fa_confirm_tg_description": "Перейдите в @tonkeeper_2fa_bot и нажмите «Подтвердить», чтобы завершить транзакцию.", "two_fa_confirm_tg_title": "Подтвердите транзакцию в Telegram", + "two_fa_long": "Двухфакторная аутентификация", + "two_fa_recovery_cancelled_toast": "Восстановление отменено. Теперь переведите свои средства в новый кошелек для безопасности", "two_fa_send_continue_with_tg": "Продолжить с Telegram", - "two_fa_settings_heading_description": "Эта экспериментальная функция позволяет включить двухфакторную аутентификацию (2FA) для дополнительной безопасности кошелька. Она будет запрашивать подтверждение в Telegram при крупных транзакциях и поможет восстановить ваш кошелек, если вы потеряете мнемоническую фразу. Шаги установки:", + "two_fa_settings_cancel_recovery_button": "Отменить восстановление", + "two_fa_settings_change_tg_button": "Изменить привязанный аккаунт Telegram", + "two_fa_settings_disable_button": "Отключить 2FA", + "two_fa_settings_heading_active_description": "Ваш кошелек защищен. Вы можете отключить двухфакторную аутентификацию при необходимости, но это приведет к снижению уровня безопасности.", + "two_fa_settings_heading_active_title": "Двухфакторная аутентификация включена", + "two_fa_settings_heading_description": "Эта экспериментальная функция позволяет включить двухфакторную аутентификацию (2FA) для дополнительной безопасности кошелька. Она будет запрашивать подтверждение в Telegram во время транзакций. Шаги установки:", + "two_fa_settings_heading_recovery_description": "2FA будет отключена {date}. Если вы не инициировали восстановление, отмените его и переведите свои средства в новый кошелек для безопасности.", + "two_fa_settings_heading_recovery_title": "Процесс восстановления кошелька начат", "two_fa_settings_heading_title": "Двухфакторная аутентификация", + "two_fa_settings_reconnect_tg_connection_modal_description": "Отсканируйте QR-код или откройте Telegram, чтобы подключить новый аккаунт. Если у вас есть доступ к предыдущему аккаунту, изменение произойдет мгновенно. Без доступа это займет 14 дней.", + "two_fa_settings_reconnect_tg_connection_modal_heading": "Подключить новый аккаунт Telegram", "two_fa_settings_set_up_deploy_step_button": "Активировать 2FA", "two_fa_settings_set_up_deploy_step_description": "Подтвердите транзакцию для установки расширения 2FA", "two_fa_settings_set_up_tg_connection_modal_copy_button": "Копировать ссылку", - "two_fa_settings_set_up_tg_connection_modal_heading": "Отсканируйте QR-код ниже с помощью вашего мобильного телефона или откройте Telegram на этом устройстве для подключения.", + "two_fa_settings_set_up_tg_connection_modal_heading": "Отсканируйте QR-код или откройте Telegram, чтобы подключить новый аккаунт.", "two_fa_settings_set_up_tg_connection_modal_open_button": "Открыть Телеграм", "two_fa_settings_set_up_tg_step_description": "Подтвердите соединение в Telegram ", - "two_fa_settings_warning_balance_required": "Для установки или удаления расширения требуется баланс TON.", + "two_fa_settings_tg_recovery_button": "Привязать новый аккаунт", + "two_fa_settings_tg_recovery_text": "Не можете получить доступ к своему Telegram аккаунту? Привязка нового аккаунта займет 14 дней.", + "two_fa_settings_warning_balance_required": "Для установки или удаления расширения требуется 0.5 TON.", "two_fa_settings_warning_battery_gasless": "Батарейка Tonkeeper и безгазовые транзакции не работают с двухфакторной аутентификацией.", + "two_fa_settings_warning_can_not_recover": "2FA не поможет восстановить вашу секретную фразу.", "two_fa_settings_warning_wallet_will_stop": "Этот же кошелек перестанет работать на других ваших устройствах.", "two_fa_short": "2ФА", "txActions_USDT_transfer": "Перевод USDT", @@ -385,6 +413,7 @@ "upload_file": "Загрузить файл", "View": "Вид", "view_on_tonviewer": "Открыть в Tonviewer", + "wallet_2fa_recovery_started": "Процесс сброса запущен", "wallet_address": "Адрес кошелька", "wallet_aside_collectibles": "Коллекции", "wallet_aside_domains": "Домены", diff --git a/packages/uikit/src/components/ConnectLedgerNotification.tsx b/packages/uikit/src/components/ConnectLedgerNotification.tsx index 92fdde018..17e6b5f45 100644 --- a/packages/uikit/src/components/ConnectLedgerNotification.tsx +++ b/packages/uikit/src/components/ConnectLedgerNotification.tsx @@ -36,7 +36,7 @@ const ButtonsBlock = styled.div` `; type LedgerContentLedgerParams = - | { path: number[]; transaction: LedgerTransaction; onSubmit: (result: Cell) => void } + | { path: number[]; transactions: LedgerTransaction[]; onSubmit: (result: Cell[]) => void } | { path: number[]; tonProof: LedgerTonProofRequest; @@ -49,6 +49,7 @@ export const LedgerContent: FC<{ }> = ({ ledgerParams, onClose }) => { const { t } = useTranslation(); const [isCompleted, setIsCompleted] = useState(false); + const [currentTxToConfirmIndex, setCurrentTxToConfirmIndex] = useState(0); const { mutateAsync: connectLedger, @@ -62,21 +63,25 @@ export const LedgerContent: FC<{ try { const transport = await connectLedger(); try { - if ('transaction' in ledgerParams) { - const val = await transport.signTransaction( - ledgerParams.path, - ledgerParams.transaction - ); - setIsCompleted(true); - setTimeout(() => ledgerParams.onSubmit(val), 500); - } else { + if ('tonProof' in ledgerParams) { const val = await transport.getAddressProof( ledgerParams.path, ledgerParams.tonProof ); setIsCompleted(true); setTimeout(() => ledgerParams.onSubmit(val), 500); + return; } + + const result: Cell[] = []; + for (const transaction of ledgerParams.transactions) { + const val = await transport.signTransaction(ledgerParams.path, transaction); + result.push(val); + setCurrentTxToConfirmIndex(i => i + 1); + } + + setIsCompleted(true); + setTimeout(() => ledgerParams.onSubmit(result), 500); } catch (e) { console.error(e); if ( @@ -115,9 +120,20 @@ export const LedgerContent: FC<{ currentStep = 'all-completed'; } + const connectionStepsProps = + 'transactions' in ledgerParams + ? { + transactionsToSign: ledgerParams.transactions.length, + signingTransactionIndex: currentTxToConfirmIndex, + action: 'transaction' as const + } + : { + action: 'ton-proof' as const + }; + return ( - +