faportalsmp
An asynchronous Python library for the Portals Gift Marketplace — a maintained fork of aportalsmp. Trade Telegram gifts, place offers, run giveaways, read your account, and deposit & withdraw GRAM.
pip install faportalsmpimport aportalsmp as portals
What this fork adds over aportalsmp
Same library, brought in line with the current marketplace and extended. Nothing to relearn — existing code keeps working.
| Area | aportalsmp | faportalsmp |
|---|---|---|
| Wallet | Withdraw only | GRAM deposit (intent + auto-send) & withdraw, status, limits, history |
| Gift list | Static | Regenerated from the live API + generator script; swapped IDs fixed |
| Name lookup | Exact / cased | Case-, apostrophe- and hyphen-insensitive |
| Python | 3.10+ in practice | Runs on 3.8+ |
Installing
Requires Python 3.8+. Everything — including GRAM deposit auto-send — is included in the single install.
pip install faportalsmp
Then import the package as aportalsmp:
import aportalsmp as portals
Limits
Practical limits enforced by the API and the library:
- search / lists — up to
100items per request; paginate withoffset(increment bylimit). - offer expiration —
expiration_daysmust be7or0(no expiration). - offer price — edits require a value
>= 0.5GRAM. - wallet — deposit/withdraw minimums, maximums and the daily limit are returned by
walletLimits().
Authentication
Every call takes an authData string — a Telegram Mini App initData prefixed with tma . Generate it from a user session with update_auth().
async def update_auth(api_id, api_hash, session_string, session_path, session_name)→ strOpens the Portals Mini App as your Telegram user and returns the tma … authData string. Provide either api_id + api_hash, or an existing session_string.
from aportalsmp import update_auth
authData = await update_auth(api_id=12345, api_hash="abc...", session_name="account")
# or
authData = await update_auth(session_string="BQ...")
authData= to every function below.Working with gifts
Read floors and listings, then buy, list, re-price, transfer or withdraw gifts.
async def search(sort, offset, limit, gift_name, model, backdrop, symbol, min_price, max_price, exclude_bundled, premarket_status, authData)→ list[PortalsGift]Search listed gifts with filters and sorting.
| Parameter | Description |
|---|---|
sort str | price_asc (default), price_desc, latest, gift_id_asc/desc, model_rarity_asc/desc |
gift_name str | list | Collection name(s); resolved leniently |
model / backdrop / symbol | Attribute filters (str or list) |
min_price / max_price int | Price bounds in GRAM |
limit / offset int | ≤ 100 per page; paginate with offset |
gifts = await portals.search(
gift_name="Plush Pepe", model="Sad Cat",
backdrop=["Onyx Black", "Midnight Blue"],
sort="price_asc", min_price=0, max_price=5000, limit=20,
authData=authData,
)
async def buy(nft_id, price, authData)→ dictBuy a listing at price (GRAM). Returns the purchase result JSON (purchased / failed with reasons).
if gifts:
result = await portals.buy(nft_id=gifts[0].id, price=gifts[0].price, authData=authData)async def sale(nft_id, price, authData) · bulkList(nfts, authData)→ SaleResultList a single gift (sale) or many at once (bulkList, a list of {"nft_id", "price"}) for sale in GRAM.
Your gifts & inventory
See everything you own, then unlist, quick-sell, transfer or withdraw it to Telegram.
# view — full inventory (everything you own)
mine = await portals.inventory(offset=0, limit=50, authData=authData)
for g in mine:
print(g.name, g.model, g.status)
# or only your marketplace listings
listed = await portals.myPortalsGifts(listed=True, authData=authData)
# take a listing off sale
await portals.unlist(nft_id=mine[0].id, authData=authData)
# instant-sell into the best collection offer
plan = await portals.quickSalePreview(nft_ids=[mine[0].id], authData=authData)
await portals.quickSale(nft_ids=[mine[0].id], authData=authData)
# withdraw the gift out of Portals (to your Telegram account)
await portals.withdrawGifts(nft_ids=[mine[0].id], authData=authData)
# or transfer it to another user inside Portals
await portals.transferGifts(nft_ids=[mine[0].id], username="someone", authData=authData)
| Function | Purpose |
|---|---|
inventory(offset, limit, authData) | Every gift you own (listed or not) |
inventoryCollections(authData) | Your inventory grouped by collection, with counts |
inventoryValue(authData) | Estimated total value of your inventory (GRAM) |
myPortalsGifts(offset, limit, listed, authData) | Your gifts filtered by marketplace listing status |
unlist(nft_id, authData) · bulkUnlist(nft_ids, authData) | Remove gift(s) from sale |
quickSalePreview / quickSale(nft_ids, authData) | Preview & execute an instant sale into the top offer |
checkAvailability(nft_ids, authData) | Check gifts are still available (e.g. before buying) |
withdrawGifts(nft_ids, authData) | Withdraw gifts out of Portals (to Telegram) |
transferGifts(nft_ids, username, anonymous, authData) | Transfer gifts to another user inside Portals |
The rest of the gifts module:
| Function | Returns | Purpose |
|---|---|---|
giftsFloors(authData) | GiftsFloors | Floor price of every collection (short names) |
filterFloors(gift_name, authData) | Filters | Model / backdrop / symbol floors for a collection |
collections(limit, authData) | Collections | Collections with floor, supply, volume |
marketActivity(...) | list[Activity] | Recent buys / listings / offers / price updates |
myPortalsGifts(offset, limit, listed, authData) | list[PortalsGift] | Gifts you own |
myActivity(offset, limit, authData) | list[MyActivity] | Your activity |
changePrice(nft_id, price, authData) | None | Re-price a listing |
transferGifts(nft_ids, username, anonymous, authData) | None | Transfer gifts to a user |
withdrawGifts(nft_ids, authData) | None | Withdraw gifts out of the marketplace |
getGiveaways / giveawayInfo / joinGiveaway | Giveaway… | Browse and join giveaways |
"plush pepe", "Durov's Cap" and "Durov’s Cap" all match. Refresh the collection list any time with python scripts/update_collections.py.Working with offers
Per-gift offers and collection-wide offers.
# single-gift offers
await portals.makeOffer(nft_id="...", offer_price=10, expiration_days=7, authData=authData)
await portals.editOffer(offer_id="...", new_price=12, authData=authData)
await portals.cancelOffer(offer_id="...", authData=authData)
# collection-wide offers
await portals.collectionOffer(gift_name="Plush Pepe", amount=100, max_nfts=3, authData=authData)
top = await portals.topOffer(gift_name="Plush Pepe", authData=authData)
Working with your account
Points, stats and balances. Balances are denominated in GRAM.
| Function | Returns | Fields |
|---|---|---|
myPoints(authData) | Points | total_points, purchase/sell/referral/bonus points & counts |
myStats(authData) | Stats | total_bought, total_sold, total_volume |
myBalances(authData) | Balances | balance, frozen_funds (GRAM) |
Wallet · GRAM
Deposit GRAM
A deposit is a native GRAM transfer to the Portals deposit wallet carrying a text comment equal to the deposit id — that is how the marketplace credits your balance.
async def deposit(amount, mnemonic, address, wallet_version, authData)→ DepositInfoIntent — pass only authData to mint a deposit and get where/how to send GRAM. Auto-send — also pass a mnemonic (+ amount) and the library signs & broadcasts the transfer via pytoniq.
| Parameter | Description |
|---|---|
amount float | GRAM to deposit (required for auto-send) |
mnemonic str | list | 24-word wallet mnemonic; enables auto-send. Used only to sign, never transmitted |
wallet_version str | v4r2 (default), v3r2, v3r1, v5r1 |
address str | Override the destination (normally from the API) |
# intent — send GRAM yourself
info = await portals.deposit(authData=authData)
info.address # where to send GRAM
info.comment # text comment to attach (the deposit id)
# auto-send — library signs & broadcasts
info = await portals.deposit(
amount=0.5, mnemonic="word1 ... word24", wallet_version="v4r2", authData=authData,
)
info.sent_tx # {'status': 'sent', 'amount': 0.5, 'to': ..., 'comment': ...}
async def depositStatus(ids, authData)→ list[DepositStatus]Poll one or more deposits by id. Omit ids for recent deposits.
Withdraw GRAM
async def withdrawGram(amount, wallet, authData)→ strWithdraw GRAM to an external TON address. Returns the withdrawal id.
wid = await portals.withdrawGram(amount=1.0, wallet="UQ...address", authData=authData)
statuses = await portals.withdrawStatus(ids=wid, authData=authData)
# withdrawPortals and withdrawTon are aliases of withdrawGram
async def walletLimits(authData) · walletHistory(offset, limit, authData) · withdrawStatus(ids, authData)walletLimits → min/max amounts, daily limit & fees. walletHistory → deposits, withdrawals and trades. withdrawStatus → poll withdrawals by id.
Advanced trading
Granular floors, market config, portfolio history and premarket orders. These return raw API dicts.
| Function | Purpose |
|---|---|
backdropFloors(gift_name, authData) | Per-backdrop floor prices for a collection |
modelBackgroundFloors(gift_name, authData) | Per-model / per-background floor prices |
attributeFloors(payload, authData) | Floors for specific attribute combinations |
marketConfig(authData) | Marketplace config (fees, feature flags) |
inventoryCostHistory(authData) | Historical value of your inventory over time |
Premarket orders
Collateral-based premarket orders. The POST bodies are marketplace-defined — pass them as payload.
| Function | Purpose |
|---|---|
premarketCollaterals(authData) | Available collateral options |
premarketCollections(authData) | Collections that support premarket |
premarketPreviewGift(payload, authData) | Preview adding a gift to a premarket order |
premarketAddGift(payload, authData) | Add a gift to a premarket order |
Referrals & cashback
# referrals
info = await portals.referralInfo(authData=authData)
friends = await portals.referralFriends(offset=0, limit=20, authData=authData)
await portals.applyReferral(code="FRIEND_CODE", authData=authData)
await portals.claimReferral(authData=authData)
# cashback
cb = await portals.cashbackInfo(authData=authData)
await portals.claimCashback(authData=authData)
Objects
Responses are wrapped in lightweight objects with typed properties and a .toDict() escape hatch to the raw API dict.
gift = (await portals.search(gift_name="Plush Pepe", limit=1, authData=authData))[0]
gift.name, gift.model, gift.backdrop, gift.symbol, gift.price
gift.toDict() # raw API dict
Exceptions
All errors subclass Exception and are importable from aportalsmp.
from aportalsmp import requestError
try:
await portals.buy(nft_id="...", price=1.0, authData=authData)
except requestError as e:
print("API error:", e)
Contacts & donations
Source & issues: github.com/thebrainair/faportalsmp.