Skip to content

Commit 4adb41f

Browse files
committed
feat(ui): enhance SubscriptionForm with extension address parsing and cancel functionality improvements
1 parent 57e0e96 commit 4adb41f

File tree

4 files changed

+155
-73
lines changed

4 files changed

+155
-73
lines changed

apps/demo-dapp-with-react-ui/src/components/SubscriptionForm/SubscriptionForm.tsx

Lines changed: 140 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,32 @@ import {
99
useTonConnectUI,
1010
useTonWallet
1111
} from '@tonconnect/ui-react';
12+
import { Cell, loadMessage } from '@ton/core';
13+
14+
/**
15+
* Parse extension address from BOC (external message)
16+
* @param boc - Base64 encoded BOC string
17+
* @returns Extension address in user-friendly format or error message
18+
*/
19+
function parseExtensionAddressFromBoc(boc: string): string {
20+
try {
21+
const slice = Cell.fromBase64(boc).beginParse();
22+
const message = loadMessage(slice);
23+
24+
// Extract destination address from message info
25+
if (message.info.type === 'external-out') {
26+
return message.info.dest?.toString() || 'No destination address found';
27+
} else if (message.info.type === 'internal') {
28+
return message.info.dest.toString();
29+
} else if (message.info.type === 'external-in') {
30+
return 'External-in message (no destination)';
31+
}
32+
33+
return 'Unknown message type';
34+
} catch (error) {
35+
return `Error parsing BOC: ${error instanceof Error ? error.message : String(error)}`;
36+
}
37+
}
1238

1339
const baseSubscriptionPayload: CreateSubscriptionV2Request = {
1440
validUntil: Math.floor(Date.now() / 1000) + 600, // 10 minutes from now
@@ -32,23 +58,35 @@ const baseSubscriptionPayload: CreateSubscriptionV2Request = {
3258
}
3359
};
3460

61+
const baseCancelPayload: CancelSubscriptionV2Request = {
62+
validUntil: Math.floor(Date.now() / 1000) + 600, // 10 minutes from now
63+
extensionAddress: ''
64+
};
65+
3566
export function SubscriptionForm() {
3667
const [subscription, setSubscription] =
3768
useState<CreateSubscriptionV2Request>(baseSubscriptionPayload);
3869
const [subscriptionRes, setSubscriptionRes] = useState<CreateSubscriptionV2Response | null>(
3970
null
4071
);
4172
const [subscriptionError, setSubscriptionError] = useState<string | null>(null);
73+
74+
const [cancelPayload, setCancelPayload] =
75+
useState<CancelSubscriptionV2Request>(baseCancelPayload);
4276
const [cancelRes, setCancelRes] = useState<CancelSubscriptionV2Response | null>(null);
4377
const [cancelError, setCancelError] = useState<string | null>(null);
4478

4579
const wallet = useTonWallet();
4680
const [tonConnectUi] = useTonConnectUI();
4781

48-
const onChange = useCallback((value: object) => {
82+
const onSubscriptionChange = useCallback((value: object) => {
4983
setSubscription((value as { updated_src: typeof subscription }).updated_src);
5084
}, []);
5185

86+
const onCancelChange = useCallback((value: object) => {
87+
setCancelPayload((value as { updated_src: typeof cancelPayload }).updated_src);
88+
}, []);
89+
5290
// const loadTemplate = (template: CreateSubscriptionV2Request) => {
5391
// setSubscription(template);
5492
// };
@@ -60,6 +98,13 @@ export function SubscriptionForm() {
6098
.then(res => {
6199
setSubscriptionRes(res);
62100
setSubscriptionError(null);
101+
// Auto-fill extensionAddress in cancel form
102+
if (res.extensionAddress) {
103+
setCancelPayload(prev => ({
104+
...prev,
105+
extensionAddress: res.extensionAddress as string
106+
}));
107+
}
63108
})
64109
.catch(err => {
65110
setSubscriptionError(err instanceof Error ? err.message : String(err));
@@ -68,21 +113,14 @@ export function SubscriptionForm() {
68113
};
69114

70115
const onCancel = () => {
71-
if (!subscriptionRes?.boc) {
72-
console.error('No subscription response boc available');
116+
if (!cancelPayload.extensionAddress) {
117+
setCancelError('No extensionAddress provided');
73118
return;
74119
}
75120

76-
const cancelRequest: CancelSubscriptionV2Request = {
77-
validUntil: Math.floor(Date.now() / 1000) + 600, // 10 minutes from now
78-
extensionAddress: subscriptionRes.boc,
79-
network: subscription.network,
80-
from: subscription.from
81-
};
82-
83121
setCancelError(null);
84122
tonConnectUi
85-
.cancelSubscription(cancelRequest, { version: 'v2' })
123+
.cancelSubscription(cancelPayload, { version: 'v2' })
86124
.then(res => {
87125
setCancelRes(res);
88126
setCancelError(null);
@@ -95,66 +133,100 @@ export function SubscriptionForm() {
95133

96134
return (
97135
<div className="create-subscription-form">
98-
<h3>Configure and create subsciption</h3>
99-
<h4>Subscription data </h4>
100-
101-
{/* <div className="template-buttons">
102-
<button onClick={() => loadTemplate(defaultTextData)}>
103-
Text
104-
</button>
105-
<button onClick={() => loadTemplate(defaultBinaryData)}>
106-
Binary
107-
</button>
108-
<button onClick={() => loadTemplate(defaultCellData)}>
109-
Cell
110-
</button>
111-
</div> */}
112-
<ReactJson
113-
name={false}
114-
src={subscription}
115-
theme="ocean"
116-
onEdit={onChange}
117-
onAdd={onChange}
118-
onDelete={onChange}
119-
/>
120-
{subscriptionError && (
121-
<>
122-
<h4 style={{ color: 'red' }}>Create subscription error</h4>
123-
<div style={{ color: 'red', padding: '10px', border: '1px solid red' }}>
124-
{subscriptionError}
125-
</div>
126-
</>
127-
)}
128-
{subscriptionRes && (
129-
<>
130-
<h4 style={{ color: 'green' }}>Create subscription response</h4>
131-
<ReactJson name={false} src={subscriptionRes} theme="ocean" />
132-
</>
133-
)}
134-
{cancelError && (
135-
<>
136-
<h4 style={{ color: 'red' }}>Cancel subscription error</h4>
137-
<div style={{ color: 'red', padding: '10px', border: '1px solid red' }}>
138-
{cancelError}
136+
<h3>Subscriptions</h3>
137+
138+
{/* SUBSCRIBE BLOCK */}
139+
<div className="subscription-block">
140+
<h4 style={{ marginBottom: '10px' }}>Subscription Request Data</h4>
141+
142+
<ReactJson
143+
name={false}
144+
src={subscription}
145+
theme="ocean"
146+
onEdit={onSubscriptionChange}
147+
onAdd={onSubscriptionChange}
148+
onDelete={onSubscriptionChange}
149+
/>
150+
151+
{subscriptionError && (
152+
<>
153+
<h4 style={{ color: 'red' }}>Error</h4>
154+
<div style={{ color: 'red', padding: '10px', border: '1px solid red' }}>
155+
{subscriptionError}
156+
</div>
157+
</>
158+
)}
159+
160+
{subscriptionRes && (
161+
<>
162+
<h4 style={{ color: 'green' }}>Create subscription response</h4>
163+
<ReactJson name={false} src={subscriptionRes} theme="ocean" />
164+
<div
165+
style={{
166+
marginTop: '10px',
167+
padding: '10px',
168+
backgroundColor: '#f0f0f0',
169+
borderRadius: '4px'
170+
}}
171+
>
172+
<strong>Parsed Extension Address from BOC:</strong>
173+
<div
174+
style={{
175+
fontFamily: 'monospace',
176+
marginTop: '5px',
177+
wordBreak: 'break-all'
178+
}}
179+
>
180+
{parseExtensionAddressFromBoc(subscriptionRes.boc)}
181+
</div>
182+
</div>
183+
</>
184+
)}
185+
186+
{wallet && (
187+
<div className="buttons-container">
188+
<button onClick={onSend}>Create subscription</button>
139189
</div>
140-
</>
141-
)}
142-
{cancelRes && (
143-
<>
144-
<h4 style={{ color: 'green' }}>Cancel subscription response</h4>
145-
<ReactJson name={false} src={cancelRes} theme="ocean" />
146-
</>
147-
)}
148-
{wallet && (
149-
<div className="buttons-container">
150-
<button onClick={onSend}>Create subscription</button>
151-
{subscriptionRes && (
152-
<button onClick={onCancel} disabled={!subscriptionRes?.boc}>
190+
)}
191+
</div>
192+
193+
{/* UNSUBSCRIBE BLOCK */}
194+
<div className="cancel-block">
195+
<h4 style={{ marginBottom: '10px' }}>Cancel Request Data</h4>
196+
197+
<ReactJson
198+
name={false}
199+
src={cancelPayload}
200+
theme="ocean"
201+
onEdit={onCancelChange}
202+
onAdd={onCancelChange}
203+
onDelete={onCancelChange}
204+
/>
205+
206+
{cancelError && (
207+
<>
208+
<h4 style={{ color: 'red' }}>Error</h4>
209+
<div style={{ color: 'red', padding: '10px', border: '1px solid red' }}>
210+
{cancelError}
211+
</div>
212+
</>
213+
)}
214+
215+
{cancelRes && (
216+
<>
217+
<h4 style={{ color: 'green' }}>Cancel subscription response</h4>
218+
<ReactJson name={false} src={cancelRes} theme="ocean" />
219+
</>
220+
)}
221+
222+
{wallet && (
223+
<div className="buttons-container">
224+
<button onClick={onCancel} disabled={!cancelPayload.extensionAddress}>
153225
Cancel subscription
154226
</button>
155-
)}
156-
</div>
157-
)}
227+
</div>
228+
)}
229+
</div>
158230
</div>
159231
);
160232
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
export interface CreateSubscriptionV2Response {
22
/**
3-
* Subscription extension address
3+
* Subscription extension address (Bag of Cells)
44
*/
55
boc: string;
6+
/**
7+
* Parsed extension address for convenience
8+
*/
9+
// TODO: remove this property for release, only for testing purposes
10+
extensionAddress?: string;
611
}

packages/sdk/src/parsers/create-subscription-v2-parser.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ export class CreateSubscriptionV2Parser extends RpcParser<'createSubscriptionV2'
5353
convertFromRpcResponse(
5454
rpcResponse: WithoutId<CreateSubscriptionV2RpcResponseSuccess>
5555
): CreateSubscriptionV2Response {
56-
return rpcResponse.result;
56+
return {
57+
...rpcResponse.result,
58+
// TODO: remove this property for release, only for testing purposes
59+
// @ts-ignore
60+
extensionAddress: rpcResponse.result.extension_address
61+
};
5762
}
5863
}
5964

packages/sdk/src/ton-connect.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -633,15 +633,15 @@ export class TonConnect implements ITonConnect {
633633
const from = data.from ?? this.account!.address; // TODO: verify if data.from is needed or can be removed in favor of always using this.account!.address
634634
const network = data.network ?? this.account!.chain;
635635

636+
const { subscription, validUntil } = data;
636637
const { firstChargeDate, withdrawAddress, withdrawMsgBody, ...subscriptionRest } =
637-
data.subscription;
638+
subscription;
638639

639640
const response = await this.provider!.sendRequest(
640641
createSubscriptionV2Parser.convertToRpcRequest({
641-
...data,
642642
from,
643643
network,
644-
valid_until: data.validUntil,
644+
valid_until: validUntil,
645645
subscription: {
646646
...subscriptionRest,
647647
...(firstChargeDate !== undefined && { first_charge_date: firstChargeDate }),

0 commit comments

Comments
 (0)