diff --git a/packages/@core-js/src/BatteryAPI/BatteryGenerated.ts b/packages/@core-js/src/BatteryAPI/BatteryGenerated.ts index 02ee15352..23d039035 100644 --- a/packages/@core-js/src/BatteryAPI/BatteryGenerated.ts +++ b/packages/@core-js/src/BatteryAPI/BatteryGenerated.ts @@ -21,6 +21,11 @@ export interface Status { } export interface Config { + /** + * with zero balance it is possible to transfer some jettons (stablecoins, jusdt, etc) to this address to refill the balance. Such transfers would be paid by Battery Service. + * @example "0:07331e629e39d006d86a8cc7659c10a97c671f7535dc8b7f251a1a944dda348e" + */ + fund_receiver: string; /** * when building a message to transfer an NFT or Jetton, use this address to send excess funds back to Battery Service. * @example "0:da6b1b6663a0e4d18cc8574ccd9db5296e367dd9324706f3bbd9eb1cd2caf0bf" @@ -31,6 +36,11 @@ export interface Config { export interface Balance { /** @example "10.250" */ balance: string; + /** + * reserved amount in units (TON/USD) + * @example "0.3" + */ + reserved: string; /** @example "usd" */ units: BalanceUnitsEnum; } diff --git a/packages/mobile/src/blockchain/wallet.ts b/packages/mobile/src/blockchain/wallet.ts index 9a81655b4..773f3895e 100644 --- a/packages/mobile/src/blockchain/wallet.ts +++ b/packages/mobile/src/blockchain/wallet.ts @@ -38,6 +38,7 @@ import { createTonApiInstance } from '$wallet/utils'; import { config } from '$config'; import { toNano } from '$utils'; import { BatterySupportedTransaction } from '$wallet/managers/BatteryManager'; +import { compareAddresses } from '$utils/address'; const TonWeb = require('tonweb'); @@ -329,8 +330,14 @@ export class TonWallet { boc: string, params?, withRelayer = true, + forceRelayer = false, ): Promise<[BigNumber, boolean]> { - const { emulateResult, battery } = await emulateBoc(boc, params, withRelayer); + const { emulateResult, battery } = await emulateBoc( + boc, + params, + withRelayer, + forceRelayer, + ); return [new BigNumber(emulateResult.event.extra).multipliedBy(-1), battery]; } @@ -427,6 +434,7 @@ export class TonWallet { tk.wallet.battery.state.data.supportedTransactions[ BatterySupportedTransaction.Jetton ], + compareAddresses(address, tk.wallet.battery.fundReceiver), ); return [Ton.fromNano(feeNano.toString()), isBattery]; @@ -473,7 +481,7 @@ export class TonWallet { } const excessesAccount = sendWithBattery - ? await tk.wallet.battery.getExcessesAccount() + ? tk.wallet.battery.excessesAccount : tk.wallet.address.ton.raw; const boc = this.createJettonTransfer({ @@ -497,6 +505,7 @@ export class TonWallet { tk.wallet.battery.state.data.supportedTransactions[ BatterySupportedTransaction.Jetton ], + compareAddresses(address, tk.wallet.battery.fundReceiver), ); feeNano = fee; isBattery = battery; @@ -632,7 +641,7 @@ export class TonWallet { ? AddressFormatter.isBounceable(address) : false, }); - const [feeNano, isBattery] = await this.calcFee(boc); + const [feeNano, isBattery] = await this.calcFee(boc, undefined, true); return [Ton.fromNano(feeNano.toString()), isBattery]; } diff --git a/packages/mobile/src/core/ModalContainer/NFTOperations/Modals/SignRawModal.tsx b/packages/mobile/src/core/ModalContainer/NFTOperations/Modals/SignRawModal.tsx index 9fd3aa107..6552d6b32 100644 --- a/packages/mobile/src/core/ModalContainer/NFTOperations/Modals/SignRawModal.tsx +++ b/packages/mobile/src/core/ModalContainer/NFTOperations/Modals/SignRawModal.tsx @@ -118,7 +118,7 @@ export const SignRawModal = memo((props) => { const boc = TransactionService.createTransfer(contract, { messages: TransactionService.parseSignRawMessages( params.messages, - isBattery ? await tk.wallet.battery.getExcessesAccount() : undefined, + isBattery ? tk.wallet.battery.excessesAccount : undefined, ), seqno: await getWalletSeqno(wallet), sendMode: 3, diff --git a/packages/mobile/src/core/NFTSend/NFTSend.tsx b/packages/mobile/src/core/NFTSend/NFTSend.tsx index 79ae333fc..ba62d509b 100644 --- a/packages/mobile/src/core/NFTSend/NFTSend.tsx +++ b/packages/mobile/src/core/NFTSend/NFTSend.tsx @@ -291,7 +291,7 @@ export const NFTSend: FC = (props) => { throw new CanceledActionError(); } - const excessesAccount = isBattery && (await wallet.battery.getExcessesAccount()); + const excessesAccount = isBattery && tk.wallet.battery.excessesAccount; const nftTransferMessages = [ internal({ diff --git a/packages/mobile/src/navigation/hooks/useDeeplinkingResolvers.ts b/packages/mobile/src/navigation/hooks/useDeeplinkingResolvers.ts index fa75b385b..2c04baa8b 100644 --- a/packages/mobile/src/navigation/hooks/useDeeplinkingResolvers.ts +++ b/packages/mobile/src/navigation/hooks/useDeeplinkingResolvers.ts @@ -471,7 +471,7 @@ export function useDeeplinkingResolvers() { const excessesAccount = !config.get('disable_battery_send') && tk.wallet.battery.state.data.balance !== '0' - ? await tk.wallet.battery.getExcessesAccount() + ? tk.wallet.battery.excessesAccount : null; await openSignRawModal( diff --git a/packages/mobile/src/wallet/Wallet/WalletContent.ts b/packages/mobile/src/wallet/Wallet/WalletContent.ts index 8a976bd2f..3918274a8 100644 --- a/packages/mobile/src/wallet/Wallet/WalletContent.ts +++ b/packages/mobile/src/wallet/Wallet/WalletContent.ts @@ -108,6 +108,7 @@ export class WalletContent extends WalletBase { this.tonProof, this.batteryapi, this.storage, + this.isTestnet, ); this.cards = new CardsManager( this.persistPath, @@ -171,6 +172,7 @@ export class WalletContent extends WalletBase { this.staking.load(), this.subscriptions.load(), this.battery.load(), + this.battery.loadBatteryConfig(), this.activityList.load(), this.cards.load(), ]); @@ -185,6 +187,7 @@ export class WalletContent extends WalletBase { this.staking.reload(), this.subscriptions.reload(), this.battery.load(), + this.battery.loadBatteryConfig(), this.activityList.reload(), this.cards.load(), ]); diff --git a/packages/mobile/src/wallet/managers/BatteryManager.ts b/packages/mobile/src/wallet/managers/BatteryManager.ts index 3d408e64d..d71cf444d 100644 --- a/packages/mobile/src/wallet/managers/BatteryManager.ts +++ b/packages/mobile/src/wallet/managers/BatteryManager.ts @@ -4,6 +4,7 @@ import { Storage } from '@tonkeeper/core/src/declarations/Storage'; import { State } from '@tonkeeper/core/src/utils/State'; import { TonProofManager } from '$wallet/managers/TonProofManager'; import { logger, NamespacedLogger } from '$logger'; +import { config } from '$config'; export enum BatterySupportedTransaction { Swap = 'swap', @@ -15,6 +16,8 @@ export interface BatteryState { isLoading: boolean; balance?: string; reservedBalance?: string; + excessesAccount?: string; + fundReceiver?: string; supportedTransactions: Record; } @@ -37,6 +40,7 @@ export class BatteryManager { private tonProof: TonProofManager, private batteryapi: BatteryAPI, private storage: Storage, + private isTestnet: boolean, ) { this.state.persist({ partialize: ({ balance, reservedBalance, supportedTransactions }) => ({ @@ -50,6 +54,14 @@ export class BatteryManager { this.logger = logger.extend('BatteryManager'); } + get excessesAccount() { + return this.state.data.excessesAccount; + } + + get fundReceiver() { + return this.state.data.fundReceiver; + } + public async fetchBalance() { try { if (!this.tonProof.tonProofToken) { @@ -68,8 +80,7 @@ export class BatteryManager { this.state.set({ isLoading: false, balance: data.balance, - // @ts-expect-error reservedAmount will be implemented in API later. Remove then - reservedBalance: data.reservedBalance ?? '0', + reservedBalance: data.reserved ?? '0', }); } catch (err) { this.state.set({ isLoading: false }); @@ -77,21 +88,23 @@ export class BatteryManager { } } - public async getExcessesAccount() { + public async loadBatteryConfig() { try { if (!this.tonProof.tonProofToken) { throw new Error('No proof token'); } - const data = await this.batteryapi.getConfig({ headers: { 'X-TonConnect-Auth': this.tonProof.tonProofToken, }, }); - - return data.excess_account; + console.log(data); + return this.state.set({ + excessesAccount: data.excess_account, + fundReceiver: data.fund_receiver, + }); } catch (err) { - return null; + this.logger.error(err); } } @@ -220,21 +233,35 @@ export class BatteryManager { } } - public async emulate(boc: string): Promise { + public async emulate( + boc: string, + ): Promise<{ consequences: MessageConsequences; withBattery: boolean }> { try { if (!this.tonProof.tonProofToken) { throw new Error('No proof token'); } - return await this.batteryapi.emulate.emulateMessageToWallet( - { boc }, + const res = await fetch( + `${config.get('batteryHost', this.isTestnet)}/wallet/emulate`, { + method: 'POST', + body: JSON.stringify({ boc }), headers: { + 'Content-Type': 'application/json', 'X-TonConnect-Auth': this.tonProof.tonProofToken, }, }, ); + + const data: MessageConsequences = await res.json(); + + const withBattery = + res.headers.get('supported-by-battery') === 'true' && + res.headers.get('allowed-by-battery') === 'true'; + + return { consequences: data, withBattery }; } catch (err) { + console.log(err); throw new Error(err); } } diff --git a/packages/shared/utils/blockchain.ts b/packages/shared/utils/blockchain.ts index f8de4b655..342843c02 100644 --- a/packages/shared/utils/blockchain.ts +++ b/packages/shared/utils/blockchain.ts @@ -22,12 +22,7 @@ export async function sendBoc(boc, attemptWithRelayer = true) { ) { throw new Error('Battery disabled'); } - if ( - !tk.wallet.battery?.state?.data?.balance || - tk.wallet.battery.state.data.balance === '0' - ) { - throw new Error('Zero balance'); - } + return await tk.wallet.battery.sendMessage(boc); } catch (err) { return await tk.wallet.tonapi.blockchain.sendBlockchainMessage( @@ -39,7 +34,12 @@ export async function sendBoc(boc, attemptWithRelayer = true) { } } -export async function emulateBoc(boc, params?, attemptWithRelayer = false) { +export async function emulateBoc( + boc, + params?, + attemptWithRelayer = false, + forceRelayer = false, +) { try { if ( !attemptWithRelayer || @@ -48,14 +48,16 @@ export async function emulateBoc(boc, params?, attemptWithRelayer = false) { ) { throw new Error('Battery disabled'); } + if ( - !tk.wallet.battery?.state?.data?.balance || - tk.wallet.battery.state.data.balance === '0' + !forceRelayer && + (!tk.wallet.battery?.state?.data?.balance || + tk.wallet.battery.state.data.balance === '0') ) { throw new Error('Zero balance'); } - const emulateResult = await tk.wallet.battery.emulate(boc); - return { emulateResult, battery: true }; + const { consequences, withBattery } = await tk.wallet.battery.emulate(boc); + return { emulateResult: consequences, battery: withBattery }; } catch (err) { const emulateResult = await tk.wallet.tonapi.wallet.emulateMessageToWallet({ boc,