diff --git a/README.md b/README.md index 18daf00a..cd127173 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,6 @@ type SettingsProps = { mapWalletListFn?: Parameters[0]["mapWalletListFn"]; customTranslations?: RecursivePartial; tokensForEnabledYieldsOnly?: boolean; - preferredTransactionFormat?: TransactionFormat; validatorsConfig?: { [Key in SupportedSKChains]?: { allowed?: string[]; diff --git a/packages/widget/src/domain/types/settings.ts b/packages/widget/src/domain/types/settings.ts deleted file mode 100644 index 79cb9c8e..00000000 --- a/packages/widget/src/domain/types/settings.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { TransactionFormat } from "../../generated/api/legacy"; - -export type { TransactionFormat }; diff --git a/packages/widget/src/domain/types/transaction.ts b/packages/widget/src/domain/types/transaction.ts index 02439c76..9f25d561 100644 --- a/packages/widget/src/domain/types/transaction.ts +++ b/packages/widget/src/domain/types/transaction.ts @@ -1,3 +1,8 @@ +import { + Cell, + type CommonMessageInfoRelaxedInternal, + loadMessageRelaxed, +} from "@ton/core"; import type { GetType } from "purify-ts"; import { array, @@ -185,6 +190,30 @@ export const unsignedTonTransactionCodec = oneOf([ export type DecodedTonTransaction = GetType; +type DecodedTonRawTransaction = Extract< + DecodedTonTransaction, + ReadonlyArray +>; + +export const normalizeTonTransactionToRaw = ( + tx: DecodedTonTransaction +): DecodedTonRawTransaction => { + if (Array.isArray(tx)) { + return tx; + } + + const parsedTx = loadMessageRelaxed(Cell.fromBase64(tx.message).beginParse()); + const info = parsedTx.info as CommonMessageInfoRelaxedInternal; + + return [ + { + address: info.dest.toString(), + amount: info.value.coins.toString(), + payload: parsedTx.body.toBoc().toString("base64"), + }, + ]; +}; + export const substratePayloadCodec = Codec.interface({ tx: Codec.interface({ address: string, diff --git a/packages/widget/src/providers/settings/types.ts b/packages/widget/src/providers/settings/types.ts index a403a5b6..3710e911 100644 --- a/packages/widget/src/providers/settings/types.ts +++ b/packages/widget/src/providers/settings/types.ts @@ -4,7 +4,6 @@ import type { SupportedSKChainIds, SupportedSKChains, } from "../../domain/types/chains"; -import type { TransactionFormat } from "../../domain/types/settings"; import type { PreferredTokenYieldsPerNetwork } from "../../domain/types/stake"; import type { TokenDto } from "../../domain/types/tokens"; import type { SKExternalProviders } from "../../domain/types/wallets"; @@ -70,7 +69,6 @@ export type SettingsProps = { mapWalletListFn?: (val: WalletList) => WalletList; customTranslations?: RecursivePartial; tokensForEnabledYieldsOnly?: boolean; - preferredTransactionFormat?: TransactionFormat; validatorsConfig?: { [Key in SupportedSKChains | "*"]?: { allowed?: string[]; diff --git a/packages/widget/src/providers/sk-wallet/index.tsx b/packages/widget/src/providers/sk-wallet/index.tsx index ce9788a6..ae654d35 100644 --- a/packages/widget/src/providers/sk-wallet/index.tsx +++ b/packages/widget/src/providers/sk-wallet/index.tsx @@ -29,6 +29,7 @@ import { ExternalProviderError } from "../../domain/types/external-providers"; import { decodeAndPrepareEvmTransaction, normalizeSolanaTransactionToHex, + normalizeTonTransactionToRaw, substratePayloadCodec, unsignedEVMTransactionCodec, unsignedSolanaTransactionCodec, @@ -310,6 +311,11 @@ export const SKWalletProvider = ({ children }: PropsWithChildren) => { return Either.encase(() => JSON.parse(tx)) .mapLeft(() => "Failed to parse tx") .chain((val) => unsignedTonTransactionCodec.decode(val)) + .chain((val) => + Either.encase(() => + normalizeTonTransactionToRaw(val) + ).mapLeft(() => "Failed to normalize TON tx") + ) .map((v) => ({ type: "ton", tx: v })); } diff --git a/packages/widget/tests/use-cases/sk-wallet.test.tsx b/packages/widget/tests/use-cases/sk-wallet.test.tsx index d2b0711a..491a5b0b 100644 --- a/packages/widget/tests/use-cases/sk-wallet.test.tsx +++ b/packages/widget/tests/use-cases/sk-wallet.test.tsx @@ -1,3 +1,10 @@ +import { + Address, + beginCell, + type CommonMessageInfoRelaxedInternal, + internal, + storeMessageRelaxed, +} from "@ton/core"; import { delay, HttpResponse, http } from "msw"; import { solana, ton } from "../../src/domain/types/chains/misc"; import { MiscNetworks } from "../../src/domain/types/chains/networks"; @@ -16,12 +23,15 @@ import { describe, expect, it, vi } from "../utils/test-extend"; import { renderHook } from "../utils/test-utils"; const renderHookWithExternalProvider = ( - externalProviders: SKExternalProviders + externalProviders: SKExternalProviders, + options: { + variant?: "default" | "utila"; + } = {} ) => renderHook(useSKWallet, { wrapper: ({ children }) => ( @@ -58,6 +68,53 @@ const createSolanaTxMeta = (): SKTxMeta => ({ providersDetails: [], }); +const createTonTxMeta = (): SKTxMeta => ({ + txId: "", + actionId: "", + actionType: "STAKE", + txType: "APPROVAL", + amount: "100", + inputToken: { + address: "", + decimals: 0, + symbol: "", + name: "", + network: "ton", + }, + structuredTransaction: null, + annotatedTransaction: null, + providersDetails: [], +}); + +const createDefaultTonTransactionFixture = () => { + const message = internal({ + to: Address.parseRaw( + "0:0000000000000000000000000000000000000000000000000000000000000000" + ), + value: 123n, + body: "Deposit", + }); + const info = message.info as CommonMessageInfoRelaxedInternal; + + return { + tx: JSON.stringify({ + seqno: 0, + message: beginCell() + .store(storeMessageRelaxed(message)) + .endCell() + .toBoc() + .toString("base64"), + }), + rawTx: [ + { + address: info.dest.toString(), + amount: info.value.coins.toString(), + payload: message.body.toBoc().toString("base64"), + }, + ], + }; +}; + describe("SK Wallet", () => { it("should work with solana external provider", async ({ worker }) => { const switchChainSpy = vi.fn(async (_: number) => {}); @@ -238,26 +295,11 @@ describe("SK Wallet", () => { }); await expect.poll(() => tonWallet.result.current.isConnected).toBe(true); + const tonFixture = createDefaultTonTransactionFixture(); const tonRes = await tonWallet.result.current.signTransaction({ network: "ton", - tx: JSON.stringify({ seqno: 0, message: "12345" }), - txMeta: { - txId: "", - actionId: "", - actionType: "STAKE", - txType: "APPROVAL", - amount: "100", - inputToken: { - address: "", - decimals: 0, - symbol: "", - name: "", - network: "ton", - }, - structuredTransaction: null, - annotatedTransaction: null, - providersDetails: [], - }, + tx: tonFixture.tx, + txMeta: createTonTxMeta(), ledgerHwAppId: null, }); @@ -268,25 +310,62 @@ describe("SK Wallet", () => { expect(sendTransactionSpy).toHaveBeenCalledWith( { type: "ton", - tx: { seqno: 0n, message: "12345" }, + tx: tonFixture.rawTx, }, + createTonTxMeta() + ); + }); + + it("keeps raw ton transactions unchanged for external provider", async ({ + worker, + }) => { + const switchChainSpy = vi.fn(async (_: number) => {}); + const sendTransactionSpy = vi.fn(async (_: unknown) => "hash"); + const rawTx = [ { - txId: "", - actionId: "", - actionType: "STAKE", - txType: "APPROVAL", - amount: "100", - inputToken: { - address: "", - decimals: 0, - symbol: "", - name: "", - network: "ton", - }, - structuredTransaction: null, - annotatedTransaction: null, - providersDetails: [], - } + address: "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c", + amount: "123", + payload: "te6cckEBAQEAAgAAAA==", + }, + ]; + + worker.use( + http.get(legacyApiRoute("/v1/yields/enabled/networks"), async () => { + await delay(); + return HttpResponse.json([MiscNetworks.Ton]); + }) + ); + + const tonWallet = await renderHookWithExternalProvider({ + type: "generic", + currentAddress: "UQDyiNAyPy8QRQy45-SjxzrbKVOTOVyXaVGPZSLI9jxHF_Sy", + currentChain: ton.id, + supportedChainIds: [ton.id], + provider: { + signMessage: async () => "hash", + switchChain: switchChainSpy, + sendTransaction: sendTransactionSpy, + }, + }); + await expect.poll(() => tonWallet.result.current.isConnected).toBe(true); + + const tonRes = await tonWallet.result.current.signTransaction({ + network: "ton", + tx: JSON.stringify(rawTx), + txMeta: createTonTxMeta(), + ledgerHwAppId: null, + }); + + expect(tonRes.extract()).toEqual({ + signedTx: "hash", + broadcasted: true, + }); + expect(sendTransactionSpy).toHaveBeenCalledWith( + { + type: "ton", + tx: rawTx, + }, + createTonTxMeta() ); }); });