Sending Tokens to New Accounts
A Supra address can be generated before an on-chain Account resource exists at that address. For integrations that send SUPRA or other assets to user-supplied addresses, the absence of an on-chain account must not be treated as an invalid address.
The Supra framework provides account-aware transfer functions specifically for recipients that may not exist yet. These functions create the recipient account and initialize the required receiving state as part of the transfer.
Key principle: Validate the address itself. Do not use account existence as the address-validity check.
At a Glance
| Asset | Use this function | Creates the recipient account? |
|---|---|---|
| Native SUPRA | 0x1::supra_account::transfer | Yes |
Legacy Coin<T> | 0x1::supra_account::transfer_coins<CoinType> | Yes |
Legacy Coin<T>, recipient already ready | 0x1::coin::transfer<CoinType> | No |
| Fungible Asset | The FA transfer API for that asset | Depends on the asset |
Address and Account Are Different
A wallet can generate a Supra address locally without creating an on-chain account.
The Supra framework defines the supra_framework::account::Account resource and an exists_at(address) view that checks whether that resource exists at the address. A newly generated address therefore has no Account resource until an on-chain operation creates it.
The important distinction is:
- Address validity is a property of the address supplied by the user. It can be checked off-chain.
- Account existence is on-chain state that changes the first time the address is used.
How Supra Initializes a New Recipient
The Supra framework implements this behavior in 0x1::supra_account.
supra_account::transfer is documented as transferring SUPRA to a recipient account that might not exist. It checks account::exists_at(to) and calls create_account(to) when necessary before completing the transfer. It also handles SUPRA receiving-state initialization:
- When the SUPRA fungible-store feature is enabled, it creates the SUPRA Primary Fungible Store as required.
- Otherwise, it registers
SupraCoinwhen the recipient is not already registered.
A sender does not need to perform a separate “activate recipient account” transaction before sending SUPRA.
Native SUPRA
0x1::supra_account::transfer(
source,
recipient,
amount
)Signature in the current Supra framework:
public entry fun transfer(
source: &signer,
to: address,
amount: u64
)Framework implementation:
public entry fun transfer(source: &signer, to: address, amount: u64) {
if (!account::exists_at(to)) {
create_account(to)
};
if (features::operations_default_to_fa_supra_store_enabled()) {
fungible_transfer_only(source, to, amount)
} else {
if (!coin::is_account_registered<SupraCoin>(to)) {
coin::register<SupraCoin>(&create_signer(to));
};
coin::transfer<SupraCoin>(source, to, amount)
}
}Important: The internal storage path for SUPRA depends on the framework feature configuration. Call the public
supra_account::transfer entry point rather than duplicating the internal feature-dependent logic.
Custom Coin<T> Assets
For a custom asset using Supra’s legacy Coin<T> standard, use the account-aware:
0x1::supra_account::transfer_coins<CoinType>(
source,
recipient,
amount
)Signature in the current Supra framework:
public entry fun transfer_coins<CoinType>(
from: &signer,
to: address,
amount: u64
)transfer_coins withdraws the asset from the sender and passes it to supra_account::deposit_coins, which performs the recipient-side initialization:
Create the account if it does not exist
Check whether the recipient is registered for CoinType
If not registered, check whether the recipient accepts direct coin transfers
Register the CoinType
Deposit the coins
A custom Coin<T> recipient therefore does not need to register the CoinStore<T> manually before receiving through this account-aware path.
Direct-transfer opt-out
An existing account can explicitly disable direct transfers of coins it has not registered for:
0x1::supra_account::set_allow_direct_coin_transfers(
account,
false
)The default is to allow direct transfers when the account has not configured this option.
An existing account that has opted out of direct transfers can still cause an account-aware custom-coin transfer to fail. This is a different condition from a new account that simply has not been initialized yet.
Strict coin::transfer vs. Account-Aware Transfer
The legacy Coin module exposes:
0x1::coin::transfer<CoinType>(
from,
to,
amount
)This function withdraws the coins and calls coin::deposit on the recipient. It does not create the recipient account or register the recipient’s CoinStore<T> for you, so it is appropriate only when the recipient is already prepared to receive that CoinType.
For user-facing transfers where the recipient may be new, prefer the account-aware paths listed in At a Glance.
Recipient States
| State | Description | Recommended behavior |
|---|---|---|
| New address | No Account resource exists at the address | Use an account-aware transfer path |
| Account exists, CoinStore missing | Account exists but is not registered for the custom Coin<T> | Use supra_account::transfer_coins<CoinType> |
| Account and receiving state exist | Recipient is ready for the asset | Transfer normally |
| Existing account opted out | Recipient has disabled direct transfers of unregistered coins | Surface the transfer failure; the recipient must explicitly register/allow the asset |
These states describe the transfer conditions an integration should handle. They are not a complete classification of every possible on-chain account state.
Do Not Use Account Existence as Address Validation
Recommended
An account-existence check can still be used as an informational or optimization signal, but it must not be the primary address-validity check.
Wallet Integration
A wallet should let a newly generated address receive funds without requiring a separate activation step. For a newly generated wallet, a plain zero balance (0 SUPRA) is the correct display.
Avoid showing:
Account does not existInvalid walletWallet not activatedActivate your account before receivingSend SUPRA to activate your wallet
The account is initialized by the appropriate on-chain operation; the user should not need to understand this lifecycle.
Recommended warning for outgoing transfers
When sending to an address with no known on-chain activity, a wallet may show a non-blocking confirmation:
This address has no on-chain activity yet.
If you entered the address manually, double-check it before continuing. Transactions sent to an incorrect address cannot be reversed.
This protects users from typing errors without blocking legitimate first-time transfers.
CEX, Bridge, DEX, and On-Ramp Integrations
Services that send withdrawals to user-supplied Supra addresses should follow these rules.
1. Validate the address, not account existence
Do not reject a withdrawal because an account lookup indicates the recipient has not been initialized.
2. Use the account-aware transfer function
0x1::supra_account::transfer for native SUPRA, 0x1::supra_account::transfer_coins<CoinType> for legacy custom Coin<T> assets.
3. Budget for recipient initialization
The sender pays the transaction gas, so a withdrawal system should budget for the work required to initialize a new recipient. Do not require the recipient to first obtain SUPRA from another account just to make the withdrawal possible.
4. Do not expose low-level resource errors as “invalid address”
Recipient account has not been initialized must not be surfaced to the user as Invalid Supra address. The withdrawal system should select the correct account-aware transfer path instead.
Smart Contract and dApp Payouts
Any dApp contract that pays a user-supplied address should consider that the recipient account may not yet exist — token claims, referral rewards, staking rewards, LP withdrawals, game winnings, refunds, airdrops, and fee rebates all hit this case.
For legacy Coin<T> payouts, prefer the account-aware supra_account::deposit_coins or transfer_coins flow. A contract should not assume that every recipient already has a registered CoinStore<T>.
Limitation: Account-aware transfer functions do not override recipient-level permissions. An existing account can explicitly disable direct transfers of unregistered legacy coins, and such a transfer can still fail.
Fungible Assets and SUPRA
The Supra framework contains both legacy Coin<T> functionality and Fungible Asset functionality. For native SUPRA, supra_account::transfer already contains the framework’s feature-dependent handling (see the diagram in How Supra Initializes a New Recipient).
Applications should not reproduce this internal branching logic. Use the public account-aware entry point and keep application code independent of the current feature-flag implementation.
For other Fungible Assets, use the FA APIs appropriate for that asset and framework version rather than assuming every asset is represented by a legacy CoinStore<T>. See the Fungible Asset (FA) Module page.
Checking Account State Through RPC
The Supra account module exposes the following view:
0x1::account::exists_at(address)It returns whether the supra_framework::account::Account resource exists at the supplied address:
#[view]
public fun exists_at(addr: address): bool {
exists<Account>(addr)
}This is useful when an integration needs to distinguish an initialized account from an address that has not yet been initialized.
Do not use exists_at as the address-validity check. A valid new address legitimately returns false.
The RPC API can also be used to inspect account resources — for example GET /rpc/v1/accounts/{address} plus resource-specific queries. Use the Supra Mainnet RPC API reference for the exact endpoints, response format, and error handling, and do not hard-code an assumption that every 404 from an account or resource lookup represents an invalid address.
Testing
Every integration that sends tokens to a user-supplied Supra address should test a completely new recipient.
| # | Scenario | Expected result |
|---|---|---|
| 1 | SUPRA to an existing account | Transaction succeeds |
| 2 | SUPRA to a newly generated address (no Account) | Succeeds through supra_account::transfer; the account is created and SUPRA received |
| 3 | Custom Coin<T> to a newly generated address | Succeeds through supra_account::transfer_coins<CoinType>; account and CoinStore<T> are initialized |
| 4 | Malformed address input | Address validation fails before transaction submission |
| 5 | Unregistered Coin<T> to an account that opted out | Transfer can fail because the recipient disabled direct transfers of unregistered coins |
Tests 2 and 5 matter most: “new account” and “existing account that rejects direct transfers” are different conditions and must not be handled by the same error path.
Integration Checklist
- Validate the address independently of account existence.
- Do not reject a valid address solely because
Accountis missing. - Use
0x1::supra_account::transferfor native SUPRA transfers to recipients that may be new. - Use
0x1::supra_account::transfer_coins<CoinType>for legacy customCoin<T>transfers to recipients that may be new. - Do not assume that
0x1::coin::transfer<CoinType>initializes a recipient. - Handle existing accounts that have opted out of direct transfers of unregistered coins.
- Keep SUPRA FA/Coin feature branching inside the framework/API layer rather than duplicating it in application code.
- Budget transaction gas for recipient initialization.
- Test a recipient address that has never had an on-chain
Accountresource. - Test an invalid address separately from an uninitialized address.
- Do not require users to manually activate a wallet before receiving funds.
- Ensure explorer and wallet UIs can represent a valid address with no on-chain account state.
- Verify the framework and RPC behavior against the network/framework version used by your integration.
Common Errors
| Error | Meaning | Action |
|---|---|---|
| Account resource not found | The address may be valid but has not been initialized on-chain | Validate the address format and use the appropriate account-aware transfer path |
CoinStore<T> not found | The account exists but is not registered for the requested legacy Coin<T> | Use supra_account::transfer_coins<CoinType> |
| Direct transfer rejected | The recipient has disabled direct transfers of unregistered legacy coins | Do not treat this as an uninitialized account; the recipient must allow or register the asset |
| SUPRA transfer fails with a registration error | The integration is bypassing the account-aware SUPRA transfer path | Use 0x1::supra_account::transfer |
Summary
A valid address is not the same thing as an existing on-chain account. A newly generated Supra address can be a
valid destination before its Account resource exists — do not use on-chain account existence as an address-validity
gate.
An integration that rejects a valid new address because its Account resource is missing creates an unnecessary onboarding dependency on an existing funded account.