Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="realunit-wallet"/>
</intent-filter>
<!-- HTTPS App Links for invite codes (verified against Digital
Asset Links for host realunit.app). Custom-scheme filter
above still covers realunit-wallet://invite/{code}. -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="https"
android:host="realunit.app"
android:pathPrefix="/invite"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
Expand Down
22 changes: 21 additions & 1 deletion assets/languages/strings_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,26 @@
"receiver": "Empfänger",
"recoveryWords": "Wiederherstellungs-Wörter",
"recoveryWordsInvalid": "Wiederherstellungs-Wörter ungültig",
"referralCodeHint": "Einladungs- oder Promo-Code",
"referralCodeOptional": "Einladungs- oder Promo-Code (optional)",
"referralCopyInviteLink": "Einladungslink kopieren",
"referralCreateInvite": "Einladungslink erstellen",
"referralCreateInviteDescription": "Gib den Vornamen der Person ein, die du einladen möchtest.",
"referralGuestName": "Name der eingeladenen Person",
"referralInviteUrlLabel": "Persönlicher Einladungslink",
"referralNotEligible": "Das Empfehlungsprogramm steht verifizierten Aktionären mit dem erforderlichen Bestand zur Verfügung.",
"referralPayout": "Empfehlungsprämie",
"referralPayoutChf": "CHF ${chf} zum Zeitpunkt der Gutschrift",
"referralPromoTitle": "Aktion",
"referrals": "Empfehlungen",
"referralShareInviteLink": "Einladungslink versenden",
"referralSharePrice": "Aktienkurs",
"referralShareText": "Hey ${guestName}, ${hostName} lädt dich ein zu RealUnit: ${url}",
"referralsSubtitle": "Erhalte 20 REALU pro Weiterempfehlung",
"referralStatusCredited": "Gutgeschrieben",
"referralStatusOpen": "Offen",
"referralTermsCheckbox": "Ich habe die Teilnahmebedingungen gelesen und akzeptiert",
"referralTotalReceived": "Insgesamt erhalten",
"refresh": "Aktualisieren",
"registerAccountType": "Kontotyp",
"registerCitizenship": "Staatsangehörigkeit",
Expand Down Expand Up @@ -466,4 +486,4 @@
"youPay": "Sie bezahlen",
"youReceive": "Sie erhalten",
"youSell": "Sie verkaufen"
}
}
22 changes: 21 additions & 1 deletion assets/languages/strings_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,26 @@
"receiver": "Receiver",
"recoveryWords": "recovery words",
"recoveryWordsInvalid": "Recovery words invalid",
"referralCodeHint": "Invite or promo code",
"referralCodeOptional": "Invite or promo code (optional)",
"referralCopyInviteLink": "Copy invite link",
"referralCreateInvite": "Create invite link",
"referralCreateInviteDescription": "Enter the first name of the person you want to invite.",
"referralGuestName": "Name of the invited person",
"referralInviteUrlLabel": "Personal invite link",
"referralNotEligible": "The referral programme is available once you are a verified shareholder with the required holding.",
"referralPayout": "Referral prize",
"referralPayoutChf": "CHF ${chf} at credit",
"referralPromoTitle": "Promotion",
"referrals": "Referrals",
"referralShareInviteLink": "Share invite link",
"referralSharePrice": "Share price",
"referralShareText": "Hey ${guestName}, ${hostName} is inviting you to RealUnit: ${url}",
"referralsSubtitle": "Receive 20 REALU per referral",
"referralStatusCredited": "Credited",
"referralStatusOpen": "Open",
"referralTermsCheckbox": "I have read and accepted the terms of participation",
"referralTotalReceived": "Total received",
"refresh": "Refresh",
"registerAccountType": "Account type",
"registerCitizenship": "Citizenship",
Expand Down Expand Up @@ -466,4 +486,4 @@
"youPay": "You pay",
"youReceive": "You receive",
"youSell": "You sell"
}
}
47 changes: 47 additions & 0 deletions assets/legal/referral_terms_de.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Teilnahmebedingungen Referral-Programm

*Stand: 14.08.2026*

## 1. Veranstalterin

RealUnit Schweiz AG, Schochenmühlestrasse 6, 6340 Baar. Das Referral-Programm wird in der RealUnit App angeboten.

## 2. Teilnahmeberechtigung

Teilnahmeberechtigt sind Nutzerinnen und Nutzer der RealUnit App, die mindestens 70 RealUnit-Aktientoken halten. Massgeblich für die Teilnahmeberechtigung ist der Stand bei Erstellung der Einladung und die Qualifikation nach Ziff. 4. Mitarbeitende und Organe der RealUnit Schweiz AG sowie mit ihr verbundene Unternehmen sind von der Teilnahme ausgeschlossen.

## 3. Ablauf

Die teilnehmende Person erstellt in der App einen persönlichen Einladungslink für eine namentlich genannte Person. Die eingeladene Person darf noch nicht in der App registriert sein.

## 4. Prämie

Für jede erfolgreiche Empfehlung erhält die teilnehmende Person 20 RealUnit-Aktientoken. Erfolgreich ist eine Empfehlung, wenn die eingeladene Person sich registriert und einen ersten Kauf von RealUnit-Aktientoken tätigt. Der Frankenwert der Gutschrift wird in der Transaktionshistorie ausgewiesen. Die Prämie ist Entgelt für die Vermittlung.

## 5. Obergrenze

Es werden höchstens einhundert erfolgreiche Empfehlungen innerhalb eines Quartals vergütet.

## 6. Steuern

Die Prämie stellt steuerbares Einkommen dar. Die Anzahl, der Frankenwert und das Datum der Gutschrift sind in der App ausgewiesen. RealUnit erteilt keine Steuerauskunft.

## 7. Datenschutz

Die empfehlende Person sieht nicht Registrierung, Verifizierung oder Käufe der eingeladenen Person. Offene Einladungen werden nach drei Monaten gelöscht.

## 8. Missbrauch

Selbstempfehlung, die Nutzung mehrfacher Identitäten und koordinierte Anwerbung sind untersagt. Bei Missbrauch kann RealUnit Prämien verweigern oder zurückfordern und die Teilnahme beenden.

## 9. Werbung

Für die Einladung darf nur der in der App bereitgestellte Einladungslink verwendet werden.

## 10. Dauer, Änderung und Beendigung

RealUnit kann das Programm jederzeit ändern oder beenden. Bereits entstandene Ansprüche bleiben bestehen.

## 11. Schweizer Recht, Gerichtsstand Baar

Es gilt Schweizer Recht. Gerichtsstand ist Baar.
47 changes: 47 additions & 0 deletions assets/legal/referral_terms_en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Terms of Participation — Referral Programme

*As of: 14 August 2026*

## 1. Organiser

RealUnit Schweiz AG, Schochenmühlestrasse 6, 6340 Baar. The referral programme is offered in the RealUnit app.

## 2. Eligibility

Eligible are users of the RealUnit app who hold at least 70 RealUnit share tokens. Eligibility is determined at the time the invitation is created and by the qualification under section 4. Employees and officers of RealUnit Schweiz AG and of companies affiliated with it are excluded from participation.

## 3. Process

The participant creates a personal invitation link in the app for a named person. The invited person must not already be registered in the app.

## 4. Reward

For each successful referral the participant receives 20 RealUnit share tokens. A referral is successful when the invited person registers and makes a first purchase of RealUnit share tokens. The Swiss-franc value of the credit is shown in the transaction history. The reward is remuneration for the introduction.

## 5. Cap

At most one hundred successful referrals are remunerated within a calendar quarter.

## 6. Taxes

The reward constitutes taxable income. The quantity, Swiss-franc value and date of the credit are shown in the app. RealUnit does not provide tax advice.

## 7. Data protection

The referrer does not see the invitee's registration, verification or purchases. Open invitations are deleted after three months.

## 8. Abuse

Self-referral, use of multiple identities and coordinated solicitation are prohibited. In case of abuse RealUnit may refuse or claw back rewards and terminate participation.

## 9. Advertising

Only the invitation link provided in the app may be used for invitations.

## 10. Duration, changes and termination

RealUnit may change or end the programme at any time. Claims that have already arisen remain unaffected.

## 11. Swiss law, place of jurisdiction Baar

Swiss law applies. Place of jurisdiction is Baar.
4 changes: 4 additions & 0 deletions ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:realunit.app</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
Expand Down
11 changes: 10 additions & 1 deletion lib/models/transaction.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import 'package:realunit_wallet/models/asset.dart';
import 'package:realunit_wallet/packages/service/transaction_history_service.dart';

enum TransactionTypes { transfer, genericContractCall, tokenTransfer, savingsAdd, savingsRemove }
enum TransactionTypes {
transfer,
genericContractCall,
tokenTransfer,
savingsAdd,
savingsRemove,
/// Referral/promo payout credited via the API. [Transaction.data] holds the
/// CHF value frozen at credit (decimal string); never recompute from price.
referralPayout,
}

/// Transaction with on-chain metadata
class Transaction {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// Response from `POST /v1/realunit/referral/bind`.
/// For `kind == Promo`, [campaignText] / [campaignTextEn] carry the prescribed
/// wording from the API — do not compose campaign copy in the app.
class ReferralBindResultDto {
final String kind;
final String? campaignText;
final String? campaignTextEn;
final num? minBuyRealu;
final DateTime? validUntil;
final num? redemptionCap;

const ReferralBindResultDto({
required this.kind,
this.campaignText,
this.campaignTextEn,
this.minBuyRealu,
this.validUntil,
this.redemptionCap,
});

bool get isPromo => kind == 'Promo';
bool get isInvite => kind == 'Invite';

/// Locale-aware campaign wording. EN falls back to DE when the EN field is absent.
String? campaignTextForLocale(String languageCode) {
if (languageCode == 'en') {
return campaignTextEn ?? campaignText;
}
return campaignText ?? campaignTextEn;
}

factory ReferralBindResultDto.fromJson(Map<String, dynamic> json) {
return ReferralBindResultDto(
kind: json['kind'] as String,
campaignText: json['campaignText'] as String?,
campaignTextEn: json['campaignTextEn'] as String?,
minBuyRealu: json['minBuyRealu'] as num?,
validUntil: json['validUntil'] != null
? DateTime.parse(json['validUntil'] as String)
: null,
redemptionCap: json['redemptionCap'] as num?,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/// Response from `POST /v1/realunit/referral/invites`.
/// `copyText` is a German-only API fallback; prefer i18n share text when available.
class ReferralCreatedInviteDto {
final String code;
final String url;
final String guestName;
final String? copyText;

const ReferralCreatedInviteDto({
required this.code,
required this.url,
required this.guestName,
this.copyText,
});

factory ReferralCreatedInviteDto.fromJson(Map<String, dynamic> json) {
return ReferralCreatedInviteDto(
code: json['code'] as String,
url: json['url'] as String,
guestName: json['guestName'] as String,
copyText: json['copyText'] as String?,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/// One invite row from `GET /v1/realunit/referral/invites`.
/// Status values from the API: `Open` / `Bound` / `Credited` / `Deleted`.
/// The UI only surfaces Open as "offen" and Credited as "gutgeschrieben".
class ReferralInviteDto {
final int id;
final String code;
final String url;
final String guestName;
final String status;
final DateTime created;

const ReferralInviteDto({
required this.id,
required this.code,
required this.url,
required this.guestName,
required this.status,
required this.created,
});

bool get isOpen => status == 'Open';
bool get isCredited => status == 'Credited';

factory ReferralInviteDto.fromJson(Map<String, dynamic> json) {
return ReferralInviteDto(
id: (json['id'] as num).toInt(),
code: json['code'] as String,
url: json['url'] as String,
guestName: json['guestName'] as String,
status: json['status'] as String,
created: DateTime.parse(json['created'] as String),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/// One referral payout row from `GET /v1/realunit/referral/payouts`.
/// [chfValue] is the CHF amount frozen at credit — never recompute from current price.
class ReferralPayoutDto {
final int id;
final num amount;
final num chfValue;
final DateTime created;
final String kind;
final String status;
final String? txHash;

const ReferralPayoutDto({
required this.id,
required this.amount,
required this.chfValue,
required this.created,
required this.kind,
required this.status,
this.txHash,
});

factory ReferralPayoutDto.fromJson(Map<String, dynamic> json) {
return ReferralPayoutDto(
id: (json['id'] as num).toInt(),
amount: json['amount'] as num,
chfValue: json['chfValue'] as num,
created: DateTime.parse(json['created'] as String),
kind: json['kind'] as String,
status: json['status'] as String,
txHash: json['txHash'] as String?,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/// Server-side referral programme summary for the current wallet
/// (`GET /v1/realunit/referral/summary`). `eligible` is the authoritative
/// entry gate — see CONTRIBUTING.md "API as Decision Authority".
class ReferralSummaryDto {
final bool eligible;
final bool termsAccepted;
final num? minHolding;
final int openCount;
final int creditedCount;
final num realuSum;
final num chfSum;

const ReferralSummaryDto({
required this.eligible,
required this.termsAccepted,
this.minHolding,
required this.openCount,
required this.creditedCount,
required this.realuSum,
required this.chfSum,
});

factory ReferralSummaryDto.fromJson(Map<String, dynamic> json) {
return ReferralSummaryDto(
eligible: json['eligible'] as bool,
termsAccepted: json['termsAccepted'] as bool,
minHolding: json['minHolding'] as num?,
openCount: (json['openCount'] as num).toInt(),
creditedCount: (json['creditedCount'] as num).toInt(),
realuSum: json['realuSum'] as num,
chfSum: json['chfSum'] as num,
);
}
}
Loading