Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Debt Algorand Standard Application

version = "0.21.0"

Smart Financial Contract

A standard for debt instruments tokenization on Algorand

The Debt Algorand Standard Application (D-ASA) is a standard for tokenizing debt instruments on the Algorand Virtual Machine.

It provides a framework for arranging the contract, configuring its role-based access control, issuing and distributing it on the primary market, executing cash flows, exchanging it on the secondary market, and querying information about the debt instrument.

The specification complies with the Algorithmic Contract Types Unified Standards (ACTUS) for the definition of the contracts.

D-ASA is, in essence, a full tokenization framework for ACTUS-compliant debt instruments, issued and executed on the Algorand Virtual Machine.

The specification allows the tokenization of various debt instruments, such as bonds, loans, commercial papers, mortgages, etc.

The reference implementation of some fixed income contracts is provided.

This document is a technical specification, it is not intended to be a legal or a financial document.

Contents

Contents are organized in three hierarchical levels (see the navigation sidebar on the left):

Part
└── 1. Chapter
    └── 1.1. Section
        └── 1.1.1. Sub-section

The navigation sidebar can be folded up to the Chapter level by clicking the folding icon (>), next to the level name.

Contributing

The D-ASA is free and open source.

The source code is released on the official GitHub repository.

External contributions are welcome, the project relies on the community to improve and expand.

Issues and feature requests can be submitted on the GitHub issues page.

If you would like to contribute, please read the guidelines and consider submitting a pull request.

License

The D-ASA source and documentation are released under the AGPL-3.0 license.

Motivation

Debt instruments represent one of the biggest asset classes (along with equities, commodities, and real estate). Debt instruments are investment contracts between borrowers and lenders, used to raise capital with binding obligations between the parties, who agree on the payoff and the cash flows (payments schedule, interest rates, maturity, etc.).

The definition of a comprehensive specification for the tokenization of debt instruments benefits several players of the traditional value chain, such as issuers, arrangers, asset managers, risk managers, lenders, payment agents, transfer agents, etc.

The Debt Algorand Standard Application turns a traditional debt instrument into a deterministic ACTUS financial contract, executed on the Algorand Virtual Machine (AVM).

The machine-readable and executable contract removes existing frictions over the debt instruments lifecycle and reconciliation, enabling use cases such as truly atomic delivery-vs-payment (with instant finality and no counterparty risk), deterministic cash flows analysis (when, how much, to whom), and easier quantitative risk management.

Definitions

The keywords “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

The data types (like uint64, byte[], etc.) in this document are to be interpreted as specified in ARC-4.

Acronyms in square brackets like \( [THIS] \) are to be interpreted as defined in the ACTUS dictionary.

Notes like this are non-normative

Non-normative sections mainly contextualize the specification for technical readers unfamiliar with the financial concepts described.

Tip

Sections like this are examples aiming to clarify the specifications.

Sections like this are either pseudo-code or formal examples.

Math Symbols

For a correct rendering of mathematical symbols and formulas, it is recommended to right-click on the symbol below, and select Math Settings -> Math Renderer -> Common HTML from the drop-down menu.

$$ \mathcal{C} $$

Once MathJax rendering is correctly set, you should see a calligraphic “C”.

Overview

A Debt Algorand Standard Application (D-ASA) is an Algorand application that executes a fixed-income ACTUS contract on the AVM.

A conforming D-ASA MUST process a debt instrument through the following stages:

  1. Define the debt instrument as an ACTUS contract.

  2. Normalize the ACTUS contract into AVM-compatible integers, state, and schedule pages.

  3. Execute the normalized contract on the AVM through explicit ABI methods.

The D-ASA therefore uses the AVM as the execution layer of ACTUS. The canonical contract interface is the normalized ACTUS interface defined in this specification.

This specification is structured in four layers:

  1. RBAC: identifies operational actors and their authorities.

  2. ACTUS Kernel: defines the normalized contract terms, schedule, and lifecycle state machine.

  3. Accounting: defines holder positions, unit balances, checkpoints, and claims.

  4. Execution: executes the ACTUS cashflows and tokenized contract transfers.

    • Payment Agent: defines funding and withdrawal of due ACTUS cashflows.

    • Transfer Agent: defines primary distribution and secondary transfer execution.


flowchart TD
  RBAC["Layer 1: RBAC<br/>Operational actors & authorities"]
  KERNEL["Layer 2: ACTUS Kernel<br/>Contract terms, schedule & state machine"]
  ACCOUNTING["Layer 3: Accounting<br/>Positions, balances, checkpoints & claims"]
  PAYMENT["Layer 4a: Payment Agent<br/>Funding & withdrawal of cashflows"]
  TRANSFER["Layer 4b: Transfer Agent<br/>Primary distribution & secondary transfers"]

  RBAC --> KERNEL
  KERNEL --> ACCOUNTING
  ACCOUNTING --> PAYMENT
  ACCOUNTING --> TRANSFER

  style RBAC fill:#bbdefb,stroke:#1976d2,stroke-width:2px,color:#000
  style KERNEL fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
  style ACCOUNTING fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
  style PAYMENT fill:#ffccbc,stroke:#d84315,stroke-width:2px,color:#000
  style TRANSFER fill:#ffccbc,stroke:#d84315,stroke-width:2px,color:#000

Conformance

A conforming implementation:

  • MUST implement the public ABI described in the Interfaces section;

  • MUST accept normalized ACTUS terms, an initial kernel state, and a paged execution schedule;

  • MUST execute ACTUS non-cash and cash events through the kernel and agent interfaces;

  • MUST follow the ACTUS compliance profile defined in the Contract section.

ACTUS compliance

D-ASA is designed to be ACTUS-compliant, with three minor deviations required by AVM constraints:

ACTUSD-ASA
Time formatISO 8601UNIX
Time precisionMillisecond \( 10^{-3} [s] \)Second \( [s] \)
ArithmeticFloating-pointFixed-point

The ACTUS compliance has not yet been certified by an official ACTUS standardization body.

Trust Model

The trust model defines who is allowed to configure, operate, suspend, and observe the D-ASA lifecycle.

The canonical role set and the RBAC enforcement rules are specified in the following pages.

Roles

D-ASA defines custom roles and permissions for the entities involved in the debt instrument.

The D-ASA MUST define contract roles \( [CNTRL] \).

The D-ASA MUST identify operational authorities with reserved role identifiers.

The contract roles MUST be associated with Algorand Addresses through a role key of the form:

[R:||<role key>||#||<role address>]

Where || denotes concatenation.

The current reference implementation uses the following role set:

KeyIDRoleScope
ARR20ArrangerOwns contract creation, configuration, schedule upload, and upgrade authority
OPD25Op DaemonOptional automation address for payment execution workflows
MNG40Account ManagerOpens holder accounts
PYD50Primary DealerAllocates units during primary distribution
TRS60TrusteeSets or clears the contract default-performance flag
AUT70AuthoritySuspends the contract or individual accounts
MOC80ObserverApplies rate-reset events that depend on observed data

Tip

The Arranger with Algorand Address XYZ is identified as R:ARR#XYZ.

Role model

The Arranger and Op Daemon are single-address global roles.

The Account Manager, Primary Dealer, Trustee, Authority, and Observer roles are time-bounded assignments. A role assignment is active only if:

role_validity_start <= current_block_timestamp <= role_validity_end

Every stored assignment window MUST be strictly ordered:

role_validity_start < role_validity_end

The D-ASA MUST reject actions that require an inactive role assignment.

Time-bounded role addresses MUST NOT be the global zero address.

Responsibilities

Issuer (Borrower)

Issuers are individuals, companies, institutions, governments, or other entities who borrow capital by issuing a debt.

The Issuer \( [RPL] \) is a meta-role in the D-ASA role model, they have no specific permissions.

Arranger

Arrangers are legal entities authorized to arrange debt instruments on behalf of the issuers.

The Arranger owns an Algorand Address.

That address MUST NOT be the Algorand global zero address.

The Arranger MUST be able to:

  • create the contract;
  • upload normalized ACTUS terms and schedule pages;
  • execute IED;
  • append or apply arranger-controlled observed events;
  • rotate the arranger address;
  • assign and revoke time-bounded roles;
  • update the application.

Op Daemon

The Op Daemon is an optional execution helper. If configured, the Op Daemon MAY trigger due cashflow funding together with the Arranger and trigger holder cashflow claims in addition to the holder or payment address.

Account Manager

The Account Manager owns an Algorand Address.

The Account Manager MUST be able to open holder accounts. The role does not control payments or transfers.

The right to open lender accounts can be granted to different entities, such as KYC providers or banks.

Primary Dealer

The Primary Dealer MUST be able to reserve units during primary distribution before IED.

Trustee

The Trustee role MUST control the contract performance \( [PRF] \) (see Performance section for further details).

In the current reference implementation, an active Trustee can set or clear the contract-level defaulted performance flag with rbac_contract_default.

This performance flag is distinct from the kernel lifecycle status.

Authority

The Authority owns an Algorand Address.

The Authority MUST be able to suspend the contract and individual holder accounts.

Observer

Debt instruments may rely on external data, such as interest rates, covenant breaches, etc., provided by trusted oracles.

The Observer \( [MOC] \) owns an Algorand Address.

The Observer MUST be able to apply due rate-reset events (RR, RRF) whose execution depends on observed external values.

Role-Based Access Control

The D-ASA role model MUST gate every privileged ABI method.

Role administration

The Arranger MUST control role administration through:

  • rbac_assign_role
  • rbac_revoke_role
  • rbac_get_role_validity
  • rbac_get_address_roles

Role assignments MUST be validated against their stored time window at execution time. A role that exists but is outside its validity interval MUST be treated as inactive.

When assigning a time-bounded role, the target address MUST NOT be the global zero address and the supplied validity interval MUST satisfy role_validity_start < role_validity_end.

Arrangement

The Arranger MUST retain the authority to:

  • rotate the arranger address with rbac_rotate_arranger;
  • set the optional operation daemon with rbac_set_op_daemon;
  • update the application with contract_update.

The arranger slot MUST NOT be set to the Algorand global zero address during contract creation or later rotation, because doing so would lock out arranger-only operations.

Suspension

Debt instruments are regulated under different legal frameworks and their jurisdictions.

The D-ASA ensures efficient execution of the debt instrument in the “best case scenarios”, where it offers the highest improvements in cost and time efficiency if compared to the traditional, manual, and labor-intensive contracts.

The D-ASA provides methods to comply with regulatory obligations, allowing the management of the “worst case scenarios”, in which the intervention of the authority or the regulator is necessary.

Debt instruments can be temporarily suspended due to regulations or operational reasons.

The D-ASA suspension authority MUST be restricted to specific contract roles.

Contract suspension

rbac_contract_suspension MUST suspend or resume contract-wide operations.

When the contract is suspended, the implementation MUST reject:

  • primary distribution;
  • funding and claiming of due cashflows;
  • holder payment-address updates;
  • transfers;
  • any other method explicitly guarded by the suspension flag.

Account suspension

account_suspension MUST suspend or resume a specific holder account.

When an account is suspended, the implementation MUST reject:

  • unit allocations to that account during primary distribution;
  • transfers from or to that account;
  • on-chain cashflow execution to that account while the suspension remains in force.

Trustee-controlled default

rbac_contract_default MUST be restricted to the Trustee role.

In the current reference implementation, this method sets or clears the contract-level defaulted performance flag in RBAC global state. It does not change the kernel lifecycle status.

Observer-controlled events

The Observer role MUST authorize due RR and RRF event application through apply_non_cash_event.

All other non-cash events remain arranger-controlled unless a future profile explicitly states otherwise.

Contract

The contract section defines the ACTUS compliance profile, the normalized kernel state, the schedule model, and the AVM configuration flow.

The canonical execution chain is:

ACTUS contract -> AVM normalization -> AVM execution

Contract

Financial contracts are legal agreements between two (or more) counterparties on the exchange of future cash flows. Debt instruments are a subset of financial contracts.

This section specifies the D-ASA ACTUS contract layer.

A conforming D-ASA MUST express the debt instrument as:

  1. ACTUS contract attributes;

  2. Normalized ACTUS terms, initial kernel state, and execution schedule for the AVM;

  3. Explicit AVM execution of due ACTUS events.

The canonical execution chain is, therefore:

flowchart LR
  ACTUS["ACTUS Contract"]
  NORMALIZE["AVM Normalization"]
  EXEC["AVM Execution"]

  ACTUS --> NORMALIZE
  NORMALIZE --> EXEC

  style ACTUS fill:#fff9c4,stroke:#f57f17,stroke-width:2px,color:#000
  style NORMALIZE fill:#c8e6c9,stroke:#388e3c,stroke-width:2px,color:#000
  style EXEC fill:#ffccbc,stroke:#d84315,stroke-width:2px,color:#000

The following pages define:

  • The supported ACTUS compliance profile;
  • The normalized on-chain state and schedule model;
  • The contract normalization and configuration flow;
  • The numeric representation rules required to move ACTUS values onto the AVM.

ACTUS Compliance Profile

Debt instruments such as bullet bonds, amortizing loans, mortgages, etc. differ based on their cash flow exchange patterns (e.g., principal and interest payment time schedules, fixed or variable interest rates, etc.).

The ACTUS taxonomy reduces the majority of all financial contracts to a defined set of 32 generalized cash flow exchange patterns, called contract types.

The D-ASA MUST be classified with an ACTUS contract type \( [CT] \) (see the ACTUS taxonomy).

The contract type MUST have the following properties:

  • family: Basic
  • class: Fixed Income
  • sub-class: Maturities

Contract Identifier

The D-ASA contract identifier \( [CID] \) is the Algorand Application ID (uint64).

The D-ASA contract layer MUST follow a constrained ACTUS fixed-income profile that can be normalized and executed on the AVM.

Supported contract families

The current kernel supports the following ACTUS contract family identifiers:

IDContract TypeDescriptionRateUse case
0PAMPrincipal payment fully at \( [IED] \) and repaid at \( [MD] \).Fix or variable ratesAll kind of bonds, term deposits, bullet loans, mortgages, etc.
1ANNPrincipal payment fully at \( [IED] \) and repaid periodically in constants amounts till \( [MD] \).Fix or variable ratesClassical level payment mortgages, leasing contracts, etc.
2NAMAs ANN, when resetting rate total amount (principal + interest) stays constant. \( [MD] \) shifts.Variable onlyAdjustable rate mortgages
3LAMPrincipal payment fully at \( [IED] \) and repaid periodically in constants amounts till \( [MD] \), interest reduced accordingly.Fix or variableAmortizing loans
4LAXFlexible version of LAM.Fix or variableTeaser rate loans
5CLMLoans rolled over as long as they are not called. Once called, it has to be paid back after noticed period.Fix or variableLoans with call options

Non-normative subtypes such as PAM:ZCB and PAM:FCB are resolved before normalization. The AVM kernel stores only the normalized family identifier.

Supported event types

The normalized execution schedule MUST contain only event types permitted for the configured contract family.

FamilyAllowed events
PAMIED, IP, MD, RR, RRF
ANNIED, IP, PR, MD, RR, RRF, IPCB, PRF
NAMIED, IP, PR, MD, RR, RRF, IPCB
LAMIED, IP, PR, MD, RR, RRF, IPCB
LAXIED, IP, PR, PI, MD, RR, RRF, IPCB, PRF
CLMIED, IP, PR, MD, RR, RRF
  • Cash events are limited to IP, PR, and MD.

  • Non-cash events are limited to IED, PI, RR, RRF, IPCB, and PRF.

Normalization constraints

The D-ASA profile further requires:

  • business_day_convention = NOS;
  • calendar = NC;
  • One of the supported day-count conventions listed in Day-Count Convention;
  • AVM-compatible uint64 values for amounts, times, and fixed-point factors.

Normalization MUST reject any contract that cannot satisfy those constraints.

Observed events

Observed schedule extension is intentionally narrow:

  • Only CLM contracts may append observed events at runtime;

  • append_observed_cash_event only accepts observed PR cash events;

  • apply_non_cash_event applies due RR and RRF events under Observer control;

  • Arranger-controlled observed appends MUST preserve event ordering and contiguous event IDs.

Denomination

Debt instruments are denominated in a currency, in which principal and interests are calculated.

The D-ASA MUST be denominated either in an on-chain or off-chain denomination asset \([CUR]\)1.

All values (uint64) are integer minor units of the relevant on-chain or off-chain denomination asset.

The reference implementation supports only on-chain ASA denominations (e.g., “stablecoins”).

On-chain denomination

The denomination asset MUST be an Algorand Standard Asset (ASA), an Application asset (App), or the ALGO.

The denomination asset identifier MUST be the ASA ID, the App ID, or 0 for ALGO.

If asset is ALGO (0): amount is in microALGOs (\( 10^{-6} \) ALGO).

If asset is ASA or App: amount is in base units as per that asset’s decimals.

Tip

The value (uint64) 10000 of an ASA denomination with 2 decimals is interpreted as 100.00 units of the ASA.

Off-chain denomination

The denomination asset identifier MUST be the ISO 4217 currency numeric code.

The denomination asset MUST use the decimal digits specified by the ISO 4217.

If asset is ISO 4217 numeric code: amount is in minor units (\( 10^{-d} \) with \( d \) as per ISO 4217 digits).

Tip

The value (uint64) 10000 an EUR (ISO 4217, 2 decimals) denomination is interpreted as 100.00 Euro.



  1. ACTUS only allows ISO 4217 currency identifiers, therefore an on-chain denomination is not supported by ACTUS.

Day-Count Convention

Debt instruments use a day-count convention to calculate the amount of accrued interest when the next interest payment is less than a full interest period away.

The D-ASA MUST specify one day-count convention \( [IPCD] \).

The day-count convention MUST be identified with one of the following enumerated IDs (uint8):

IDNameACTUSDescription
0Actual/Actual\([AA]\)Year fractions accrue on the basis of the actual number of days per month and per year in the respective period
1Actual/360\([A360]\)Year fractions accrue on the basis of the actual number of days per month and 360 days per year in the respective period
2Actual/365\([A365]\)Year fractions accrue on the basis of the actual number of days per month and 365 days per year in the respective period
330/360 ISDA\([30E360ISDA]\)Year fractions accrue on the basis of 30 days per month and 360 days per year in the respective period (ISDA method)
430/360\([30E360]\)Year fractions accrue on the basis of 30 days per month and 360 days per year in the respective period
528/366\([28E366]\)Year fractions accrue on the basis of 28 days per month and 366 days per year in the respective period

Calendar

Calendars define the non-working days which may affect the dates of traditional debt instruments.

The AVM (so the D-ASA) time has no notion of calendars. Conversion of serial UNIX timestamps into a year/month/day triple1 (and vice versa) can be performed by external Algorand Applications2 or client side (normalization).

The D-ASA MAY specify a calendar \( [CLDR] \).

The calendar MUST be identified with one of the following enumerated IDs:

IDNameACTUSDescription
0No Calendar\([NC]\)No holidays defined (default if not specified)
1Monday to Friday\([MF]\)Saturdays and Sundays are holidays

Business Day Convention

Debt instruments cash flows execution may be stopped on non-working days (according to a calendar).

The business day convention defines how D-ASA execution can be shifted to the next business day (following) or the previous on (preceding).

The D-ASA MAY specify a business day convention \( [BDC] \).

IDNameACTUSDescription
0No Shift\([NOS]\)No shift applied to non-business days
1Shift-Calculate Following\([SCF]\)Shift event dates first then calculate accruals etc. Strictly shift to the next following business day
2Shift-Calculate Modified-Following\([SCMF]\)Shift event dates first then calculate accruals etc. Shift to the next following business day if this falls in the same month. Shift to the most recent preceding business day otherwise
3Calculate-Shift Following\([CSF]\)Calculate accruals etc. first then shift event dates. Strictly shift to the next following business day
4Calculate-Shift Modified-Following\([CSMF]\)Calculate accruals etc. first then shift event dates. Shift to the next following business day if this falls in the same month. Shift to the most recent preceding business day otherwise
5Shift-Calculate Preceding\([SCP]\)Shift event dates first then calculate accruals etc. Strictly shift to the most recent preceding business day
6Shift-Calculate Modified-Preceding\([SCMP]\)Shift event dates first then calculate accruals etc. Shift to the most recent preceding business day if this falls in the same month. Shift to the next following business day otherwise
7Calculate-Shift Preceding\([CSP]\)Calculate accruals etc. first then shift event dates. Strictly shift to the most recent preceding business day
8Calculate-Shift Modified-Preceding\([CSMP]\)Calculate accruals etc. first then shift event dates. Shift to the most recent preceding business day if this falls in the same month. Shift to the next following business day otherwise

End of Month Convention

Debt instruments may define due dates as the last day of the month.

The end-of-month convention defines how D-ASA execution can be shifted according to the different number of days in months (31, 30, and 28) according to the calendar.

The D-ASA MAY specify an end-of-month convention \( [EOMC] \).

IDNameACTUS AcronymDescription
0Same Day\( [SD] \)Schedule times always fall on the schedule anchor date day of the month
1End of Month\( [EOM] \)Schedule times fall on the end of every month if the anchor date represents the last day of the respective month

Normalization profile restrictions

The D-ASA kernel MUST store one normalized day-count convention identifier.

The current kernel accepts the following identifiers:

IDNameACTUS
0Actual/ActualAA
1Actual/360A360
2Actual/365A365
330E/360 ISDA30E360ISDA
430E/36030E360

A contract configuration MUST fail if the normalized terms use any other day-count identifier.

The current D-ASA ACTUS profile imposes the following additional constraints on date handling:

  • business_day_convention MUST be NOS;
  • calendar MUST be NC;
  • Timestamps MUST be expressed as UTC UNIX seconds.

Those constraints are enforced during normalization so that the AVM only receives values that can be executed deterministically without external calendar logic.

Accrual factors

The AVM kernel does not recompute year fractions from raw dates. Instead, normalization MUST precompute the relevant accrual factors and place them in each ExecutionScheduleEntry.

This split is intentional:

  • Date arithmetic and ACTUS schedule generation happen off chain;
  • Execution, validation, and state transitions happen on chain.


  1. The paper “chrono-Compatible Low-Level Date Algorithms” (ref), by Howard Hinnant, provides a list of algorithms for the conversion of serial UNIX time into a proleptic Gregorian calendar (and vice versa).

  2. An example of Algorand Application implementing the conversion of serial UNIX time into a proleptic Gregorian calendar year/month/day triple.

Main Contract Attributes

The following are the main ACTUS Contract Attributes.

This section is intended to clarify ACTUS-specific terminology and map it to the corresponding terms used in the D-ASA implementation. ACTUS remains the normative specification throughout.

Important

In the event of any inconsistency, the ACTUS specification shall prevail.

For further details, refer to the ACTUS specification.

Principal

Debt instruments principal is the amount of capital borrowed and used as a base for calculating interest.

The D-ASA MUST define the principal \( [NT] \), expressed in the denomination asset.

The D-ASA MUST define a minimum denomination, expressed in the denomination asset.

The minimum denomination MUST be a divisor of the principal.

Premium and Discount

Debt instruments principal may be placed at premium or discount on issuance.

The D-ASA MAY define a premium or discount \( [PDIED] \) to apply to the principal on the issuance.

Tip

Let’s have a D-ASA denominated in EUR, with a principal of 1M EUR paid at maturity and a minimum denomination of 1,000 EUR. The D-ASA has a principal discount of 200 bps (2%) at the issuance. Each D-ASA unit is sold on the primary market at 980 EUR and will be redeemed for 1,000 EUR of principal at maturity.

Interests

Debt instruments interest is calculated on a fixed or variable rate on the outstanding principal.

The interest rate is the nominal yield paid by the debt instrument on the principal, usually defined per-annum (APY).

Interest Rate

Debt instruments may have variable interest rates, based on external data oracles.

The D-ASA MAY define a nominal interest rate \( [IPNR] \).

If the interest rate is variable, the D-ASA MUST define interest update dates known \( [RRF] \) or unknown \( [RR] \).

Cap and Floor

Debt instruments may define limitations to the interest rate variability, either over the whole contract lifespan or over specific periods.

Life Caps

The D-ASA MAY define a life cap \( [RRLC] \) to apply to the variable interest rate.

The D-ASA MAY define a life floor \( [RRLF] \) to apply to the variable interest rate.

Period Caps

The D-ASA MAY define a period cap \( [RRPC] \) to the variable interest rate.

The D-ASA MAY define a period floor \( [RRPF] \) to apply to the variable interest rate.

Fixing Period

Debt instruments usually schedule interest rate updates before the new rate applies (defined by the rate reset schedule).

The D-ASA MUST define a fixing period \( [RRFIX] \) that specifies a period of time before the interest payment \( [IP] \) in which the interest can be updated.

Issuance

Debt instruments start accruing interest on the issuance date.

The D-ASA MUST have an initial exchange date \( [IED] \) (issuance).

Maturity

Debt instruments may have a maturity date, on which the principal is repaid and the contract obligations expire.

Debt instruments may have a fixed or variable maturity date.

The D-ASA MAY have a maturity date \( [MD] \).

The maturity date MAY be updated in case of pre-payment options.

Prepayment Options

Debt instruments could have early repayment options to repay the principal to lenders (partially or totally) before maturity or to reduce the maturity date.

Debt instrument with defined maturity date may terminate earlier if the full principal redemption happens earlier than maturity.

The D-ASA MAY define prepayment options.

Prepayment Effects

Debt instruments could have early repayment options to repay the principal to lenders (partially or totally) before maturity or to reduce the maturity date.

Debt instrument with a defined maturity date may terminate earlier if the full principal redemption happens earlier than maturity.

An early repayment option could have different prepayment effects \( [PPEF] \):

  • It MAY repay the principal partially or totally before the maturity date;

  • It MAY reduce the maturity date.

The prepayment effect MUST be identified with one of the following enumerated IDs:

IDNameACTUS AcronymDescription
0No Prepayment\([N]\)Prepayment is not allowed under the agreement
1Prepayment Reduces Redemption Amount\([A]\)Prepayment is allowed and reduces the redemption amount for the remaining period up to maturity
2Prepayment Reduces Maturity\([M]\)Prepayment is allowed and reduces the maturity

Penalties

Debt instruments may have a penalty as a consequence of an early repayment option.

The D-ASA MAY define a penalty type \( [PYTP] \) for the early repayment options.

The penalty type MUST be identified with one of the following enumerated IDs:

IDNameACTUS AcronymDescription
0No Penalty\([N]\)No penalty applies
1Fixed Penalty\([A]\)A fixed amount applies as penalty
2Relative Penalty\([R]\)A penalty relative to the notional outstanding applies
3Interest Rate Differential\([I]\)A penalty based on the current interest rate differential relative to the notional outstanding applies

Performance

Debt instruments performances are exposed to credit risks.

The D-ASA performance \( [PRF] \) MUST be identified with one of the following enumerated IDs:

IDNameACTUS AcronymDescription
0Performant\( [PF] \)Contract is performing according to terms and conditions
1Delayed\( [DL] \)Contractual payment obligations are delayed according to the grace period
2Delinquent\( [DQ] \)Contractual payment obligations are delinquent according to the delinquency period
3Default\( [DF] \)Contract defaulted on payment obligations according to delinquency period
4Matured\( [MA] \)Contract matured
5Terminated\( [TE] \)Contract has been terminated

Note

The current reference implementation supports manual default-performance tracking through an RBAC-managed boolean defaulted flag. It does not yet model the full PRF lifecycle enum or automatic grace-period and delinquency transitions on chain.

Grace Period

Debt instruments may define a grace period as a time window after the payment due date during which payment may be retried without a penalty.

The D-ASA MAY define a grace period \( [GRP] \).

Delinquency Period

Debt instruments may define a delinquency period as a time window after the grace period. If payment happens after the delinquency period, then the counterparty is in technical default.

The D-ASA MAY define a delinquency period \( [DQP] \).

Default

Default is the ultimate failure to pay the lenders according to the payment obligations. When this happens, the creditors have the right to declare default to the debtors.

Default processes require the intervention of regulatory bodies and courts, therefore the D-ASA default status bridges the default process off-chain.

The D-ASA SHOULD enter default status if it cannot perform payments on due dates.

The D-ASA MAY disable all non-administrative methods on default status.

The D-ASA default can be called either automatically (based on program conditions) or manually (based on the decision of a trustee).

The Trustee MAY set the default status with the rbac_contract_default method.

In the current reference implementation, rbac_contract_default(defaulted: bool) stores a boolean defaulted flag in RBAC global state. This performance flag is distinct from the kernel lifecycle status.

Tip

The D-ASA has no grace period and no delinquency period. A D-ASA interest payment is triggered on due date, but there is not enough liquidity to pay all the lenders. The D-ASA contract automatically enters in default immediately.

Tip

The D-ASA has a grace period and a delinquency period. A D-ASA interest payment is triggered on due date, but there is not enough liquidity to pay all the lenders. The D-ASA program starts counting the grace period and delinquency period. If the delinquency period expires, then the contract enters in default.

Tip

A D-ASA interest payment is triggered on due date, but there is not enough liquidity to pay all the lenders. The D-ASA contract relies on a Trustee to call the default.

Kernel State and Schedule

The ACTUS kernel stores the executable contract as three normalized payload classes:

  1. NormalizedActusTerms
  2. InitialKernelState
  3. ExecutionScheduleEntry[]

Normalized Terms

NormalizedActusTerms MUST contain the immutable or quasi-immutable contract configuration required by the kernel, including:

  • Contract Type ID;
  • Denomination asset ID and Settlement asset ID;
  • Total units;
  • Notional Principal;
  • Initial Exchange Amount and Date;
  • Maturity Date;
  • Day-Count Convention ID;
  • Rate Reset parameters;
  • Dynamic-principal-redemption flags;
  • Fixed-point scale.

Initial kernel state

InitialKernelState MUST capture the pre-IED kernel state snapshot uploaded with contract_config. It defines:

  • Status Date \( [SD] \);
  • Starting event cursor;
  • Outstanding principal;
  • Interest calculation base;
  • Current nominal rate;
  • Accrued interest;
  • Next principal redemption;
  • Cumulative interest and principal indices.

Execution schedule

The normalized ACTUS execution schedule is an array of ExecutionScheduleEntrys.

Execution schedule entry

Each ExecutionScheduleEntry MUST contain:

  • A contiguous event_id;
  • An ACTUS event_type;
  • scheduled_time;
  • Precomputed accrual factors;
  • The next normalized rate and principal state;
  • Entry flags.

Entry flags are stored as a bitfield integer and MUST comply with the following bit positions:

FlagBit PositionDecimal ValueMeaning
CASH_EVENT1 << 01Event produces cash flows (requires contract funds)
NON_CASH_EVENT1 << 12Event updates state without cash flows
OBSERVED_EVENT1 << 24Event requires external observation (e.g., rate reset)
INITIAL_PRF1 << 38Event is the initial performance flag update

The schedule MUST satisfy the following invariants:

Schedule invariants

  • event_id = 0 MUST be IED;
  • Event IDs MUST be contiguous across all pages;
  • Schedule entries MUST be ordered by nondecreasing scheduled_time;
  • Page size MUST NOT exceed 16 entries in the current kernel;
  • The last uploaded page MUST finalize schedule_entry_count and move the contract to STATUS_PENDING_IED.

A normalized ACTUS schedule page MUST be identified with a key of the form:

[S#||<0-based page index>]

Where || denotes concatenation.

ACTUS cycles

ACTUS cycles are an off-chain schedule-generation concept. They define recurring periods before normalization resolves them into concrete timestamps.

The D-ASA SDK models ACTUS cycles with the syntax:

<count><unit>[+|-]

where:

  • count is a positive integer;
  • unit is one of D, W, M, Q, H, Y;
  • + and - are optional ACTUS stub markers.

Examples:

  • 90D
  • 3M
  • 1Q
  • 1H
  • 2Y
  • 3M+
  • 1Q-

Cycles are used together with ACTUS anchors in ContractAttributes, such as:

  • interest_payment_anchor + interest_payment_cycle
  • principal_redemption_anchor + principal_redemption_cycle
  • rate_reset_anchor + rate_reset_cycle

The normalization process MUST resolve those cycles into explicit schedule entries before the contract is uploaded to the AVM.

As a result, the on-chain kernel does not store raw ACTUS cycles. It stores only the normalized timestamps and state transitions that were derived from those cycles.

Contract status machine

The kernel uses the following status identifiers:

StatusIDMeaning
INACTIVE0Terms and schedule not fully configured
PENDING_IED50Schedule uploaded; issuance not yet activated
ACTIVE100IED executed; contract lifecycle is live
ENDED200Terminal state reached
stateDiagram-v2
  [*] --> INACTIVE: Contract create
  INACTIVE --> PENDING_IED: Upload schedule
  PENDING_IED --> ACTIVE: Execute IED
  ACTIVE --> ACTIVE: Execute events
  ACTIVE --> ENDED: Terminal state
  ENDED --> [*]

  state "INACTIVE (0)<br/>Terms and schedule<br/>not fully configured" as INACTIVE
  state "PENDING_IED (50)<br/>Schedule uploaded<br/>Issuance not activated" as PENDING_IED
  state "ACTIVE (100)<br/>IED executed<br/>Contract lifecycle live" as ACTIVE
  state "ENDED (200)<br/>Terminal state reached" as ENDED

Performance default flag

Kernel lifecycle status and contract performance are distinct concepts.

Outside the kernel normalized state, the reference implementation stores a boolean RBAC-managed global-state flag, defaulted, to record manual contract default performance.

An active Trustee MAY update this flag by calling rbac_contract_default with a boolean defaulted argument.

defaulted is not part of InitialKernelState and is not returned by contract_get_state. Clients that need it MUST read the dedicated global-state key.

Due-event execution

The kernel advances the schedule through explicit ABI calls:

  • contract_execute_ied applies the first due IED;
  • apply_non_cash_event applies the next due non-cash event after IED;
  • fund_due_cashflows processes due cash events and advances the cursor;
  • contract_get_next_due_event returns the next due entry, or a zero sentinel after end.

This split is the normative execution model for D-ASA.

Normalization and Configuration

The contract configuration flow MUST follow the same sequence as the reference implementation:

%%{init: {'flowchart':{'nodeSpacing': 20, 'rankSpacing': 20}}}%%
flowchart TD
    A[ContractAttributes] --> B[normalize_contract_attributes]
    B --> C[contract_config]
    C --> D[contract_schedule]
    D --> E[primary_distribution]
    E --> F[contract_execute_ied]

Required steps

  1. Define the debt instrument with ContractAttributes.

  2. Normalize the contract off chain with normalize_contract_attributes().

  3. Upload NormalizedActusTerms, InitialKernelState, and the prospectus with contract_config.

  4. Upload the normalized schedule in contiguous pages with contract_schedule.

  5. Reserve all units with primary_distribution.

  6. Activate issuance by executing contract_execute_ied.

contract_schedule MUST be called after contract_config, and contract_execute_ied MUST be called only after the full schedule is uploaded and the full unit supply is reserved.

Example

The deployment helpers and tests provide a code-accurate PAM fixed coupon bond example:

attrs = make_pam_fixed_coupon_bond_profile(
    contract_id=1,
    status_date=1_700_000_000,
    initial_exchange_date=1_702_592_000,
    maturity_date=1_858_112_000,
    notional_principal=10_000,
    nominal_interest_rate=0.02,
    interest_payment_cycle=Cycle.parse_cycle("90D"),
    interest_payment_anchor=1_710_368_000,
)

normalized = normalize_contract_attributes(
    attrs,
    denomination_asset_id=12345,
    denomination_asset_decimals=2,
    notional_unit_value=100,
    secondary_market_opening_date=1_702_592_000,
    secondary_market_closure_date=1_858_198_400,
)

pages = normalized.schedule_pages(page_size=16)

The normalized result has the following properties:

  • notional_principal = 1_000_000 base units;
  • total_units = 100;
  • fixed_point_scale = 1_000_000_000;
  • schedule = [IED] + 20 * [IP] + [MD];
  • len(schedule) = 22;
  • len(pages) = 2.

The upload sequence is:

client.send.contract_config(...)
client.send.contract_schedule(schedule_page_index=0, is_last_page=False, ...)
client.send.contract_schedule(schedule_page_index=1, is_last_page=True, ...)
client.send.primary_distribution(...)
client.send.contract_execute_ied()

Schedule paging

Schedule paging is part of configuration, not a transport detail. The caller MUST preserve:

  • contiguous schedule_page_index values;
  • contiguous event_id values across pages;
  • chronological ordering across page boundaries;
  • one final page flagged with is_last_page = true.

Secondary-market configuration

transfer_set_schedule is separate from kernel configuration. Secondary-market dates are not part of the on-chain NormalizedActusTerms struct in the current ABI and, if enforced, MUST be configured separately at the execution layer. The SDK normalization metadata for secondary-market dates MUST satisfy secondary_market_opening_date >= initial_exchange_date and secondary_market_closure_date > secondary_market_opening_date.

Performance state

The contract-level defaulted performance flag is not part of normalization or InitialKernelState. If used, it is updated separately at execution time through the RBAC method rbac_contract_default.

Numeric Precision and Conversions

All values uploaded to the kernel MUST fit into AVM uint64 values.

Amounts

Display amounts are normalized into ASA base units:

base_units = int(display_amount * 10^decimals)

The conversion truncates toward zero because normalization converts the scaled decimal value to an integer directly.

Rates

Rates are normalized into fixed-point integers with:

fixed_point_rate = int(rate * 1_000_000_000)

The D-ASA fixed-point scale is:

FIXED_POINT_SCALE = 1_000_000_000

The kernel then uses wide multiplication and division to apply those rates safely on chain.

Units

The normalization process MUST derive the total unit supply from:

total_units = notional_principal / notional_unit_value

This division MUST be exact.

Initial exchange amount

The initial exchange amount is normalized as:

initial_exchange_amount = notional - discount
initial_exchange_amount = notional + premium

where:

  • a positive premium_discount_at_ied is treated as a discount;
  • a negative premium_discount_at_ied is treated as a premium.

Normalization MUST fail if the result becomes negative or exceeds uint64.

Overflow constraints

Every normalization step MUST reject:

  • negative amounts that would require signed integers on chain;
  • values larger than 2^64 - 1;
  • scaled amounts or rates that overflow after multiplication by their scale.

Example

For a denomination asset with 2 decimals:

  • 10_000 display units normalize to 1_000_000 base units;
  • 100 display units normalize to 10_000 base units;
  • 0.02 normalizes to 20_000_000 fixed-point units;
  • 1_000_000 / 10_000 = 100 total units.

Cumulative indices

The accounting layer uses fixed-point cumulative indices for contract-wide cashflow funding:

  • Contract-wide amount -> Per-unit index delta;
  • Per-unit index delta -> Claim at settlement time.

This design preserves precision at contract level while keeping holder settlement lazy and deterministic.

Metadata

Metadata can be used to inform optional properties that define the specific D-ASA.

Important

D-ASA metadata is informational only, they have no influence on the contract execution and are non-normative with respect to ACTUS specifications.

Prospectus

Debt instruments are defined by their prospectus.

The D-ASA can notarize the debt instrument prospectus in the metadata.

The D-ASA prospectus hash (byte[32]) and prospectus URL (string) MAY be set on contract configuration.

The prospectus hash MUST be computed with SHA-512/256, as defined in NIST FIPS 180-4.

The digests are a single SHA-256 integrity metadata defined in the W3C subresource integrity specification. Details on generating those digests can be found on the MDN Web Docs (only SHA-256 is supported by this specification).

Accounting

The accounting section defines how the kernel’s contract-wide cashflow state is projected into holder positions, checkpoints, and claimable balances.

Account (Lender)

Lenders provide capital to borrowers with the expectation of a financial return, defined by debt instruments.

Lenders \( [CPID] \) own D-ASA accounts, characterized by a pair of Algorand Addresses:

  • Holding Address: address that owns D-ASA units with the right to future payments;
  • Payment Address: address that receives D-ASA payments.

The Payment Address MAY be different from the Holding Address.

The Account is uniquely identified by the account key of the form:

[A#||<holding address>]

Where || denotes concatenation.

D-ASA units can be in custody with a third party or temporarily deposited on an order book (Holding Address). At the same time, payments are always executed towards the lender (on the Payment Address).

Seniority

Debt instruments may have an order of repayment in the event of a sale or default of the issuer, based on lenders’ seniority. Lenders with the same seniority are treated equally.

D-ASA does not enforce lender seniority \( [SEN] \).

D-ASA units

D-ASA units represent pro-rata ownership of the normalized ACTUS contract.

Total supply

The kernel MUST store total_units as a uint64.

The off-chain normalization process MUST derive total_units exactly as:

notional_principal / notional_unit_value

The division MUST be exact. If the principal is not divisible by the chosen unit value, normalization MUST fail.

notional_unit_value is an SDK-side normalization input. It is used to derive total_units, but it is not persisted on chain.

Unit states

A holder account stores two unit balances:

  • reserved_units: units allocated before IED;
  • units: active units participating in funded ACTUS cashflows.

When IED executes, reserved units become activatable and are moved into units the first time the holder position is touched.

Position accounting

Each holder position MUST track:

  • The payment address;
  • Active and reserved unit balances;
  • Suspension state;
  • The last settled event cursor;
  • The last applied cumulative interest index;
  • The last applied cumulative principal index;
  • Claimable interest and principal amounts.

The accounting layer does not maintain a mutable on-chain nominal unit value. Transfers and claims are therefore unit-based and index-based, not value-array-based.

Lazy settlement

The accounting layer MUST settle holder positions lazily against the global cumulative indices maintained by the kernel.

On settlement:

  1. The delta between global and account checkpoints is computed;
  2. The delta is multiplied by the holder’s active units;
  3. The result is moved into claimable_interest and claimable_principal;
  4. The checkpoints and settled cursor are advanced.

This model allows:

  • Contract-wide funding of due ACTUS cash events once;
  • Account-by-Account realization of the funded cashflows later.

Execution

The execution layer applies the configured contract in production through primary distribution, cashflow funding and claims, and unit transfers.

Algorand Virtual Machine (AVM)

The Algorand Virtual Machine (AVM) is a trust-less bytecode-based Turing-complete stack interpreter that executes programs on the Algorand protocol.

The D-ASA is an Algorand Application that runs on the AVM.

Performance

MetricValue
Block Size5 MB
Block Time~2.8 sec
TPS~10k txn/sec
Finalityinstant
Numeric Precision512 bit

For further details on the AVM architecture, refer to the AVM specification.

Time

The time on the Algorand Virtual Machine is defined by the block UNIX timestamp.

The Algorand protocol has dynamic block latency with instant finality. At the time of writing (March 2026), block finality is about 2.8 seconds.

The AVM time may present a drift with respect to external standard time references.

Transaction Ordering

Important

Transaction ordering in a block is not enforced by the Algorand protocol. In a healthy network, block proposers are selected randomly by the Algorand consensus based on a Verifiable Random Function (VRF). Therefore, the order of transactions (e.g., asset transfers or cashflow payments) in a block is random and unbiased, with no systematic advantage or precedence of a payee with respect to others.

Payment Agent

Debt instruments’ cash flows usually involve:

  • Principal repayment
  • Early principal repayments
  • Interest payments

The Payment Agent executes ACTUS cashflows in two phases:

  1. fund_due_cashflows
  2. claim_due_cashflows

Funding phase

fund_due_cashflows MUST:

  • Inspect the schedule at the current global cursor;
  • Process only due cash events (IP, PR, MD);
  • Compute contract-wide interest and principal due from the normalized schedule;
  • Reserve settlement funds in the contract;
  • Convert those amounts into cumulative per-unit indices;
  • Advance the global event cursor.

If an Op Daemon address is configured, fund_due_cashflows MUST only accept calls from:

  • The Op Daemon;
  • The Arranger.

If no Op Daemon is configured, the current implementation applies no extra caller restriction beyond the contract checks.

Claim phase

claim_due_cashflows MUST:

  • Settle the holder position to the latest cumulative indices;
  • Expose the holder’s claimable interest and principal amounts;
  • Execute an on-chain transfer if the payment address is executable;
  • Otherwise leave the claimable amounts reserved for a later attempt.

The method MUST return an opaque payment_info context unchanged so callers can bind off-chain metadata to the claim.

The payment information could be used, for example, for:

  • Adding unique identifiers or external context to the payments;
  • Enabling external payment system integration in the case of off-chain settlement;
  • Providing information about the settled amount and conversion rate used with respect to the denomination asset.

Authorization model

If an Op Daemon address is configured, claim_due_cashflows MUST only accept calls from:

  • The Op Daemon;
  • The Account holding address;
  • The Account payment address.

If no Op Daemon is configured, the current implementation applies no extra caller restriction beyond the contract and account checks.

Settlement

Debt instruments cash flows may be settled in a currency different from the denomination.

The D-ASA MUST define either an on-chain or off-chain settlement asset \( [CURS] \)1 to regulate the cash flows.

All values (uint64) are integer minor units of the relevant on-chain or off-chain settlement asset.

If the D-ASA defines a settlement asset different from the denomination asset, then the respective denomination/settlement conversion rate is applied at settlement time.

The denomination/settlement conversion rate can be provided by different oracles, depending on if the denomination/settlement assets are on-chain or off-chain.

If the D-ASA does not define a different settlement asset, then the cash flows MUST be settled in the denomination asset and the settlement asset identifier MUST be equal to the denomination asset identifier.

Note

The D-ASA reference implementation supports only on-chain ASA settlement assets.

Note

The D-ASA reference implementation requires settlement assets to be the denomination asset.

On-chain settlement

The settlement asset MUST be an Algorand Standard Asset (ASA), an Application asset (App), or the ALGO.

The settlement asset identifier MUST be the ASA ID, the App ID, or 0 for ALGO.

If asset is ALGO (0): amount is in microALGOs (\( 10^{-6} \) ALGO).

If asset is ASA or App: amount is in base units as per that asset’s decimals.

Tip

The value (uint64) 10000 of settlement in ASA settlement 2 decimals is interpreted as 100.00 units of the ASA.

On-chain settlement is possible even if the denomination asset is a traditional off-chain currency.

Off-chain settlement

The settlement asset identifier MUST be the ISO 4217 currency numeric code.

The settlement asset MUST use the decimal digits specified by the ISO 4217.

If asset is ISO 4217 numeric code: amount is in minor units (\( 10^{-d} \) with \( d \) as per ISO 4217 digits).

Tip

The value (uint64) 10000 of a settlement in EUR (ISO 4217, 2 decimals) is interpreted as 100.00 Euro.

In the case of an off-chain settlement, the D-ASA state machine:

  • Regulates payments’ approval conditions (e.g. interest is due);
  • Notarizes the amounts and timestamps of payments settled off-chain.


  1. ACTUS only allows ISO 4217 currency identifiers, therefore an on-chain settlement is not supported by ACTUS.

Transfer Agent

Debt instruments can be transferable among investors.

D-ASA supports both on-chain and off-chain transfer agents.

The Transfer Agent executes primary and secondary movement of D-ASA units.

Secondary transfers

transfer MUST move active units only after:

  • The contract is active;
  • The transfer window is open, if a window was configured;
  • Both counterparties are valid, distinct, and unsuspended accounts;
  • Both counterparties are settled to the current global indices;
  • There is no due ACTUS event pending at the global cursor.

The sender of the D-ASA transfer MUST have enough D-ASA units to transfer.

The method MUST return the number of units transferred.

Transfers are unit-based. The current kernel does not persist a mutable per-unit nominal value or per-unit coupon status.

Caller model

The D-ASA transferability policy may involve and integrate KYC/AML processes, pre-authorizations, secondary market restrictions, etc.

The current reference implementation requires the holder to call transfer directly.

The method MUST reject calls from any other address. Transfers are therefore direct holder actions, not transfer-agent proxy calls.

Primary Market

Debt instruments can be distributed on the primary market in different ways, such as book building, auctions, etc.

The primary market allocates units before the contract becomes active.

Allocation phase

primary_distribution MUST reserve units for an existing holder account while the contract is in STATUS_PENDING_IED.

The method MUST reject allocations that:

  • Occur before configuration is complete;
  • Occur after IED;
  • Exceed the remaining unallocated supply;
  • Target a suspended or missing holder account;
  • Request zero units.

Only an active Primary Dealer MUST be able to call primary_distribution.

The Primary Dealer role validity period MAY be used to precisely bound the primary distribution window.

Completion requirement

contract_execute_ied MUST fail unless:

reserved_units_total == total_units

This rule makes full issuance an explicit precondition of activation in the current reference implementation.

Activation

Primary distribution reserves units only. Reserved units do not accrue funded ACTUS cashflows until IED executes and the holder position is activated.

Proxies

The Primary Dealer role MAY be delegated to a proxy Application implementing a primary market.

Tip

The primary market is performed as an auction on a dedicated Algorand Application. The implementation requires the primary_distribution method to be called exclusively by the primary market Application, which defines the auction’s outcome.

Tip

The primary market is performed as a book building by an authorized Book-builder Address. The implementation requires the primary_distribution method to be called exclusively by the authorized Book-builder.

Secondary Market

Debt instruments can be traded on secondary markets.

Secondary-market control is an execution-layer policy.

Transfer window

If transfer window enforcement is required, the Arranger MUST configure it with transfer_set_schedule(open_date, closure_date).

The transfer window is interpreted as:

open_date <= now < closure_date

The IED MUST be defined and the method MUST reject a schedule in which open_date >= closure_date and a schedule in which open_date < initial_exchange_date.

Default behavior

If no transfer window is configured, the current reference implementation applies no additional secondary-market date restriction beyond the generic transfer checks.

Relation to normalization

The SDK normalization helpers may carry secondary-market dates as deployment metadata. Those dates are not part of the on-chain NormalizedActusTerms ABI struct in the current kernel. On-chain enforcement therefore happens through transfer_set_schedule, with contract_config additionally rejecting preconfigured transfer windows whose opening date is before IED.

Delivery-vs-Payment (DvP)

Delivery-vs-Payment (DvP) can be used to exchange D-ASA units both on the primary market (placement) and secondary market (e.g., OTC trades).

The D-ASA framework supports the execution of composable DvP transactions.

The D-ASA DvP transactions are native Algorand Group transactions.

The Algorand Group transactions ensure atomicity, they are executed on an “all-or-nothing” basis, meaning that either all transactions in the group are executed or none of them are.

Therefore, D-ASA DvP ensures no counterparty risk, as the transfer of D-ASA units and the corresponding payment occur simultaneously. If either the transfer or the payment fails, the entire transaction group is rejected, preventing any party from being left in a vulnerable position.

The simplest DvP consists of:

  • A delivery-leg, executed by the D-ASA Transfer Agent;
  • A payment-leg, executed as a general Algorand transaction (usually an ASA transfer).

The payment is not restricted to specific assets (e.g., not restricted to the denomination or settlement assets).

The amounts of the DvP are negotiated between the two parties.

Events

The D-ASA uses two distinct event concepts:

  1. ACTUS schedule events: contractual events stored in the normalized execution schedule.

  2. AVM execution events: proofs that a scheduled event was actually applied on chain.

Those concepts are related, but they are not interchangeable.

ACTUS schedule events

ACTUS schedule events live inside ExecutionScheduleEntry records in the boxed schedule. They describe what the contract is expected to execute next.

An ACTUS schedule event contains:

  • event_id
  • event_type
  • scheduled_time
  • accrual factors
  • next-state values
  • flags

Schedule events exist before execution and remain part of contract state even if no AVM proof has been emitted yet.

See Kernel State and Schedule for the normative schedule definition.

AVM execution events

When the kernel applies a schedule entry, it emits a standard ARC-28 ExecutionEvent as an execution proof.

This proof is non-normative receipt data. It does not replace the kernel state, and it does not define future schedule entries. Its purpose is to attest that a given event was applied at a specific block time with a specific payoff and settlement outcome.

The current execution-proof schema is:

{
  "name": "ExecutionEvent",
  "desc": "Non-normative receipt for on-chain execution of a scheduled ACTUS event",
  "args": [
    { "name": "contract_id", "type": "uint64", "desc": "Application ID emitting the proof" },
    { "name": "event_id", "type": "uint64", "desc": "Executed schedule event identifier" },
    { "name": "event_type", "type": "uint8", "desc": "Executed ACTUS event type" },
    { "name": "scheduled_time", "type": "uint64", "desc": "Contractual due timestamp from the schedule" },
    { "name": "applied_at", "type": "uint64", "desc": "Block timestamp at which execution occurred" },
    { "name": "payoff", "type": "uint64", "desc": "Contractual payoff computed for the event" },
    { "name": "payoff_sign", "type": "uint8", "desc": "Payoff sign identifier" },
    { "name": "settled_amount", "type": "uint64", "desc": "Amount actually reserved or transferred on chain" },
    { "name": "currency_id", "type": "uint64", "desc": "Settlement asset identifier" },
    { "name": "sequence", "type": "uint64", "desc": "Monotonic proof sequence, currently event_id + 1" }
  ]
}

Proof semantics

The following field distinctions are normative for interpreting the receipt:

  • scheduled_time is the contractual timestamp from the normalized ACTUS schedule.
  • applied_at is the actual block timestamp at which the kernel applied the event.
  • payoff is the contractual amount implied by the event.
  • settled_amount is the amount actually reserved or transferred on chain.
  • non-cash executions emit a proof with settled_amount = 0.

Supported ACTUS event identifiers

The current kernel can emit proofs for the following ACTUS event types, subject to contract-family compatibility:

TypeAcronymDescription
1IEDInitial Exchange Date
3PRPrincipal Redemption
4PIPrincipal Increase
5PRFPrincipal Redemption Fixing
8IPInterest Payment
11RRFRate Reset Fixing
12RRRate Reset
18IPCBInterest Calculation Base Reset
19MDMaturity

SDK Overview

The high-level Python API lives in the d_asa package and is centered on DAsa.

It wraps the generated ARC-56 client in d_asa.artifacts.dasa_client and keeps the generated ABI structs out of the normal user flow.

from d_asa import (
    DAsa,
    ContractAttributes,
    PricingContext,
    make_pam_zero_coupon_bond,
    normalize_contract_attributes,
)

The root package also continues to expose the domain models and the ACTUS normalizer directly:

  • ContractAttributes
  • make_pam_zero_coupon_bond(...)
  • make_pam_fixed_coupon_bond_profile(...)
  • normalize_contract_attributes(...)
  • NormalizationResult
  • NormalizedActusTerms
  • InitialKernelState
  • ExecutionScheduleEntry
  • AccountPosition
  • ObservedEventRequest
  • ObservedCashEventRequest
  • Cycle

Use DAsa for contract interaction, and keep d_asa.artifacts.dasa_client as the explicit low-level escape hatch when needed.

Deploy and Attach

Deploy

from d_asa import DAsa

app = DAsa.deploy(
    algorand=algorand,
    arranger=arranger,
    app_name="my-dasa",
)

Deploy and Configure

deploy_configured(...) accepts either a precomputed NormalizationResult or raw ContractAttributes plus the normalization inputs.

app = DAsa.deploy_configured(
    algorand=algorand,
    arranger=arranger,
    normalized=normalized,
    prospectus_url="Issuer term sheet",
)

When configuration happens through the high-level API, DAsa also stores a PricingContext derived from normalized.terms.notional_unit_value.

Attach to an Existing App

app = DAsa.from_app_id(
    algorand=algorand,
    app_id=12345,
)

If you already have the generated client:

from d_asa.artifacts.dasa_client import DasaClient

raw_client = DasaClient(algorand=algorand, app_id=12345)
app = DAsa.from_client(raw_client)

Contract View

app.contract exposes readonly helpers built from on-chain state:

  • get_state()
  • get_next_due_event()
  • get_schedule_entry(event_id)
  • get_arranger()
  • get_address_roles(address)
  • get_role_validity(role, address)
  • raw_client

Role Wrappers

DAsa binds signer accounts into role-oriented wrappers.

arranger = app.arranger(arranger_account)
account_manager = app.account_manager(manager_account)
primary_dealer = app.primary_dealer(dealer_account)
trustee = app.trustee(trustee_account)
authority = app.authority(authority_account)
observer = app.observer(observer_account)
op_daemon = app.op_daemon(op_daemon_account)
holder = app.account(holder_account)

Arranger

ArrangerRole covers the contract lifecycle and arranger-controlled actions:

  • configure(...)
  • configure_from_attributes(...)
  • configure_contract(...)
  • upload_schedule(...)
  • execute_ied()
  • set_transfer_window(...)
  • fund_due_cashflows(...)
  • append_observed_cash_event(...)
  • append_observed_cash_events(...)
  • apply_non_cash_event(...)
  • rotate_arranger(...)
  • set_op_daemon(...)
  • assign_role(...)
  • revoke_role(...)

Other Roles

  • AccountManagerRole.open_account(...)
  • PrimaryDealerRole.primary_distribution(...)
  • TrusteeRole.set_default(...)
  • AuthorityRole.suspend_account(...)
  • AuthorityRole.set_contract_suspension(...)
  • ObserverRole.apply_non_cash_event(...)
  • OpDaemonRole.fund_due_cashflows(...)
  • OpDaemonRole.claim(...)

Holding Accounts

HoldingAccount is the investor-facing wrapper:

  • get_raw_position()
  • get_actualized_position(...)
  • get_valuation(...)
  • quote_trade(...)
  • build_otc_dvp(...)
  • transfer(...)
  • claim(...)
  • update_payment_address(...)

Actualized Positions and Valuation

HoldingAccount.get_actualized_position() mirrors the contract’s lazy settlement rules off chain:

  • reserved units become active after IED
  • cumulative interest and principal indices are applied to the holder checkpoints
  • the returned position is a preview only; it does not mutate chain state
holder = app.account(investor)
position = holder.get_actualized_position()

get_valuation() builds on top of that position and adds economic fields:

  • principal_share
  • claimable_interest
  • claimable_principal
  • accrued_interest_not_due
  • economic_value_total
valuation = holder.get_valuation()

Trade Quotes

quote_trade(units=...) is intended to support secondary-market pricing in the middle of an accrual period.

The quote always returns:

  • the current principal share of the traded units
  • accrued interest that is not yet due
  • a par-reference clean value
  • a par-reference dirty value

If a clean quote is supplied, the SDK derives a market dirty value by adding accrued-not-due carry.

from decimal import Decimal
from d_asa import TradeQuoteInput

quote = holder.quote_trade(
    25,
    clean_quote=TradeQuoteInput(clean_price_per_100=Decimal("99.25")),
)

Important behavior:

  • Quotes are rejected when a due ACTUS event is already pending at the requested timestamp.

  • Existing claimable_interest and claimable_principal remain attached to the seller account and are reported as retained balances, not transferred unit value.

  • clean_price_per_100 requires a PricingContext, because notional_unit_value is SDK metadata and is not stored on chain.

Over-the-Counter (OTC) DvP Builder

HoldingAccount.build_otc_dvp(...) builds an atomic delivery-vs-payment group for OTC secondary trades.

The builder is intentionally non-custodial:

  • it creates the payment leg
  • it creates the D-ASA transfer leg
  • it returns the composed group for inspection, signing, simulation, or sending

Algo Settlement

draft = app.account(seller).build_otc_dvp(
    buyer=buyer,
    units=10,
    payment_amount=150_000,
)

result = draft.send()

ASA Settlement

draft = app.account(seller).build_otc_dvp(
    buyer=buyer,
    units=10,
    payment_amount=15_000,
    payment_asset_id=settlement_asa_id,
)

Guardrails

The builder validates:

  • units > 0
  • payment_amount > 0
  • seller and buyer are different
  • the seller signer matches the transfer sender holding address
  • optional quote tolerance, when provided

You can combine it with quote_trade(...) inputs to fail fast when the negotiated payment amount drifts too far from the SDK’s accrual-aware reference.

Interfaces

The interface section documents the public ARC-4 ABI grouped by architecture layer, plus the ABI structs used by those methods.

ABI Types

This section documents the canonical ARC-4 structs exposed by the D-ASA ABI.

The types below refer to the on-chain ABI structs in smart_contracts/abi_types.py, not to the richer SDK-side normalization models in src/models.py.

RoleValidity

Time-bounded role assignment.

FieldTypeMeaning
role_validity_startuint64Inclusive activation timestamp
role_validity_enduint64Inclusive expiration timestamp

Prospectus

Informational contract metadata uploaded with contract_config.

FieldTypeMeaning
hashbyte[32]Prospectus digest
urlstringProspectus location or label

NormalizedActusTerms

Normalized ACTUS terms required by the AVM kernel.

FieldTypeMeaning
contract_typeuint8ACTUS contract family id
denomination_asset_iduint64Denomination asset id
settlement_asset_iduint64Settlement asset id
total_unitsuint64Total unit supply
notional_principaluint64Notional principal in base units
initial_exchange_amountuint64Initial exchange amount in base units
initial_exchange_dateuint64IED timestamp
maturity_dateuint64MD timestamp, or 0 if absent
day_count_conventionuint8Supported day-count identifier
rate_reset_spreaduint64Fixed-point spread
rate_reset_multiplieruint64Fixed-point rate multiplier
rate_reset_flooruint64Fixed-point floor
rate_reset_capuint64Fixed-point cap
rate_reset_nextuint64Prefixed next rate value
has_rate_reset_floorboolFloor flag
has_rate_reset_capboolCap flag
dynamic_principal_redemptionboolDynamic annuity redemption flag
fixed_point_scaleuint64Fixed-point scale, currently 1_000_000_000

SDK-only metadata such as notional_unit_value and secondary-market dates is not part of this ABI struct.

InitialKernelState

Pre-IED state snapshot uploaded with contract_config.

FieldTypeMeaning
status_dateuint64Reference timestamp for the uploaded state
event_cursoruint64Starting global event cursor
outstanding_principaluint64Outstanding principal in base units
interest_calculation_baseuint64Interest base in base units
nominal_interest_rateuint64Current fixed-point nominal rate
accrued_interestuint64Accrued interest in base units
next_principal_redemptionuint64Next redemption target
cumulative_interest_indexuint64Per-unit fixed-point interest index
cumulative_principal_indexuint64Per-unit fixed-point principal index

ExecutionScheduleEntry

One normalized ACTUS schedule entry.

FieldTypeMeaning
event_iduint64Global contiguous event identifier
event_typeuint8ACTUS event id
scheduled_timeuint64Due timestamp
accrual_factoruint64Fixed-point accrual factor
redemption_accrual_factoruint64Fixed-point redemption factor
next_nominal_interest_rateuint64Fixed-point next rate
next_principal_redemptionuint64Next redemption state value
next_outstanding_principaluint64Next outstanding principal
flagsuint64Entry flags

ExecutionSchedulePage

Alias for:

ExecutionScheduleEntry[]

The current kernel accepts at most 16 entries per page.

ObservedEventRequest

Observed non-cash event payload used by apply_non_cash_event.

FieldTypeMeaning
event_iduint64Expected event id when appending
event_typeuint8ACTUS event id
scheduled_timeuint64Event timestamp
accrual_factoruint64Fixed-point accrual factor
redemption_accrual_factoruint64Fixed-point redemption factor
observed_rateuint64Observed fixed-point rate input
next_nominal_interest_rateuint64Fixed-point next rate
next_principal_redemptionuint64Next redemption state value
next_outstanding_principaluint64Next outstanding principal
flagsuint64Observed-event flags

ObservedCashEventRequest

Observed cash event payload used by append_observed_cash_event.

FieldTypeMeaning
event_iduint64Expected event id when appending
event_typeuint8ACTUS cash event id
scheduled_timeuint64Event timestamp
accrual_factoruint64Fixed-point accrual factor
redemption_accrual_factoruint64Fixed-point redemption factor
next_nominal_interest_rateuint64Fixed-point next rate
next_principal_redemptionuint64Next redemption state value
next_outstanding_principaluint64Next outstanding principal
flagsuint64Observed cash-event flags

KernelState

Readonly snapshot returned by contract_get_state.

FieldTypeMeaning
contract_typeuint8ACTUS contract family id
statusuint64Kernel lifecycle status
total_unitsuint64Total unit supply
reserved_units_totaluint64Units reserved pre-IED
initial_exchange_amountuint64Initial exchange amount
event_cursoruint64Current global event cursor
schedule_entry_countuint64Total uploaded schedule entries
outstanding_principaluint64Outstanding principal
interest_calculation_baseuint64Current interest base
nominal_interest_rateuint64Current fixed-point nominal rate
accrued_interestuint64Current accrued interest
cumulative_interest_indexuint64Global per-unit interest index
cumulative_principal_indexuint64Global per-unit principal index
reserved_interestuint64Reserved interest balance
reserved_principaluint64Reserved principal balance

The contract-level defaulted performance flag is stored separately in RBAC-managed global state and is not part of KernelState.

AccountPosition

Accounting position returned by account_get_position.

FieldTypeMeaning
payment_addressaddressCashflow destination
unitsuint64Active units
reserved_unitsuint64Pre-IED reserved units
suspendedboolAccount suspension flag
settled_cursoruint64Last settled global cursor
interest_checkpointuint64Applied interest index checkpoint
principal_checkpointuint64Applied principal index checkpoint
claimable_interestuint64Claimable interest amount
claimable_principaluint64Claimable principal amount

CashFundingResult

Result returned by fund_due_cashflows.

FieldTypeMeaning
funded_interestuint64Interest reserved in the call
funded_principaluint64Principal reserved in the call
total_fundeduint64Total reserved amount
processed_eventsuint64Number of processed cash events
timestampuint64Execution timestamp

CashClaimResult

Result returned by claim_due_cashflows.

FieldTypeMeaning
interest_amountuint64Interest realized or reported
principal_amountuint64Principal realized or reported
total_amountuint64Total realized or reported amount
timestampuint64Execution timestamp
contextbyte[]Opaque caller-supplied context

RBAC Interface

The RBAC interface manages privileged roles, suspension state, contract default state, and application control.

Only the Arranger may call RBAC methods, unless otherwise specified.

contract_update

{
  "name": "contract_update",
  "readonly": false,
  "args": [],
  "returns": { "type": "void" },
  "errors": ["UNAUTHORIZED"]
}

rbac_rotate_arranger

{
  "name": "rbac_rotate_arranger",
  "readonly": false,
  "args": [
    { "name": "new_arranger", "type": "address" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the rotation" },
  "errors": ["UNAUTHORIZED", "INVALID_ROLE_ADDRESS"]
}

The new arranger must not be the Algorand global zero address.

rbac_set_op_daemon

{
  "name": "rbac_set_op_daemon",
  "readonly": false,
  "args": [
    { "name": "address", "type": "address" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the assignment" },
  "errors": ["UNAUTHORIZED"]
}

This is a non-normative helper for payment automation.

rbac_assign_role

{
  "name": "rbac_assign_role",
  "readonly": false,
  "args": [
    { "name": "role_id", "type": "uint8" },
    { "name": "role_address", "type": "address" },
    { "name": "validity", "type": "RoleValidity" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the assignment" },
  "errors": ["UNAUTHORIZED", "DEFAULTED", "INVALID_ROLE", "INVALID_ROLE_ADDRESS", "INVALID_SORTING"]
}

Only assign non-Arranger roles. The target address must not be the global zero address, and validity.role_validity_start must be strictly earlier than validity.role_validity_end.

rbac_revoke_role

{
  "name": "rbac_revoke_role",
  "readonly": false,
  "args": [
    { "name": "role_id", "type": "uint8" },
    { "name": "role_address", "type": "address" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the revocation" },
  "errors": ["UNAUTHORIZED", "DEFAULTED", "INVALID_ROLE", "INVALID_ROLE_ADDRESS"]
}

rbac_contract_suspension

{
  "name": "rbac_contract_suspension",
  "readonly": false,
  "args": [
    { "name": "suspended", "type": "bool" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the suspension update" },
  "errors": ["UNAUTHORIZED"]
}

Only an active Authority may call this method.

rbac_contract_default

{
  "name": "rbac_contract_default",
  "readonly": false,
  "args": [
    { "name": "defaulted", "type": "bool" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the default update" },
  "errors": ["UNAUTHORIZED"]
}

Only an active Trustee may call this method.

It sets or clears the contract-level defaulted performance flag. This flag is distinct from the kernel lifecycle status.

rbac_get_arranger

{
  "name": "rbac_get_arranger",
  "readonly": true,
  "args": [],
  "returns": { "type": "address", "desc": "Current arranger address" },
  "errors": []
}

rbac_get_address_roles

{
  "name": "rbac_get_address_roles",
  "readonly": true,
  "args": [
    { "name": "address", "type": "address" }
  ],
  "returns": {
    "type": "(bool,bool,bool,bool,bool)",
    "desc": "Account manager, primary dealer, trustee, authority, observer"
  },
  "errors": []
}

rbac_get_role_validity

{
  "name": "rbac_get_role_validity",
  "readonly": true,
  "args": [
    { "name": "role_id", "type": "uint8" },
    { "name": "role_address", "type": "address" }
  ],
  "returns": { "type": "RoleValidity", "desc": "Stored validity interval" },
  "errors": ["INVALID_ROLE", "INVALID_ROLE_ADDRESS"]
}

Only applicable to non-Arranger roles.

Accounting Interface

The Accounting interface manages holder positions and account-level suspension.

account_suspension

{
  "name": "account_suspension",
  "readonly": false,
  "args": [
    { "name": "holding_address", "type": "address" },
    { "name": "suspended", "type": "bool" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the suspension update" },
  "errors": ["UNAUTHORIZED", "INVALID_HOLDING_ADDRESS"]
}

Only an active Authority may call this method.

account_open

{
  "name": "account_open",
  "readonly": false,
  "args": [
    { "name": "holding_address", "type": "address" },
    { "name": "payment_address", "type": "address" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the account opening" },
  "errors": ["UNAUTHORIZED", "DEFAULTED", "SUSPENDED", "INVALID_HOLDING_ADDRESS"]
}

Only an active Account Manager may call this method.

The current implementation treats an already existing holding address as INVALID_HOLDING_ADDRESS.

account_update_payment_address

{
  "name": "account_update_payment_address",
  "readonly": false,
  "args": [
    { "name": "holding_address", "type": "address" },
    { "name": "payment_address", "type": "address" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the account update" },
  "errors": ["UNAUTHORIZED", "DEFAULTED", "SUSPENDED", "INVALID_HOLDING_ADDRESS"]
}

Only the Account Holding Address itself may call this method.

account_get_position

{
  "name": "account_get_position",
  "readonly": true,
  "args": [
    { "name": "holding_address", "type": "address" }
  ],
  "returns": { "type": "AccountPosition", "desc": "Full holder accounting position" },
  "errors": ["INVALID_HOLDING_ADDRESS"]
}

ACTUS Kernel Interface

The ACTUS Kernel interface configures the normalized contract, stores the execution schedule, and advances non-cash lifecycle events.

contract_create

{
  "name": "contract_create",
  "create": "require",
  "readonly": false,
  "args": [
    { "name": "arranger", "type": "address" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of contract creation" },
  "errors": ["INVALID_ROLE_ADDRESS"]
}

The arranger argument must not be the Algorand global zero address.

contract_config

{
  "name": "contract_config",
  "readonly": false,
  "args": [
    { "name": "terms", "type": "NormalizedActusTerms" },
    { "name": "initial_state", "type": "InitialKernelState" },
    { "name": "prospectus", "type": "Prospectus" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of contract configuration" },
  "errors": [
    "UNAUTHORIZED",
    "ALREADY_CONFIGURED",
    "INVALID_ACTUS_CONFIG",
    "INVALID_DAY_COUNT_CONVENTION",
    "INVALID_DENOMINATION",
    "INVALID_SETTLEMENT_ASSET",
    "INVALID_IED"
  ]
}

Only the Arranger may call this method.

contract_schedule

{
  "name": "contract_schedule",
  "readonly": false,
  "args": [
    { "name": "schedule_page_index", "type": "uint64" },
    { "name": "is_last_page", "type": "bool" },
    { "name": "schedule_page", "type": "ExecutionSchedulePage" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the schedule upload" },
  "errors": [
    "UNAUTHORIZED",
    "ALREADY_CONFIGURED",
    "TERMS_NOT_CONFIGURED",
    "INVALID_SCHEDULE_PAGE",
    "INVALID_EVENT_ID",
    "INVALID_ACTUS_CONFIG",
    "INVALID_SORTING"
  ]
}

Only the Arranger may call this method.

contract_execute_ied

{
  "name": "contract_execute_ied",
  "readonly": false,
  "args": [],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the IED execution" },
  "errors": [
    "NOT_CONFIGURED",
    "UNAUTHORIZED",
    "DEFAULTED",
    "SUSPENDED",
    "INVALID_EVENT_CURSOR",
    "INVALID_EVENT_TYPE",
    "NO_DUE_CASHFLOW",
    "INVALID_ACTUS_CONFIG",
    "PRIMARY_DISTRIBUTION_INCOMPLETE"
  ]
}

Only the Arranger may call this method.

apply_non_cash_event

{
  "name": "apply_non_cash_event",
  "readonly": false,
  "args": [
    { "name": "event_id", "type": "uint64" },
    { "name": "payload", "type": "ObservedEventRequest" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the event application" },
  "errors": [
    "NOT_CONFIGURED",
    "DEFAULTED",
    "SUSPENDED",
    "OBSERVED_EVENT_REQUIRED",
    "UNAUTHORIZED",
    "INVALID_EVENT_ID",
    "INVALID_EVENT_CURSOR",
    "INVALID_EVENT_TYPE",
    "INVALID_SCHEDULE_PAGE",
    "INVALID_ACTUS_CONFIG",
    "INVALID_SORTING",
    "NO_DUE_CASHFLOW",
    "PENDING_IED"
  ]
}

RR and RRF execution is Observer-controlled. Other non-cash events are Arranger-controlled.

append_observed_cash_event

{
  "name": "append_observed_cash_event",
  "readonly": false,
  "args": [
    { "name": "payload", "type": "ObservedCashEventRequest" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the append operation" },
  "errors": [
    "NOT_CONFIGURED",
    "UNAUTHORIZED",
    "DEFAULTED",
    "SUSPENDED",
    "PENDING_IED",
    "OBSERVED_EVENT_REQUIRED",
    "INVALID_EVENT_ID",
    "INVALID_EVENT_TYPE",
    "INVALID_SCHEDULE_PAGE",
    "INVALID_ACTUS_CONFIG",
    "INVALID_SORTING"
  ]
}

contract_get_state

{
  "name": "contract_get_state",
  "readonly": true,
  "args": [],
  "returns": { "type": "KernelState", "desc": "Current kernel state snapshot" },
  "errors": ["NOT_CONFIGURED"]
}

KernelState does not include the separate RBAC-managed defaulted performance flag. Clients that need that value must read the dedicated global-state key.

contract_get_next_due_event

{
  "name": "contract_get_next_due_event",
  "readonly": true,
  "args": [],
  "returns": {
    "type": "ExecutionScheduleEntry",
    "desc": "Next due schedule entry, or a zero sentinel if the contract ended"
  },
  "errors": ["NOT_CONFIGURED"]
}

Payment Agent Interface

The Payment Agent interface funds due ACTUS cash events and allows holder claims.

fund_due_cashflows

{
  "name": "fund_due_cashflows",
  "readonly": false,
  "args": [
    { "name": "max_event_count", "type": "uint64" }
  ],
  "returns": { "type": "CashFundingResult", "desc": "Funding result for the processed events" },
  "errors": [
    "NOT_CONFIGURED",
    "UNAUTHORIZED",
    "DEFAULTED",
    "SUSPENDED",
    "PENDING_IED",
    "NO_DUE_CASHFLOW",
    "NOT_ENOUGH_FUNDS"
  ]
}

If an Op Daemon is configured, only the Arranger or Op Daemon may call this method. Otherwise the method is permissionless.

claim_due_cashflows

{
  "name": "claim_due_cashflows",
  "readonly": false,
  "args": [
    { "name": "holding_address", "type": "address" },
    { "name": "payment_info", "type": "byte[]" }
  ],
  "returns": { "type": "CashClaimResult", "desc": "Holder claim result" },
  "errors": [
    "NOT_CONFIGURED",
    "INVALID_HOLDING_ADDRESS",
    "UNAUTHORIZED",
    "DEFAULTED",
    "SUSPENDED",
    "PENDING_IED",
    "NO_DUE_CASHFLOW",
    "NOT_ENOUGH_FUNDS"
  ]
}

If the holder’s payment path is not executable, the method reports the claimable amounts without clearing them.

Transfer Agent Interface

The Transfer Agent interface manages transfer windows, primary allocations, and secondary transfers.

transfer_set_schedule

{
  "name": "transfer_set_schedule",
  "readonly": false,
  "args": [
    { "name": "open_date", "type": "uint64" },
    { "name": "closure_date", "type": "uint64" }
  ],
  "returns": { "type": "uint64", "desc": "UNIX timestamp of the schedule update" },
  "errors": ["UNAUTHORIZED", "INVALID_SORTING", "INVALID_TRANSFER_OPENING"]
}

Only the Arranger may call this method. The IED MUST be already defined and the opening date MUST be greater than or equal to IED.

primary_distribution

{
  "name": "primary_distribution",
  "readonly": false,
  "args": [
    { "name": "holding_address", "type": "address" },
    { "name": "units", "type": "uint64" }
  ],
  "returns": { "type": "uint64", "desc": "Remaining undistributed unit balance" },
  "errors": [
    "NOT_CONFIGURED",
    "UNAUTHORIZED",
    "DEFAULTED",
    "SUSPENDED",
    "PRIMARY_DISTRIBUTION_CLOSED",
    "INVALID_HOLDING_ADDRESS",
    "ZERO_UNITS",
    "OVER_DISTRIBUTION"
  ]
}

transfer

{
  "name": "transfer",
  "readonly": false,
  "args": [
    { "name": "sender_holding_address", "type": "address" },
    { "name": "receiver_holding_address", "type": "address" },
    { "name": "units", "type": "uint64" }
  ],
  "returns": { "type": "uint64", "desc": "Number of units transferred" },
  "errors": [
    "NOT_CONFIGURED",
    "DEFAULTED",
    "SUSPENDED",
    "PENDING_IED",
    "CLOSED_TRANSFER",
    "UNAUTHORIZED",
    "INVALID_HOLDING_ADDRESS",
    "SELF_TRANSFER",
    "NULL_TRANSFER",
    "OVER_TRANSFER",
    "PENDING_ACTUS_EVENT"
  ]
}

The current implementation requires the sender holding address to submit the transaction.

Reference Implementation

⚠️The reference implementation has not been audited. Do not use this code for real products. The author declines all responsibility.

The reference implementation exposes one canonical on-chain contract, DASA, and composes it from five modules:

  • RbacModule
  • ActusKernelModule
  • AccountingModule
  • PaymentAgent
  • TransferAgent

The implementation executes normalized ACTUS schedules on the AVM and currently supports the kernel contract families PAM, ANN, NAM, LAM, LAX, and CLM.

Delivered artifacts

The canonical artifacts are:

  • src/d_asa/artifacts/DASA.arc56.json
  • src/d_asa/artifacts/dasa_client.py
  • src/d_asa/artifacts/dasa_avm_client.py

The deployment helpers in smart_contracts/d_asa/deploy_config.py provide code-accurate demo normalization flows for:

  • a PAM fixed coupon bond profile;
  • a PAM zero coupon bond profile.

Those demos are examples of normalized inputs to the shared kernel, not separate contract classes with different public ABIs.

Architecture

The current D-ASA architecture is layered, not product-specific. The on-chain contract is a single DASA application assembled from five modules:

flowchart LR
  RBAC["RBAC"] --> KERNEL["ACTUS Kernel"]
  KERNEL --> ACCOUNTING["Accounting"]
  ACCOUNTING --> PAYMENT["Payment Agent"]
  ACCOUNTING --> TRANSFER["Transfer Agent"]

Processing chain

The canonical processing chain is:

flowchart LR
  ACTUS["ACTUS Contract"] --> NORMALIZE["AVM Normalization"]
  NORMALIZE --> ABI["ABI Upload"]
  ABI --> EXEC["AVM Execution"]

This separation is intentional:

  • ACTUS contract definition and schedule generation happen off chain.
  • Normalization converts that contract into fixed-size AVM-compatible structs.
  • The AVM kernel validates and executes the normalized lifecycle.

Module responsibilities

  • RbacModule: role assignment, suspension, contract default control, and application-control methods.

  • ActusKernelModule: normalized-term validation, schedule storage, event cursor, state transitions, and generic ACTUS getters.

  • AccountingModule: holder positions, unit activation, checkpoints, and claim ledgers.

  • PaymentAgent: funding of due cash events and holder withdrawals.

  • TransferAgent: primary distribution, transfer schedule, and settled unit transfers.

Public API by layer

LayerPublic methods
RBACcontract_update, rbac_rotate_arranger, rbac_set_op_daemon, rbac_assign_role, rbac_revoke_role, rbac_contract_suspension, rbac_contract_default, rbac_get_arranger, rbac_get_address_roles, rbac_get_role_validity
Accountingaccount_suspension, account_open, account_update_payment_address, account_get_position
ACTUS Kernelcontract_create, contract_config, contract_schedule, contract_execute_ied, apply_non_cash_event, append_observed_cash_event, contract_get_state, contract_get_next_due_event
Payment Agentfund_due_cashflows, claim_due_cashflows
Transfer Agenttransfer_set_schedule, primary_distribution, transfer

Execution flows

Configuration

sequenceDiagram
  participant SDK as "Normalization SDK"
  participant ABI as "ABI Caller"
  participant K as "ACTUS Kernel"

  SDK->>ABI: "NormalizedActusTerms + InitialKernelState + Schedule Pages"
  ABI->>K: "contract_config(...)"
  ABI->>K: "contract_schedule(page 0)"
  ABI->>K: "contract_schedule(page n, is_last_page=true)"
  ABI->>K: "primary_distribution(...)"
  ABI->>K: "contract_execute_ied()"

Cash events

sequenceDiagram
  participant ABI as "ABI Caller"
  participant PA as "Payment Agent"
  participant ACC as "Accounting"

  ABI->>PA: "fund_due_cashflows(max_event_count)"
  PA->>ACC: "advance global indices and reserve funds"
  ABI->>PA: "claim_due_cashflows(holding_address, payment_info)"
  PA->>ACC: "settle holder checkpoints"
  PA-->>ABI: "CashClaimResult"

Transfers

sequenceDiagram
  participant ABI as "Holder"
  participant TA as "Transfer Agent"
  participant ACC as "Accounting"

  ABI->>TA: "transfer(sender, receiver, units)"
  TA->>ACC: "settle sender"
  TA->>ACC: "settle receiver"
  TA->>ACC: "move active units"

Tests

The D-ASA project is developed with AlgoKit.

  • Install AlgoKit
  • Setup your virtual environment (managed with Poetry)
algokit bootstrap all
  • Start your Algorand LocalNet (requires Docker)
algokit localnet start
  • Run tests (managed with PyTest)
algokit project run test

or, for verbose results:

poetry run pytest -s -v tests/<contract_name>/<test_case>.py

The repository also includes a D-ASA showcase walkthrough that prints the normalized ACTUS schedule beside the real ARC-28 execution proofs and realized cashflows for PAM fixed coupon and zero coupon bonds:

poetry run pytest -s -v -m showcase tests/pam/test_pam_lifecycle_showcase.py

PAM Fixed Coupon Bond example

Coverage Areas

  • tests/sdk/* covers the Python-side SDK and normalization layer: contract builders, schedules, day-count conventions, registry mappings, models, and deploy configuration helpers.

  • tests/mock_module_rbac/* exercises RBAC behavior in isolation through the dedicated mock module artifact.

  • tests/pam/fcb/* and tests/pam/zcb/* run end-to-end PAM lifecycle tests on LocalNet for fixed coupon and zero coupon bonds, including schedule upload, IED, cashflow funding, holder claims, and final contract state.

  • tests/pam/test_pam_lifecycle_showcase.py is the narrative walkthrough suite for new users. It prints the normalized ACTUS schedule beside the emitted ARC-28 execution receipts and realized cashflows. It is marked showcase and excluded from the default algokit project run test command.

  • Shared fixtures live in tests/conftest.py, tests/pam/conftest.py, and tests/conftest_helpers.py; helper decoding and time-warp utilities live in tests/utils.py.

DeepWiki (Experimental)

⚠️ This is experimental AI-generated documentation of the D-ASA reference repository. Readers may use this documentation to complement the contents of this documentation, to deepen understanding of the D-ASA architecture and reference implementation.

DeepWiki of the D-ASA repository:

https://deepwiki.com/cusma/d-asa

The embedded chatbot, with deep-research capabilities over the D-ASA codebase, can answer specific questions.

Contributing to D-ASA

Thank you for contributing to D-ASA.

Prerequisites

The standard contributor workflow assumes:

  • AlgoKit for project bootstrap, LocalNet, build, and deployment workflows
  • Poetry for the Python environment
  • Docker for Algorand LocalNet
  • pre-commit for local checks

Docs contributors also need:

Getting Started

Clone the Repository

git clone git@github.com:cusma/d-asa.git
cd d-asa

Bootstrap the Development Environment

The standard setup path is:

algokit project bootstrap all
make pre-commit
make doctor

If you are only setting up Python dependencies:

make install

If you are contributing to docs, install the mdBook toolchain as well:

cargo install mdbook --version 0.5.2 --locked
cargo install mdbook-mermaid --version 0.17.0 --locked
mdbook-mermaid install .

Verify the Environment

Run:

make doctor

make doctor runs algokit doctor, checks core contributor dependencies, checks Docker daemon reachability for LocalNet workflows, and warns if docs-only tools are missing.

Development Workflow

Core Commands

The main contributor commands are:

make help
make build
make test
make test-cov
make lint
make format
make typecheck
make all

make all runs the standard code quality loop for contributors:

  • build smart contracts
  • run lint and type checks
  • run the default non-showcase test suite

It intentionally does not require mdBook tooling.

LocalNet Workflow

Start LocalNet when you need end-to-end execution tests or LocalNet deployment:

make localnet
make deploy-localnet
make showcase
make localnet-stop

These targets map to the existing AlgoKit workflow already used by CI and the project scripts.

Docs Workflow

For docs editing with live reload:

make docs-serve

For docs validation:

make docs

make docs runs both:

  • the docs pre-commit hooks:
    • mdbook-build
    • mdbook-test
    • markdownlint
    • trailing-whitespace
  • the mdBook build artifacts remain available in book/ until make clean

Pull Requests

When preparing a pull request:

  1. Keep changes focused and atomic.
  2. Add or update tests when behavior changes.
  3. Update docs when user-facing behavior, workflows, or interfaces change.
  4. Run the relevant local checks before opening the PR.

Suggested pre-PR checklist:

make doctor
make all

If you changed docs:

make docs

If you changed LocalNet behavior or showcase flows:

make localnet
make showcase
make localnet-stop

Repository Layout

The main top-level layout is:

.algokit/          -> AlgoKit configuration and generators
.github/           -> CI/CD workflows and shared GitHub actions
docs/              -> mdBook source files
modules/           -> D-ASA module implementations
smart_contracts/   -> AVM smart contracts and generated contract artifacts
src/               -> D-ASA SDK, Python-side normalization, helpers, and shared runtime code
tests/             -> SDK, contract, LocalNet, and showcase tests

Style and Quality

Python

The project uses:

  • black for formatting
  • ruff for linting and import organization
  • mypy for type checking
  • pytest for tests

Run the standard checks with:

make format
make lint
make typecheck
make test

Markdown and mdBook

The docs are written in CommonMark and validated in CI with markdownlint, trailing whitespace checks, and mdbook test.

Numbered Lists

Numbered lists MUST use 1.-only style.

1. First item
1. Second item
1. Third item

Tables

Table rows MUST keep aligned column widths.

✅ Correct table format

| Month    | Savings |
|----------|---------|
| January  | EUR 250 |
| February | EUR 80  |
| March    | EUR 420 |

❌ Wrong table format

| Month | Savings |
|----------|---------|
| January | EUR 250 |
| February | EUR 80 |
| March | EUR 420 |

Column alignment may be controlled with : markers in the separator row.

Admonitions

An admonition is a special type of callout or notice block used to highlight important information. It is written as a blockquote with a special tag on the first line.

> [!NOTE]
> General information or additional context.

> [!TIP]
> A helpful suggestion or best practice.

> [!IMPORTANT]
> Key information that shouldn't be missed.

> [!WARNING]
> Critical information that highlights a potential risk.

> [!CAUTION]
> Information about potential issues that require caution.

MathJax

Mathematical formulas are rendered with MathJax.

Reference:

Additional Notes

  • Do not remove or hand-edit generated artifacts under smart_contracts/artifacts or src/artifacts unless the change explicitly requires regenerated output.
  • Prefer the checked-in algokit commands and make targets over ad-hoc local command variations so contributor workflows stay aligned with CI.

License

                    GNU AFFERO GENERAL PUBLIC LICENSE
                       Version 3, 19 November 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.

  A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate.  Many developers of free software are heartened and
encouraged by the resulting cooperation.  However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.

  The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community.  It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server.  Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.

  An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals.  This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU Affero General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Remote Network Interaction; Use with the GNU General Public License.

  Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software.  This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time.  Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source.  For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code.  There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.