Skip to content

Latest commit

 

History

History
206 lines (168 loc) · 9.74 KB

File metadata and controls

206 lines (168 loc) · 9.74 KB

Subscriptions 2.0

This document provides a specification for subscription extension.

Extension

Installing

When installing, wallet must perform the following steps:

  1. Add the extension address to the list of allowed extensions.
  2. Deploy the extension contract.

State init must be constructed as follows:

import { beginCell } from '@ton/core';

const stateInit = beginCell()
    .storeMaybeRef(null)
    .storeUint(0, 32) // lastRequestTime
    .storeUint(0, 32) // chargeDate
    .storeUint(0, 2) // subscription_state
    .storeRef(beginCell().storeCoins(0).storeRef(beginCell().endCell())) // precompiled
    .storeUint(0, 32) // gracePeriod
    .storeCoins(0) // callerFee
    .storeAddress(wallet)
    .storeUint(walletVersion, 8)
    .storeAddress(beneficiary)
    .storeUint(subscriptionId, 32)
    .storeCoins(0) // paymentPerPeriod
    .storeUint(0, 32) // period
    .storeRef(beginCell().storeAddress(null).storeRef(beginCell())) // withdrawInfo
    .storeRef(beginCell());

TL-B:

state$_ 
    reward_address_ref: Maybe ^[MsgAddressInt]
    last_request_time: uint32
    charge_date: uint32
    subscription_state: uint2
    precompiled: ^[precompiled_fwd_amount: Coins precompiled_c5: ^Cell]
    grace_period:uint32
    caller_fee: Coins
    wallet: MsgAddressInt
    wallet_version: WalletVersion
    beneficiary: MsgAddressInt
    subscription_id: uint32
    payment_per_period: Coins
    period: uint32
    withdraw_info: ^[withdraw_address: MsgAddressInt withdraw_msg_body: ^Cell]
    metadata: ^Cell
 = SubscriptionState;
 
wallet_version_v4#4 = WalletVersion;
wallet_version_v5r1#33 = WalletVersion;

where:

  • wallet is the address of the wallet associated with the subscription.
  • walletVersion is the version of the wallet: 0x04 for v4, 0x33 for v5r1. Contract will fail if the different value is provided.
  • beneficiary is the address that can cancel the subscription and receive the remaining balance.
  • subscriptionId is the subscription identifier (in case if few subscriptions with the same beneficiary are required).
  1. When deploying, it should call the op::deploy method with the following scheme:
deploy#f71783cb
    query_id:uint64
    first_charging_date:uint32
    payment_per_period:Coins
    period:uint32
    grace_period:uint32
    caller_fee:Coins
    withdraw_address: MsgAddressInt
    withdraw_msg_body: ^Cell
    metadata:^Cell
    = InternalMsg;

where:

  • first_charging_date is the timestamp of the first charging date. This should be specified in case the subscription has a free trial period until the specified date. If the first payment should be charged immediately, this value should be set to 0.
  • payment_per_period is the amount of coins to be withdrawn per subscription period. This value must be greater than the reserved amount of coins (can be received using get_reserved_amount get method).
  • period is the duration of the subscription period in seconds.
  • grace_period is the grace period in seconds. If the subscription is not paid within this period, it is considered expired. Three attempts to pay the subscription are allowed within the grace period. grace_period must be lesser than period.
  • caller_fee is the fee that the caller charges for calling the prolongation request. If set to 0, the fee is not charged.
  • withdraw_address is the address of the contract that will receive the subscription payment.
  • withdraw_msg_body is the message body that will be sent to the withdraw_address.
  • metadata is a cell containing additional data related to the subscription. JSON cyphered bytes.

Internal Message Handlers

  1. destruct#64737472 query_id:uint64 = MessageInternal: Destroys the subscription contract.
    • subscription_state is set to 2.
    • If called by the wallet, and the remaining balance is transferred to the beneficiary.
    • If called by the beneficiary, the remaining balance is transferred to the beneficiary, and the subscription's address is removed from the wallet's list of authorized extensions.
    • Contract remains in the blockchain and can be used for indexing.

External Message Handlers

Subscriptions can be prolonged by calling an external method on the contract using the following scheme:

cron_trigger#2114702d reward_address:MsgAddress salt:uint32 = ExternalMsgBody;

where:

  • reward_address is the address to which the caller fee will be sent if the prolongation request is successful.
  • salt is any value used to prevent liteserver from caching the message.

When this method is called, the contract checks if it is permissible to make the next write-off. If permissible, a request is sent to the wallet to proceed with the subscription extension.

Subscription prolongation requests must be sent after charging_date, during grace_period which by default equals to 3 days. Three attempts are possible, and delay equals to the grace_period / 3 is required between attempts.

If attempt was successful:

  • Exact amount of payment_per_period TON will be charged from the wallet.
  • caller_fee amount of TON will be sent to the reward_address.
  • Extension contract balance amount will be replenished to the minimum storage amount.
  • Remainder will be sent to the withdraw_address with the message body withdraw_msg_body.
  • charging_date will be set to the previous_charging_date+period.

The caller_fee is paid to reward_address only if the prolongation request is successful (i.e., if the wallet has sufficient balance to cover the subscription cost). This encourages callers to verify the user’s wallet balance before spamming requests, thereby saving the beneficiary unnecessary fees.

If attempt was unsuccessful:

  • Next attempt can be made only after grace_period / 3 seconds from the previous attempt.

If three attempts were unsuccessful, the Caller may call the op::prolong fourth time after the grace period is over, with the following results:

  • subscription_state is set to 2.
  • caller_fee is sent to the reward_address.
  • The subscription's address is removed from the wallet's list of authorized extensions.
  • The remaining balance is transferred to the beneficiary.
  • The contract remains in the blockchain and can be used for indexing.

All on-chain fees are covered by the beneficiary using the funds the user initially provided. Consequently, from the user’s perspective, the amount deducted from their wallet each period is always exactly payment_per_period (except the first payment).

Native Subscription Extension Get Methods

  • get_subscription_info() returns (slice wallet, int wallet_version, slice beneficiary, int subscription_id, slice withdraw_address, cell withdraw_msg_body, cell metadata)

    • wallet: Address of the wallet associated with the subscription.
    • wallet_version: Version of the wallet.
    • beneficiary: Address that can cancel the subscription and receive the remaining balance.
    • subscription_id: Subscription identifier.
    • withdraw_address: Address of the contract that will receive the subscription payment.
    • withdraw_msg_body: Message body that will be sent to the withdraw_address.
    • metadata: A cell containing additional data related to the subscription.
  • get_payment_info() returns (int contract_state, int payment_per_period, int period, int charge_date, int grace_period, int last_request_time, int caller_fee)

    • contract_state: Contract state. Note that it is contract state, not the state of the subscription. Subscription may be already inactive due to grace period expiration, but the contract itself may contain active state. Possible values:
      • 0 - contract is deployed but not yet initialized.
      • 1 - contract is deployed and initialized.
      • 2 - subscription is cancelled and the contract cannot be used anymore.
    • payment_per_period: Amount of coins to be withdrawn per subscription period.
    • period: Duration of the subscription period in seconds.
    • charge_date: Timestamp of the next charging date.
    • grace_period: Grace period in seconds.
    • last_request_time: Timestamp of the last prolongation request.
    • caller_fee: Fee that the caller charges for calling the prolongation request.
  • get_reserved_amount() returns int reserved_amount

    • reserved_amount: Amount of coins reserved in the contract.
  • get_is_subscription_active() returns int is_active

    • is_active: 1 if the subscription is active, 0 otherwise. Subscription is considered active if current time is less than charge_date + grace_period and the subscription state is 1.
  • get_cron_info() returns (int next_call_time, int caller_fee, int balance_after_minus_amounts, int period)

    • next_call_time: Timestamp of the next time when prolongation method can be called.
    • caller_fee: Fee that the caller charges for calling the prolongation request.
    • balance_after_minus_amounts: Balance after the call.
    • period: Period after which the prolongation request can be made.

Metadata format

Metadata fields (shortened keys in parentheses):

  1. logo (l) - URL to the subscription’s logo image.
  2. name (n) - Name of the subscription (text, max 80 characters).
  3. description (d) - Description of the subscription (text, max 255 characters).
  4. link (L) - URL to the source or service the user is subscribing to (e.g., a Telegram channel).
  5. tos (t) - URL to the Terms of Service.
  6. merchant (m) - Name of the merchant (text, max 80 characters).
  7. website (w) - URL to the merchant’s website.
  8. category (optional) (c) - Tier or category of the subscription (text, max 80 characters).

Metadata stored in the cell in the encrypted form. Encryption algorithm is implementation-defined and therefore not covered by this specification.