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 can 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.

Address and Account Are Different

A wallet can generate a Supra address locally without creating an on-chain account.

The current 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 can therefore have no Account resource until an on-chain operation creates it.

Conceptually:

Generate keypair | v Valid Supra address | | No on-chain Account resource yet v Account-aware inbound transfer | v Account created / receiving state initialized | v Tokens received

The important distinction is:

  • Address validity is a property of the address supplied by the user.
  • Account existence is an on-chain state that can change when the address is first used.

How Supra Initializes a New Recipient

The Supra framework implements this behavior in 0x1::supra_account.

The current supra_account::transfer function is explicitly 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.

The framework implementation 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.

This means a sender does not need to perform a separate “activate recipient account” transaction before sending SUPRA.

Native SUPRA

Use:

0x1::supra_account::transfer( source, recipient, amount )

Signature in the current Supra framework:

public entry fun transfer( source: &signer, to: address, amount: u64 )

The function is account-aware and is intended for a recipient that might not exist.

The current framework implementation is:

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) } }

This is the recommended account-aware framework entry point for native SUPRA transfers.

Important: The exact internal storage path for SUPRA depends on the framework feature configuration. Integrations should 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.

deposit_coins performs the recipient-side initialization:

  1. Creates the account if it does not exist.
  2. Checks whether the recipient is registered for CoinType.
  3. If not registered, checks whether the recipient accepts direct coin transfers.
  4. Registers the CoinType.
  5. Deposits the coins.

Therefore, a custom Coin<T> recipient 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 by using:

0x1::supra_account::set_allow_direct_coin_transfers( account, false )

The default behavior is to allow direct transfers when the account has not explicitly configured this option.

Consequently, an existing account that has deliberately opted out of direct transfers can still cause an account-aware custom-coin transfer to fail. This is different 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.

Therefore, using coin::transfer directly is appropriate only when the recipient is already prepared to receive that CoinType.

For user-facing transfers where the recipient may be new, prefer:

Native SUPRA -> 0x1::supra_account::transfer Custom Coin<T> -> 0x1::supra_account::transfer_coins<CoinType>

Recipient States

For integration purposes, the following states are useful:

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 that 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

A common but incorrect withdrawal flow is:

Validate address | v GET /accounts/{address} | +-- 404 --> Reject withdrawal

A missing Account resource means that the address has not yet been initialized on-chain. It does not, by itself, mean that the address is malformed.

Instead:

Validate address format | +-- Invalid --> Reject | +-- Valid | v Send using the appropriate account-aware path

An account-existence check can still be used as an informational or optimization signal, but it should not be the primary address-validity check.

Wallet Integration

A wallet should allow a newly generated address to receive funds without requiring a separate activation step.

For a newly generated wallet, a normal zero state is appropriate:

SUPRA 0

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 simply because an account lookup indicates that the recipient has not been initialized.

2. Use the account-aware transfer function

For native SUPRA:

0x1::supra_account::transfer

For legacy custom Coin<T> assets:

0x1::supra_account::transfer_coins<CoinType>

3. Budget for recipient initialization

The sender pays the transaction gas. A withdrawal system should therefore budget for the work required to initialize a new recipient.

Do not require the recipient to first obtain SUPRA from another account simply to make the withdrawal possible.

4. Do not expose low-level resource errors as “invalid address”

For example, this should not automatically become:

Invalid Supra address

when the actual condition is:

Recipient account has not been initialized

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 the possibility that the recipient account does not yet exist.

Typical examples include:

  • Token claims
  • Referral rewards
  • Staking rewards
  • LP withdrawals
  • Game winnings
  • Refunds
  • Airdrops
  • Fee rebates

For legacy Coin<T> payouts, prefer the account-aware supra_account::deposit_coins or transfer_coins flow where appropriate.

A contract should not assume that every recipient already has a registered CoinStore<T>.

Important limitation

Account-aware transfer functions do not override recipient-level permissions. An existing account can explicitly disable direct transfers of unregistered legacy coins. Such a transfer can still fail.

Fungible Assets and SUPRA

The current 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:

SUPRA transfer | +-- FA SUPRA store enabled | | | +-- Use / create SUPRA Primary Fungible Store | +-- Otherwise | +-- Ensure SupraCoin registration +-- Transfer SupraCoin

Applications should not reproduce this internal branching logic.

Use the public Supra account-aware transfer entry point and keep the application code independent from the current feature flag implementation.

For other Fungible Assets, use the FA APIs appropriate for that asset and framework version rather than assuming that every asset is represented by a legacy CoinStore<T>. See the Fungible Asset (FA) Module page for details.

Checking Account State Through RPC

The Supra mainnet RPC documentation is available at rpc-mainnet.supra.com/docs .

The Supra account module also exposes the following view:

0x1::account::exists_at(address)

In the current Supra framework, this view returns whether the supra_framework::account::Account resource exists at the supplied address.

The current implementation is:

#[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.

However:

Do not use exists_at as the address-validity check. A valid new address can legitimately return false.

Querying account resources

The RPC API can also be used to inspect account resources when you need to determine whether a particular resource exists.

For example, conceptually:

GET /rpc/v1/accounts/{address}

and resource-specific queries can be used to inspect the on-chain state.

Use the current RPC API reference  for the exact endpoint, response format, and error handling.

Do not hard-code an assumption that every 404 from an account/resource lookup represents an invalid address.

Testing

Every integration that sends tokens to a user-supplied Supra address should test a completely new recipient.

Test 1: Existing account

Sender | +-- SUPRA --> Existing account Expected: Transaction succeeds.

Test 2: New account

Sender | +-- SUPRA --> Newly generated address | +-- No Account resource Expected: Transaction succeeds through 0x1::supra_account::transfer. Recipient account is created and SUPRA is received.

Test 3: New account with custom Coin<T>

Sender | +-- Coin<T> --> Newly generated address Expected: Transaction succeeds through 0x1::supra_account::transfer_coins<CoinType>. Recipient account and CoinStore<T> are initialized as required.

Test 4: Invalid address

Sender | +-- malformed input Expected: Address validation fails before transaction submission.

Test 5: Existing account that opted out

Existing account | +-- direct transfer of an unregistered Coin<T> Expected: Transfer can fail because the recipient explicitly disabled direct transfers of unregistered coins.

This test is important because “new account” and “existing account that rejects direct transfers” are different conditions.

Integration Checklist

Before shipping a Supra token-transfer integration:

  • 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

Account resource not found

Meaning: The address may be valid but has not been initialized on-chain.

Action: Validate the address format and use the appropriate account-aware transfer path.

CoinStore<T> not found

Meaning: The account exists but is not registered for the requested legacy Coin<T>.

Action: Use supra_account::transfer_coins<CoinType> when the recipient is expected to be able to receive direct transfers.

Direct transfer rejected

Meaning: The recipient may have explicitly disabled direct transfers of unregistered legacy coins.

Action: Do not treat this as an uninitialized-account problem. The recipient needs to allow or explicitly register the asset.

Native SUPRA transfer fails with a recipient registration error

Meaning: An integration may be bypassing the account-aware SUPRA transfer path.

Action: Use 0x1::supra_account::transfer for native SUPRA transfers.

Summary

The key integration rule is simple:

Valid address != Existing on-chain account

A newly generated Supra address can be a valid destination even before its Account resource exists.

For account-aware transfers:

AssetRecommended function
Native SUPRA0x1::supra_account::transfer
Legacy Coin<T>0x1::supra_account::transfer_coins<CoinType>
Strict/pre-registered Coin<T> transfer0x1::coin::transfer<CoinType> when the recipient is already prepared
Fungible AssetUse the appropriate FA transfer API for the asset

The most important implementation rule is:

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