Skip to main content

Deploying the contract

An Equillar contract instance represents one financing project with its own immutable economic setup.

Clone the equillar contract

Clone the equillar contract using git:

git clone https://github.com/icolomina/equillar-soroban.git

Then you can go into the project folder and execute the tests:

cd contracts/investment-income-based
cargo test

Dependency issue: ed25519-dalek version conflict

If you see an error like this while building or testing:

error[E0277]: the trait bound ChaCha20Rng: ed25519_dalek::rand_core::CryptoRng is not satisfied

Fix it by running:

cargo update -p ed25519-dalek@3.0.0 --precise 2.2.0

Creating an stellar identity

Before deploying, you need a local identity to act as the source account for the upload and deploy commands. stellar keys generate creates a new keypair and stores it locally under the given alias (admin in this case), while stellar keys fund requests test funds (via Friendbot) to activate that account on the network — this only works on testnet; on mainnet the account must be funded manually.

stellar keys generate admin
stellar keys fund admin

Build the contract

For building the contract and generating the wasm file, proceed as follows from the "contracts/investment-income-based" folder:

stellar contract build

After the command finishes, you will find the wasm file under the folder "target/wasm32v1-none/release"

Upload the contract

Now, before deploying an instance of the contract, we need to install it in the network:

stellar contract upload \
--wasm target/wasm32v1-none/release/<your_wasm_filename>.wasm \
-s admin \
--network testnet

After the command finishes, you will get the contract wasm hash that you will use later to deploy an instance of the contract.

The -s admin flag (short for --source-account) tells the CLI to sign and submit this transaction using the admin identity created above.

Deploy the contract

Contract deployment parameters

The equillar contract __construct method must receive the following parameters:

ParameterTypeDescription
owner_addrAddressAddress of the contract's owner/administrator. The admin controls every capital flow in the contract except deposit inflows, which must be signed by addresses holding the operator role. The role system is covered in detail in the following section.
token_addrAddressAddress of the token (SEP-41 / CAP-46) used to handle all financial flows (deposits, payouts, returns) within the contract.
price_oracleAddressAddress of the price oracle contract used to value the collateral token against the contract's token, enabling calculation of the collateral coverage level. Covered in detail in the collateral section below.
investment_paramsInvestmentContractParamsStruct holding the economic and time-related parameters (see next section).

Note about the token addr: Equillar is designed around stablecoins as the settlement token (token_addr). Returns are meant to reflect the performance of the underlying financed project — not the price behavior of the token used to move capital. Using a volatile asset instead would introduce a source of risk entirely unrelated to the investment itself, undermining the predictability that structured financing products like are meant to provide. Since stablecoins are typically regulated and pegged to a fiat currency, they keep that additional volatility out of the equation.

Investment Params

FieldTypeDescription
i_rateu32Interest/return rate offered to investors. It is represented in basis points or as a scaled integer (e.g. 500 = 5.00%)
fundraising_daysu64Duration in days of the fundraising phase.
claim_block_daysu64Number of days during which claims are blocked/unavailable, typically counted after the fundraising period closes.
goali128Fundraising goal, expressed using the number of decimals defined by the token. For example, for a token with 7 decimals, to express 1 unit you must send 1 * 10^7 = 10000000.
return_typeu32Payment scheme used for investor returns: 1 = ReverseLoan, 2 = Coupon. ReverseLoan: capital plus gains are distributed together across the configured months. Coupon: gains are paid out first across the configured months, with the capital returned in the final payment (bond-like). See the Payment Distribution to Investors section for further details.
return_monthsu32Number of months over which the return is distributed to investors.
min_per_investmenti128Minimum amount allowed per individual investment, expressed using the number of decimals defined by the token. For example, for a token with 7 decimals, to express 1 unit you must send 1 * 10^7 = 10000000.
cmr_upper_divisoru32Maximum divisor that can be applied to reduce the commission
cmr_lower_divisoru32Minimum divisor applicable for calculating the commission
cmr_reductori128Number of token units invested, by which the divisor increases by one

Note on cmr_ parameters: These parameters will be explained in greater detail in Section Fundraising and positions.

Note on claim_block_days: The appropriate length of this window is up to the integrator and typically depends on the nature of the financed project. Project type matters — financing a truck fleet (operational quickly) differs from financing a solar plant (longer build-out, more exposure to technical/bureaucratic delays). Company profile matters too — fast-growing, cash-intensive companies (e.g. startups) generally warrant longer lock periods, reflecting their higher risk profile, compared to more established businesses.

Deployment example with stellar-cli

From the CLI version that supports constructors (protocol ≥ 22), stellar contract deploy accepts constructor arguments after --, the same way stellar contract invoke does. Simple types (Address, String, u32, u64) are passed as plain text; custom structs are passed as a JSON object whose keys match the struct's field names.

stellar contract deploy \
--wasm-hash <hash>\
-s admin \
--network testnet \
-- \
--admin_addr GBOWNERXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \
--token_addr CATOKENXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \
--price_oracle COAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX \
--investment_params '{
"i_rate": 500,
"claim_block_days": 180,
"fundraising_days": 60,
"goal": "10000000000000",
"return_type": 2,
"return_months": 12,
"min_per_investment": "1000000000",
"cmr_upper_divisor" : 60,
"cmr_lower_divisor" : 10,
"cmr_reductor" : "4000000000"
}'

Pay special attention to the goal, min_per_investment and cmr_reductor parameters: they are scaled according to the token's decimals (7 decimals in this example).

  • goal: 1000000_0000000 → one million tokens.
  • min_per_investment: 100_0000000 → a minimum investment of one hundred tokens.

Both fields are i128, so they must be passed as strings in the JSON payload (not as plain numbers), e.g. "goal": "10000000000000".

For getting the public address (G....) of the admin_addr parameter you can derive it from the admin identity we've created before uploading the contract.

stellar keys public-key admin

Balance and Events

Balance

Equillar tracks the contract's financial state through a single ContractBalance structure, which is updated after every relevant operation (investments, payments, withdrawals, collateral movements, refunds, emergency payouts, etc.). Understanding what each field represents makes it easier to follow how the balance evolves throughout the rest of this document.

FieldMeaning
reserveFunds contributed by the company and currently available to pay investors. Consumed by regular and emergency payments.
projectFunds raised from investors that belong to the project/company, net of commissions. Consumed by company withdrawals and refunds.
comissionTotal commission accrued from investments. Reduced when a refund reverses a position.
comission_withdrawalTotal commission amount already withdrawn by the integrator.
paymentsTotal amount paid out to investors so far, through regular payment rounds or emergency payouts.
project_withdrawalsTotal amount withdrawn by the company from the project balance.
payment_obligationsTotal amount still owed to investors across all active positions.
collateral_receivedTotal collateral amount deposited into the contract.
collateral_liquidatedTotal collateral amount liquidated to cover payment obligations.
collateral_returnedTotal collateral amount returned back to the company.
refunded_to_investorTotal amount refunded to investors due to fundraising cancellations.

The balance can be queried at any time through the read-only function get_contract_balance(env: Env, caller: Address). This function requires the caller to hold one of the following roles: operator, company, manager, or admin.

stellar contract invoke \
--id <YOUR_CONTRACT_ID> \
-s admin \
--network testnet \
-- \
get_contract_balance \
--addr "G...."

The following subsections walk through each stage of the contract's lifecycle, showing which event is emitted and how the balance changes at each step.

Events after deployment

Right after a successful deployment, the contract emits a ContractDeployed event:

ContractDeployed {
addr, // the address of the newly deployed contract
ts_fundraising_ends, // timestamp when the fundraising period closes
ts_payments_start, // timestamp when the payment schedule begins
}

This event lets integrators track new deployments and their key dates without having to poll the contract state directly.

At this point, the balance is fully zero-initialized — no investments, payments, collateral, or withdrawals have taken place yet:

reserve = 0
project = 0
comission = 0
comission_withdrawal = 0
payments = 0
project_withdrawals = 0
payment_obligations = 0
collateral_received = 0
collateral_liquidated = 0
collateral_returned = 0
refunded_to_investor = 0

Reading events with stellar-cli

Contract events can be queried directly using the stellar events command, filtering by contract ID and, optionally, by topic:

stellar events \
--id \
--network testnet \
--start-ledger <LEDGER_NUMBER> \
--type contract

Events emitted with #[contractevent] automatically publish the event's name as part of their topics, along with any field explicitly marked with #[topic]. This means a query can be narrowed down to only ContractDeployed events using --topic:

stellar events \
--id <CONTRACT_ID> \
--network testnet \
--start-ledger <LEDGER_NUMBER> \
--topic "ContractDeployed,*" \
--type contract