I’m trying to deploy and install a subscription plugin in a TON Wallet v4r2 using the @tonconnect/ui-react library. I’ve set up the payload with opcode 1 and included the necessary contract initialization (StateInit). However, the deployment isn’t working as expected:
- The TON amount sent with the transaction is returned to the wallet.
- The subscription plugin isn’t deployed or installed.
Steps Taken:
- Configured the subscription contract with the required parameters.
- Compiled the official subscription plugin code using
@ton-community/func-js and used result.codeBoc for StateInit.code. Compilation code image
- Sent the transaction using
tonConnectUI.sendTransaction.
Here’s the code I used:
import {
useTonWallet,
useTonAddress,
useTonConnectUI,
} from "@tonconnect/ui-react";
import { Address, beginCell, Cell, toNano } from "@ton/core";
export default function App() {
const wallet = useTonWallet();
const walletAddress = useTonAddress(true);
const [tonConnectUI] = useTonConnectUI();
const deployAndInstallSubscription = async () => {
if (!walletAddress) return null;
const BENEFICIARY = "EQB_tUOR3CwzEsHlq2aDrLF6R6Y_FV70GBldzfBwa8Hyq042";
// Configuration for the subscription contract
const config = {
wallet: Address.parse(walletAddress),
beneficiary: Address.parse(BENEFICIARY),
amount: toNano(0.1),
period: 120, // 2 minutes
startTime: 120,
timeout: 3600, // 1 hour
last_payment_time: 0,
last_request_time: 0,
failed_attempts: 0,
subscription_id: 1,
};
// StateInit: Code and Data for the contract
const stateInit = {
code: Cell.fromBase64(
"te6ccgECDwEAAmIAART/APSkE/S88sgLAQIBIAIDAgFIBAUDavIw2zxTNaEnqQT4IyehKKkEAbxRNaD4I7kTsPKe+AByUhC+lFOH8AeOhVOG2zyk4vgjAts8CwwNAgLNBgcBIaDQybZ4E/SI3gQR9IjeBBATCwSP1tngXoaYGY/SAYKYRjgsdOL4QZmemPmEEIMjm6OV1JeAPwGLhBCDq3NbvtnnAphOOC2cdGiEYvhjhBCDq3NbvtnnAVa6TgkECwoKCAJp8Q/SIYQJOIbZ58EsEIMjm6OThACGRlgqgDZ4soAf0BCmW1ZY+JZZ/kuf2AP8EIMjm6OW2eQOCgTwjo0QjF8McIIQdW5rd9s84ArTHzCCEHBsdWeDHrFSELqPSDBTJKEmqQT4IyahJ6kEvvJxCfpEMKYZ+DPQeNch1ws/UmChG76OkjA2+CNwcIIQc3VicydZ2zxQd94QaRBYEEcQNkUTUELbPOA5XwdsIjKCEGRzdHK6CgoNCQEajol/ghBkc3Ry2zzgMAoAaCGzmYIQBAAAAHL7At5w+CdvEYAQyMsFUAXPFiH6AhT0ABPLaRLLH4MGApSBAKAy3skB+wAAMO1E0PpA+kD6ANMf0x/TH9Mf0x/TB9MfMAGAIfpEMCCBOpjbPAGmGfgz0HjXIdcLP6Bw+CWCEHBsdWcigBjIywVQB88WUAT6AhXLahLLHxPLPwH6AssAyXP7AA4AQMhQCs8WUAjPFlAG+gIUyx8Syx/LH8sfyx/LB8sfye1UAFgBphX4M9Ag1wsHgQDRupWBAIjXId7TByGBAN26AoEA3roSsfLgR9M/MKirDw=="
),
data: beginCell()
.storeAddress(config.wallet)
.storeAddress(config.beneficiary)
.storeCoins(config.amount)
.storeUint(config.period, 32)
.storeUint(config.startTime, 32)
.storeUint(config.timeout, 32)
.storeUint(config.last_payment_time, 32)
.storeUint(config.last_request_time, 32)
.storeUint(config.failed_attempts, 8)
.storeUint(config.subscription_id, 32)
.endCell(),
};
// Build payload
const payload = beginCell()
.storeUint(1, 8) // op code
.storeUint(0, 8) // plugin wc
.storeCoins(toNano(0.1))
.storeRef(stateInit.code)
.storeRef(stateInit.data)
.endCell();
// Send transaction with the payload
await tonConnectUI.sendTransaction({
validUntil: Date.now() + 5 * 60 * 1000, // Valid for 5 minutes
messages: [
{
address: walletAddress,
amount: toNano(0.1).toString(),
payload: payload.toBoc().toString("base64"),
},
],
});
};
return (
<div>
<h1>TON Web Subscription</h1>
{wallet ? (
<div>
<p>Wallet connected: {wallet?.account.address}</p>
<button onClick={deployAndInstallSubscription}>
Deploy And Install Subscription
</button>
</div>
) : (
<p>Please connect your TON wallet.</p>
)}
</div>
);
}
Observed Issue
After inspecting the transaction, I found that the message is sent as an internal message, not an external message. As a result, the plugin isn’t installed or deployed.
Transaction details:
What I Found:
The TonWeb repository includes an example of deploying and installing a subscription plugin using the secret key. Here’s the example code:
import TonWeb from "tonweb";
import { SubscriptionContract } from "tonweb/src/contract/subscription/index";
import { mnemonicToPrivateKey } from "@ton/crypto";
const mnemonicArray = [
"whale",
"airport",
"runway",
"scissors",
"pony",
"elite",
"false",
"laugh",
"purse",
"letter",
"gloom",
"cousin",
"never",
"chaos",
"maid",
"air",
"body",
"candy",
"amused",
"service",
"hammer",
"kingdom",
"ability",
"vanish",
];
export default async function main() {
const BENEFICIARY = "EQB_tUOR3CwzEsHlq2aDrLF6R6Y_FV70GBldzfBwa8Hyq042";
// Create testnet tonweb
const tonweb = new TonWeb(
new TonWeb.HttpProvider("https://testnet.toncenter.com/api/v2/jsonRPC")
);
const keyPair = await mnemonicToPrivateKey(mnemonicArray);
console.log(keyPair.publicKey);
const WalletClass = tonweb.wallet.all["v4R2"];
console.log(`===keyPair.publicKey===`, keyPair.publicKey);
const wallet = new WalletClass(tonweb.provider, {
publicKey: keyPair.publicKey,
wc: 0,
});
// Get wallet address
const walletAddress = await wallet.getAddress();
const WALLET_ADDRESS = walletAddress.toString(true, true, true);
console.log("wallet address =", WALLET_ADDRESS);
// wallet seqno get method
const seqno = (await wallet.methods.seqno().call()) || 0;
console.log("wallet seqno = ", seqno);
const subscription = new SubscriptionContract(tonweb.provider, {
wc: 0,
wallet: walletAddress,
beneficiary: new TonWeb.utils.Address(BENEFICIARY),
amount: TonWeb.utils.toNano("0.2"), // 1 ton
startAt: 0,
period: 2 * 60, // 2 min,
timeout: 30, // 30 sec
subscriptionId: 1210,
});
console.log(`===subscription===`, subscription);
const subscriptionAddress = await subscription.getAddress();
console.log(
"subscription address=",
subscriptionAddress.toString(true, true, true)
);
console.log(
`===await subscription.createStateInit();===`,
await subscription.createStateInit()
);
// Deploy and install subscription
const deployAndInstallPlugin = async () => {
console.log(
"deployAndInstallPlugin",
await wallet.methods
.deployAndInstallPlugin({
secretKey: keyPair.secretKey,
seqno: seqno,
pluginWc: 0,
amount: TonWeb.utils.toNano("0.05"), // 1 ton
stateInit: (await subscription.createStateInit()).stateInit,
body: subscription.createBody(),
})
.send()
);
};
console.log(
`===await subscription.createStateInit();===`,
await subscription.createStateInit()
);
await deployAndInstallPlugin();
}
I have not found anything helpful that can describe the process how to install the official subscription plugin in wallet v4r2 contract from webapp.
Can someone explain how to install and deploy the official subscription plugin using Tonconnect? So, my webapp users can install a subscription plugin in their wallet V4r2 without requiring the secret key. User will not agree to share their secret key. User will sign the message from user wallet.
Questions:
- How can I ensure the transaction is sent as an
external message with TonConnect?
- Is there a specific method or configuration required in tonConnectUI.sendTransaction to deploy and install the plugin in Wallet v4r2?
Expected Behavior
The deployAndInstallSubscription function should deploy the subscription and install the plugin in Wallet v4r2.
Actual Behavior
The TON amount sent with the transaction is returned to the wallet without deploying or installing the plugin.
Note
I’ve reviewed the TonConnect and TonWeb documentation but haven’t found any specific example or explanation for deploying plugins directly through a web app.