Skip to content

Commit

Permalink
feature(mobile): Top up relayer with zero TON balance (#803)
Browse files Browse the repository at this point in the history
* feature(mobile): Top up relayer with zero TON balance

* fixes

* fix(mobile): Ignore SKErrorDomain 2
  • Loading branch information
voloshinskii authored Apr 18, 2024
1 parent 1a98b87 commit 81fe539
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 28 deletions.
10 changes: 10 additions & 0 deletions packages/@core-js/src/BatteryAPI/BatteryGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;
}
Expand Down
15 changes: 12 additions & 3 deletions packages/mobile/src/blockchain/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -328,9 +329,15 @@ export class TonWallet {
private async calcFee(
boc: string,
params?,
withRelayer = true,
withRelayer = false,
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];
}

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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({
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export const SignRawModal = memo<SignRawModalProps>((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,
Expand Down
2 changes: 1 addition & 1 deletion packages/mobile/src/core/NFTSend/NFTSend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ export const NFTSend: FC<Props> = (props) => {
throw new CanceledActionError();
}

const excessesAccount = isBattery && (await wallet.battery.getExcessesAccount());
const excessesAccount = isBattery && tk.wallet.battery.excessesAccount;

const nftTransferMessages = [
internal({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
3 changes: 3 additions & 0 deletions packages/mobile/src/wallet/Wallet/WalletContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export class WalletContent extends WalletBase {
this.tonProof,
this.batteryapi,
this.storage,
this.isTestnet,
);
this.cards = new CardsManager(
this.persistPath,
Expand Down Expand Up @@ -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(),
]);
Expand All @@ -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(),
]);
Expand Down
45 changes: 36 additions & 9 deletions packages/mobile/src/wallet/managers/BatteryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -15,6 +16,8 @@ export interface BatteryState {
isLoading: boolean;
balance?: string;
reservedBalance?: string;
excessesAccount?: string;
fundReceiver?: string;
supportedTransactions: Record<BatterySupportedTransaction, boolean>;
}

Expand All @@ -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 }) => ({
Expand All @@ -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) {
Expand All @@ -68,30 +80,31 @@ 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 });
return null;
}
}

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;
return this.state.set({
excessesAccount: data.excess_account,
fundReceiver: data.fund_receiver,
});
} catch (err) {
return null;
this.logger.error(err);
}
}

Expand Down Expand Up @@ -220,21 +233,35 @@ export class BatteryManager {
}
}

public async emulate(boc: string): Promise<MessageConsequences> {
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);
}
}
Expand Down
8 changes: 6 additions & 2 deletions packages/shared/components/RefillBattery/RefillBatteryIAP.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,13 @@ export const RefillBatteryIAP = memo(() => {
Toast.success(t('battery.refilled'));
setPurchaseInProgress(false);
} catch (e) {
console.log(e);
setPurchaseInProgress(false);
Toast.fail(e.message);

// SKErrorDomain, code=2 - user cancelled. Ignore this error
const regEx = /SKErrorDomain,.*2/;
if (!regEx.test(e.message)) {
Toast.fail(e.message);
}
}
},
[],
Expand Down
24 changes: 13 additions & 11 deletions packages/shared/utils/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 ||
Expand All @@ -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,
Expand Down

0 comments on commit 81fe539

Please sign in to comment.