Skip to Content
Supra Layer 1MoveVMSending Tokens & Account Initialization

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

AssetUse this functionCreates the recipient account?
Native SUPRA0x1::supra_account::transferYes
Legacy Coin<T>0x1::supra_account::transfer_coins<CoinType>Yes
Legacy Coin<T>, recipient already ready0x1::coin::transfer<CoinType>No
Fungible AssetThe FA transfer API for that assetDepends 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 SupraCoin when 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

StateDescriptionRecommended behavior
New addressNo Account resource exists at the addressUse an account-aware transfer path
Account exists, CoinStore missingAccount exists but is not registered for the custom Coin<T>Use supra_account::transfer_coins<CoinType>
Account and receiving state existRecipient is ready for the assetTransfer normally
Existing account opted outRecipient has disabled direct transfers of unregistered coinsSurface 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

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 exist
  • Invalid wallet
  • Wallet not activated
  • Activate your account before receiving
  • Send SUPRA to activate your wallet

The account is initialized by the appropriate on-chain operation; the user should not need to understand this lifecycle.

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.

#ScenarioExpected result
1SUPRA to an existing accountTransaction succeeds
2SUPRA to a newly generated address (no Account)Succeeds through supra_account::transfer; the account is created and SUPRA received
3Custom Coin<T> to a newly generated addressSucceeds through supra_account::transfer_coins<CoinType>; account and CoinStore<T> are initialized
4Malformed address inputAddress validation fails before transaction submission
5Unregistered Coin<T> to an account that opted outTransfer 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 Account is missing.
  • Use 0x1::supra_account::transfer for native SUPRA transfers to recipients that may be new.
  • Use 0x1::supra_account::transfer_coins<CoinType> for legacy custom Coin<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 Account resource.
  • 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

ErrorMeaningAction
Account resource not foundThe address may be valid but has not been initialized on-chainValidate the address format and use the appropriate account-aware transfer path
CoinStore<T> not foundThe account exists but is not registered for the requested legacy Coin<T>Use supra_account::transfer_coins<CoinType>
Direct transfer rejectedThe recipient has disabled direct transfers of unregistered legacy coinsDo not treat this as an uninitialized account; the recipient must allow or register the asset
SUPRA transfer fails with a registration errorThe integration is bypassing the account-aware SUPRA transfer pathUse 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.

References

Last updated on