diff --git a/docs/built-in-contracts/stellar-asset-contract.mdx b/docs/built-in-contracts/stellar-asset-contract.mdx new file mode 100644 index 00000000..5a6060f6 --- /dev/null +++ b/docs/built-in-contracts/stellar-asset-contract.mdx @@ -0,0 +1,186 @@ +--- +sidebar_position: 1 +title: Stellar Asset Contract +--- + +# Stellar Asset Contract + +The Stellar Asset Contract is an implementation of [CAP-46-6 Smart Contract Standardized Asset]. + +[CAP-46-6 Smart Contract Standardized Asset]: https://stellar.org/protocol/cap-46-06 + +:::caution +The Stellar Asset Contract is in early development, has not been audited, and is +intended for use in development and testing only at this stage. Report issues +[here](https://github.com/stellar/soroban-token-contract/issues/new/choose). +::: + +## Overview + +Stellar has numerous assets on its classic network, and being able to use them +in Soroban would give users much more flexibility with how they can use their +assets. For this reason, we introduced the Stellar Asset Contract, or SAC for +short, which will allow users to use their Stellar account and trustline +balances in Soroban. + +The SAC implements the [token interface](../common-interfaces/token.mdx), which is +similar to the widely used ERC-20 token standard. This should make it easier for +existing smart contract developers to get started on Stellar. + +## Deployment + +For every 'classic' asset exactly one respective Stellar Asset Contract can be +deployed. It can be deployed using the `InvokeHostFunctionOp` with +`HOST_FUNCTION_TYPE_CREATE_CONTRACT` and `CONTRACT_ID_FROM_ASSET` specified +[here](../tutorials/invoking-contracts-with-transactions). The resulting token +will have a deterministic identifier, which will be the sha256 hash of +`HashIDPreimage::ENVELOPE_TYPE_CONTRACT_ID_FROM_ASSET` xdr specified +[here](https://github.com/stellar/stellar-xdr/blob/026c9cd074bdb28ddde8ee52f2a4502d9e518a09/Stellar-transaction.x#L637). + +## Interacting with classic Stellar assets + +The Stellar Asset Contract is the only way to interact with 'classic' Stellar assets in +Soroban. 'Classic' assets include native Stellar token (lumens) and all the +existing trustlines. The issuer of the asset will be the +administrator of the deployed contract. Because the the Native Stellar token +doesn't have an issuer, it will not have an administrator either. It also cannot +be burned. + +After the contract has been deployed, users can use their classic account or +trustline balance. There are some differences depending on if you are using a +classic account identifier vs a non-account identifier like a contract. + +- Using `Identifier::Account` + - The balance must exist in a trustline (or an account for the native balance). + This means the contract will not store the balance in ContractData. If the + trustline or account is missing, any function that tries to interact with + that balance will fail. + - Classic trustline semantics will be followed. + - Transfers will only succeed if the corresponding + trustline(s) have the `AUTHORIZED_FLAG` set. + - A trustline balance can only be clawed back using the `clawback` + contract function if the trustline has `TRUSTLINE_CLAWBACK_ENABLED_FLAG` + set. + - The admin can only deauthorize a trustline if the issuer of the asset has + `AUTH_REVOCABLE` set. The deauthorization will fail if the issuer is + missing. Note that when a trustline is deauthorized from Soroban, + `AUTHORIZED_FLAG` is cleared and `AUTHORIZED_TO_MAINTAIN_LIABILITIES_FLAG` is + set to avoid having to pull offers and redeeming pool shares. + - Transfers to the issuer account will burn the token, while transfers from + the issuer account will mint. + - Trustline balances are stored in a 64-bit signed integer even though the + interface accepts 128-bit signed integers. Any operation that attempts to + send or recieve an amount more than the maximum amount that can be + represented by a 64-bit signed integer will fail. +- Using `Identifier::Ed25519` or `Identifier::Contract` + - The balance and authorization state will be stored in ContractData, as + opposed to a trustline. + - `AUTH_REVOCABLE` is not required to be set on the issuer to deauthorize a balance. + - These balances are stored in a 128-bit signed integer. + +## Authorization semantics + +See the [advanced auth example](../examples/auth-advanced) for an overview of +authorization. + +### SAC operations + +The token contract contains three kinds of operations + +- getters, such as `balance`, which do not change the state of the contract +- unprivileged mutators, such as `incr_allow` and `xfer`, which change the state of +the contract but do not require special privileges +- privileged mutators, such as `clawback` and `set_admin`, which change the state of +the contract but require special privileges + +Getters require no authorization because they do not change the state of the +contract and all contract data is public. For example, `balance` simply returns +the balance of the specified identity without changing it. + +Unprivileged mutators require authorization from some identity. The identity +which must provide authorization will vary depending on the unprivileged +mutator. For example, a "grantor" can use `incr_allow` to allow a "spender" to spend +the grantor's money up to some limit. So for `incr_allow`, the grantor must provide +authorization. Similarly, a "sender" can use `xfer` to send money to a +"recipient". So for `xfer`, the sender must provide authorization. + +Priviliged mutators require authorization from a specific privileged identity, +known as the "administrator". For example, only the administrator can `mint` more +of the asset. Similarly, only the administrator can appoint a new administrator. + +### Replay prevention + +The token contract provides replay prevention by using a [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce). +The messages that are signed to provide authorization contain a nonce. The +contract also stores a nonce per identity. When checking signatures, the +contract loads the nonce for the relevant identity. When an operation succeeds, +the nonce stored in the contract is incremented. This makes it impossible to +reuse a signature. + +The current nonce for an identity can be retrieved using the `nonce` contract +function. + +### Example: Invoker auth + +The easiest way to use the built-in token with classic accounts is to just use +the [invoker](../examples/auth.mdx#invoker) auth. In this way there won't be +a need to sign the contract payload, so the contract call would look like this: + +```rust +// `with_source_account` is a testing utility, but the contract call arguments +// would be the same for the real contract call too. +token.with_source_account(&token_admin_id).mint( + // No signature needed, just a flag that invoker auth should be used, i.e. + // `token_admin_id` in this case. + &Signature::Invoker, + // Nonce is always 0 for invokers. + &0, + &user_id, + &1000, +); +``` + +See a more complete example that uses invoker auth in the tests +[here](https://github.com/stellar/soroban-examples/blob/51a95262caba3f8ac466fa8bbc07004ad297ae13/timelock/src/test.rs#L60). + +### Example: Signing payloads + +The payload signature semantics is the same as for regular contracts using +[advanced auth](../examples/auth-advanced.mdx). The following snippet shows how +should the signature payload look like: + +```rust +let nonce = token.nonce(&token_admin_id); +// This is the test call, but the contract call arguments and signature payload +// would be the same for the real contract call too. +let sig = soroban_auth::testutils::ed25519::sign( + &env, + // Signer has the private key of the admin. + &token_admin_signer, + // Identifier of the token contract. + &token_contract_id, + // Name of the contract function we call. + symbol!("mint"), + // Arguments of the contract function call. + // Notice that instead of the signature (first `mint` argument), public key + // is used as the first argument here. + (&token_admin_id, &nonce, &user_id, &1000), +); +// Call the contract with signature we computed above. +token.mint( + &sig, + &nonce, + &user_id, + &1000, +); +``` + +## Contract Interface + +This interface can be found in the [SDK](https://github.com/stellar/rs-soroban-sdk/blob/main/soroban-token-spec/src/lib.rs). It +extends the common [token interface](../common-interfaces/token.mdx). + +## Interacting with the token contract in tests + +See [interacting with contracts in tests](../learn/interacting-with-contracts#interacting-with-contracts-in-tests) +for more general information on this topic. diff --git a/docs/built-in-contracts/token.mdx b/docs/built-in-contracts/token.mdx deleted file mode 100644 index 0bcb95e6..00000000 --- a/docs/built-in-contracts/token.mdx +++ /dev/null @@ -1,245 +0,0 @@ ---- -sidebar_position: 1 -title: Token Contract ---- - -# Token Contract - -The token contract is an implementation of [CAP-46-6 Smart Contract Standardized Asset]. - -[CAP-46-6 Smart Contract Standardized Asset]: https://stellar.org/protocol/cap-46-06 - -:::caution -The token contract is in early development, has not been audited, and is -intended for use in development and testing only at this stage.. Report issues -[here](https://github.com/stellar/soroban-token-contract/issues/new/choose). -::: - -## Overview - -Tokens are a vital part of blockchains, and contracts that implement token -functionality are inevitable on any smart contract platform. A built-in token -contract has a number of advantages over token contracts written by the -ecosystem. First, we can special case this contract and run it natively instead -of running in a WASM VM, reducing the cost of using the contract. Second, we -can use this built-in contract to allow "classic" Stellar assets to interoperate -with Soroban. Note that this standard token contract does not prevent the -ecosystem from developing other token contracts if the standard is missing -functionality they require. - -The standard token contract is similar to the widely used ERC-20 token standard, -which should make it easier for existing smart contract developers to get -started on Stellar. - -## Token contract authorization semantics - -See the [advanced auth example](../examples/auth-advanced) for an overview of -authorization. - -### Token operations - -The token contract contains three kinds of operations - -- getters, such as `balance`, which do not change the state of the contract -- unprivileged mutators, such as `approve` and `xfer`, which change the state of -the contract but do not require special privileges -- privileged mutators, such as `burn` and `set_admin`, which change the state of -the contract but require special privileges - -Getters require no authorization because they do not change the state of the -contract and all contract data is public. For example, `balance` simply returns -the balance of the specified identity without changing it. - -Unprivileged mutators require authorization from some identity. The identity -which must provide authorization will vary depending on the unprivileged -mutator. For example, a "grantor" can use `approve` to allow a "spender" to spend -the grantor's money up to some limit. So for approve, the grantor must provide -authorization. Similarly, a "sender" can use `xfer` to send money to a -"recipient". So for `xfer`, the sender must provide authorization. - -Priviliged mutators require authorization from a specific privileged identity, -known as the "administrator". For example, only the administrator can `mint` more -of the asset. Similarly, only the administrator can appoint a new administrator. - -### Replay prevention - -The token contract provides replay prevention by using a [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce). -The messages that are signed to provide authorization contain a nonce. The -contract also stores a nonce per identity. When checking signatures, the -contract loads the nonce for the relevant identity. When an operation succeeds, -the nonce stored in the contract is incremented. This makes it impossible to -reuse a signature. - -The current nonce for an identity can be retrieved using the `nonce` contract -function. - -### Example: Invoker auth - -The easiest way to use the built-in token with classic accounts is to just use -the [invoker](../examples/auth.mdx#invoker) auth. In this way there won't be -a need to sign the contract payload, so the contract call would look like this: - -```rust -// `with_source_account` is a testing utility, but the contract call arguments -// would be the same for the real contract call too. -token.with_source_account(&token_admin_id).mint( - // No signature needed, just a flag that invoker auth should be used, i.e. - // `token_admin_id` in this case. - &Signature::Invoker, - // Nonce is always 0 for invokers. - &0, - &user_id, - &1000, -); -``` - -See a more complete example that uses invoker auth in the tests -[here](https://github.com/stellar/soroban-examples/blob/51a95262caba3f8ac466fa8bbc07004ad297ae13/timelock/src/test.rs#L60). - -### Example: Signing payloads - -The payload signature semantics is the same as for regular contracts using -[advanced auth](../examples/auth-advanced.mdx). The following snippet shows how -should the signature payload look like: - -```rust -let nonce = token.nonce(&token_admin_id); -// This is the test call, but the contract call arguments and signature payload -// would be the same for the real contract call too. -let sig = soroban_auth::testutils::ed25519::sign( - &env, - // Signer has the private key of the admin. - &token_admin_signer, - // Identifier of the token contract. - &token_contract_id, - // Name of the contract function we call. - symbol!("mint"), - // Arguments of the contract function call. - // Notice that instead of the signature (first `mint` argument), public key - // is used as the first argument here. - (&token_admin_id, &nonce, &user_id, &1000), -); -// Call the contract with signature we computed above. -token.mint( - &sig, - &nonce, - &user_id, - &1000, -); -``` - -## Interacting with classic Stellar assets - -Token contract is the only way to interact with 'classic' Stellar assets in -Soroban. 'Classic' assets include native Stellar token (lumens) and all the -existing trustlines. - -For every 'classic' asset exactly one respective token contract can be deployed -via `create_token_from_asset` host function. The resulting token will have a -deterministic identifier. The issuer of the asset will be the administrator of -the deployed contract. Native Stellar token doesn't have an administrator. - -After the contract has been deployed the users can use `import` function to move -part of their existing balance to the contract or `export` to move the token -balance back to their 'classic' balance. Otherwise, the token will behave in -exactly the same way as any other token, i.e. users that don't have a -corresponding trustline or even a Stellar account can still use it (with the -exception of the `import`/`export` functions). - -## Contract Interface - -This interface can be found in the [SDK](https://github.com/stellar/rs-soroban-sdk/blob/47895a87b98a2fb8dd882839df7abde03f6c7fc6/soroban-token-spec/src/lib.rs#L27). It -extends the common [token interface](../common-interfaces/token.mdx) with -`import`/`export` functions for classic asset interactions. - -```rust -// The metadata used to initialize token (doesn't apply to contracts representing -// 'classic' Stellar assets). -pub struct TokenMetadata { - pub name: Bytes, - pub symbol: Bytes, - pub decimals: u32, -} - -// Initializes a 'smart-only' token by setting its admin and metadata. -// Tokens that represent 'classic' Stellar assets don't need to call this, as -// their metadata is inherited from the existing assets. -fn init(env: Env, admin: Identifier, metadata: TokenMetadata); - -// Functions that apply on for tokens representing classic assets. - -// Moves the `amount` from classic asset balance to the token balance of `id` -// user. -// `id` must be a classic Stellar account (i.e. an account invoker or signature -// signed by an account). -fn import(env: Env, id: Signature, nonce: i128, amount: i64); - -// Moves the `amount` from token balance to the classic asset balance of `id` -// user. -// `id` must be a classic Stellar account (i.e. an account invoker or signature -// signed by an account). -fn export(env: Env, id: Signature, nonce: i128, amount: i64); - -// Admin interface -- these functions are privileged - -// If "admin" is the administrator, burn "amount" from "from" -fn burn(e: Env, admin: Signature, nonce: i128, from: Identifier, amount: i128); - -// If "admin" is the administrator, mint "amount" to "to" -fn mint(e: Env, admin: Signature, nonce: i128, to: Identifier, amount: i128); - -// If "admin" is the administrator, set the administrator to "id" -fn set_admin(e: Env, admin: Signature, nonce: i128, new_admin: Identifier); - -// If "admin" is the administrator, freeze "id" -fn freeze(e: Env, admin: Signature, nonce: i128, id: Identifier); - -// If "admin" is the administrator, unfreeze "id" -fn unfreeze(e: Env, admin: Signature, nonce: i128, id: Identifier); - -// Token Interface - -// Get the allowance for "spender" to transfer from "from" -fn allowance(e: Env, from: Identifier, spender: Identifier) -> i128; - -// Set the allowance to "amount" for "spender" to transfer from "from" -fn approve(e: Env, from: Signature, nonce: i128, spender: Identifier, amount: i128); - -// Get the balance of "id" -fn balance(e: Env, id: Identifier) -> i128; - -// Transfer "amount" from "from" to "to" -fn xfer(e: Env, from: Signature, nonce: i128, to: Identifier, amount: i128); - -// Transfer "amount" from "from" to "to", consuming the allowance of "spender" -fn xfer_from( - e: Env, - spender: Signature, - nonce: i128, - from: Identifier, - to: Identifier, - amount: i128, -); - -// Returns true if "id" is frozen -fn is_frozen(e: Env, id: Identifier) -> bool; - -// Returns the current nonce for "id" -fn nonce(e: Env, id: Identifier) -> i128; - -// Descriptive Interface - -// Get the number of decimals used to represent amounts of this token -fn decimals(e: Env) -> u32; - -// Get the name for this token -fn name(e: Env) -> Bytes; - -// Get the symbol for this token -fn symbol(e: Env) -> Bytes; -``` - -## Interacting with the token contract in tests - -See [interacting with contracts in tests](../learn/interacting-with-contracts#interacting-with-contracts-in-tests) -for more general information on this topic. diff --git a/docs/common-interfaces/token.mdx b/docs/common-interfaces/token.mdx index 51e26490..1bbccb95 100644 --- a/docs/common-interfaces/token.mdx +++ b/docs/common-interfaces/token.mdx @@ -3,7 +3,7 @@ sidebar_position: 1 title: Token Interface --- -Token contracts, including the built-in token and example token implementations +Token contracts, including the Stellar Asset Contract and example token implementations expose the following common interface. Tokens deployed on Soroban can implement any interface they choose, however, @@ -12,16 +12,13 @@ built to support Soroban's built-in tokens. ```rust pub trait Contract { - /// Sets the administrator to "admin" and metadata. - fn init(env: soroban_sdk::Env, admin: soroban_auth::Identifier, metadata: TokenMetadata); - // -------------------------------------------------------------------------------- // Admin interface – privileged functions. // -------------------------------------------------------------------------------- - /// If "admin" is the administrator, burn "amount" from "from". - /// Emit event with topics = ["burn", from: Identifier, to: Identifier], data = [amount: i128] - fn burn( + /// If "admin" is the administrator, clawback "amount" from "from". "amount" is burned. + /// Emit event with topics = ["clawback", from: Identifier, to: Identifier], data = [amount: i128] + fn clawback( env: soroban_sdk::Env, admin: soroban_auth::Signature, nonce: i128, @@ -48,22 +45,15 @@ pub trait Contract { new_admin: soroban_auth::Identifier, ); - /// If "admin" is the administrator, freeze "id". - /// Emit event with topics = ["freeze", admin: Identifier], data = [id: Identifier] - fn freeze( - env: soroban_sdk::Env, - admin: soroban_auth::Signature, - nonce: i128, - id: soroban_auth::Identifier, - ); - - /// If "admin" is the administrator, unfreeze "id". - /// Emit event with topics = ["unfreeze", admin: Identifier], data = [id: Identifier] - fn unfreeze( + /// If "admin" is the administrator, set the authorize state of "id" to "authorize". + /// If "authorize" is true, "id" should be able to use its balance. + /// Emit event with topics = ["set_auth", admin: Identifier, id: Identifier], data = [authorize: bool] + fn set_auth( env: soroban_sdk::Env, admin: soroban_auth::Signature, nonce: i128, id: soroban_auth::Identifier, + authorize: bool, ); // -------------------------------------------------------------------------------- @@ -77,9 +67,20 @@ pub trait Contract { spender: soroban_auth::Identifier, ) -> i128; - /// Set the allowance to "amount" for "spender" to transfer from "from". - /// Emit event with topics = ["approve", from: Identifier, spender: Identifier], data = [amount: i128] - fn approve( + /// Increase the allowance by "amount" for "spender" to transfer/burn from "from". + /// Emit event with topics = ["incr_allow", from: Identifier, spender: Identifier], data = [amount: i128] + fn incr_allow( + env: soroban_sdk::Env, + from: soroban_auth::Signature, + nonce: i128, + spender: soroban_auth::Identifier, + amount: i128, + ); + + /// Decrease the allowance by "amount" for "spender" to transfer/burn from "from". + /// If "amount" is greater than the current allowance, set the allowance to 0. + /// Emit event with topics = ["decr_allow", from: Identifier, spender: Identifier], data = [amount: i128] + fn decr_allow( env: soroban_sdk::Env, from: soroban_auth::Signature, nonce: i128, @@ -90,6 +91,11 @@ pub trait Contract { /// Get the balance of "id". fn balance(env: soroban_sdk::Env, id: soroban_auth::Identifier) -> i128; + /// Get the spendable balance of "id". This will return the same value as balance() + /// unless this is called on the Stellar Asset Contract, in which case this can + /// be less due to reserves/liabilities. + fn spendable(env: soroban_sdk::Env, id: soroban_auth::Identifier) -> i128; + /// Transfer "amount" from "from" to "to. /// Emit event with topics = ["transfer", from: Identifier, to: Identifier], data = [amount: i128] fn xfer( @@ -111,8 +117,27 @@ pub trait Contract { amount: i128, ); - // Returns true if "id" is frozen. - fn is_frozen(env: soroban_sdk::Env, id: soroban_auth::Identifier) -> bool; + /// Burn "amount" from "from". + /// Emit event with topics = ["burn", from: Identifier], data = [amount: i128] + fn burn( + env: soroban_sdk::Env, + from: soroban_auth::Signature, + nonce: i128, + amount: i128, + ); + + /// Burn "amount" from "from", consuming the allowance of "spender". + /// Emit event with topics = ["burn", from: Identifier], data = [amount: i128] + fn burn_from( + env: soroban_sdk::Env, + spender: soroban_auth::Signature, + nonce: i128, + from: soroban_auth::Identifier, + amount: i128, + ); + + // Returns true if "id" is authorized to use its balance. + fn authorized(env: soroban_sdk::Env, id: soroban_auth::Identifier) -> bool; // Returns the current nonce for "id". fn nonce(env: soroban_sdk::Env, id: soroban_auth::Identifier) -> i128; @@ -133,17 +158,7 @@ pub trait Contract { ``` Most types that a token implementation utilizes are provided by the -[soroban-sdk] and [soroban-auth] crates. The following type is also used during -initialization. - -```rust -#[soroban_sdk::contracttype] -pub struct TokenMetadata { - pub name: soroban_sdk::Bytes, - pub symbol: soroban_sdk::Bytes, - pub decimals: u32, -} -``` +[soroban-sdk] and [soroban-auth] crates. [soroban-sdk]: ../SDKs/rust [soroban-auth]: ../SDKs/rust-auth diff --git a/docs/faq.mdx b/docs/faq.mdx index 34fca461..7b69ad19 100644 --- a/docs/faq.mdx +++ b/docs/faq.mdx @@ -17,12 +17,11 @@ A Soroban contract can be invoked by submitting a transaction that contains the Yes. Stellar accounts are shared with Soroban. Smart contacts have access to Stellar account signer configuration and know the source account that directly invoked them in a transaction. Check out the Auth and Advanced Auth examples for more information. ### Can Soroban contracts interact with Stellar Assets? -Yes. Soroban contains a built-in token contract that is able to import a balance from a Stellar trust line into an equivalent Soroban balance and also to export it back to the Stellar trust line. Check out the built-in token for more information on token capabilities, and the token interface for building your own tokens with custom behaviors. -*Note 1: This means that one account can simultaneously have two balances of the same asset: once in a Stellar trust line and one in a Soroban token balance. This bears some similarity to the relationship between ETH and WETH on Ethereum.* -*Note 2: Tokens that have been issued on Soroban can not be exported to a Stellar trust line.* +Yes. Soroban contains a built-in Stellar Asset Contract that is able to interact +with classic trustlines. Read more about this [here](built-in-contracts/stellar-asset-contract.mdx). -### Do Issuers of Stellar Assets maintain their authorization over an asset that has been imported to Soroban? (AUTH_REQUIRED, AUTH_REVOCABLE, AUTH_CLAWBACK) -Yes. Issuers retain the same level of control on Soroban as they have on Classic . This functionality is accessible through a set of admin functions (freeze, burn) on the built in Soroban token contract. +### Do Issuers of Stellar Assets maintain their authorization over an asset that has been sent to a non-account identifer in Soroban? (AUTH_REQUIRED, AUTH_REVOCABLE, AUTH_CLAWBACK) +Yes. Issuers retain the same level of control on Soroban as they have on Classic. This functionality is accessible through a set of admin functions (clawback, set_auth) on the built-in Stellar Asset Contract. ### Can Soroban contracts interact with any other Stellar operations? No. Aside from the interactions with Accounts and Assets as mentioned above. This means that Soroban contracts can not interact with SDEX, AMMs, Claimable Balances or Sponsorships. @@ -30,11 +29,11 @@ No. Aside from the interactions with Accounts and Assets as mentioned above. Thi ### Does the Stellar base reserve apply to Soroban contracts? No. Soroban has a different fee structure and ledger entries that are allocated by Soroban contracts do not add to an account's required minimal balance. -### Should I issue my token as a Stellar Asset or a Soroban token? -We recommend, to the extent possible, issuing tokens as Stellar assets. These tokens will benefit from being interopable with the existing ecosystem of tools available in the Stellar ecosystem, and will be usable on Soroban through import/export functionality. - -### How should Wallets handle Accounts having a Stellar and Soroban balance? -Best practices are still being explored. At this time we recommend: (1) displaying separate balances for Soroban and Stellar Assets, (2) allowing users to import/export between them and (3) executing payments based on the origin: Stellar payments for trust lines and Soroban token transfers for Soroban token balances +### Should I issue my token as a Stellar Asset or a custom Soroban token? +We recommend, to the extent possible, issuing tokens as Stellar assets. These +tokens will benefit from being interopable with the existing ecosystem of tools +available in the Stellar ecosystem, as well as being more performant because the +Stellar Asset Contract is built into the host. ### Haven't found what you're looking for? Join #soroban on the Stellar developer discord diff --git a/docs/tutorials/invoking-contracts-with-transactions.mdx b/docs/tutorials/invoking-contracts-with-transactions.mdx index ce480a32..3d8aafff 100644 --- a/docs/tutorials/invoking-contracts-with-transactions.mdx +++ b/docs/tutorials/invoking-contracts-with-transactions.mdx @@ -11,7 +11,7 @@ operations: - Invoke contract functions. - Install WASM of the new contracts. - Deploy new contracts using the installed WASM or built-in implementations ( - this currently includes only the [token contract](../built-in-contracts/token.mdx)). + this currently includes only the [token contract](../built-in-contracts/stellar-asset-contract.mdx)). [`soroban-cli`]: ../getting-started/setup#install-the-soroban-cli