- return to IOTA Rebased Useful Links
[1] Sevens Contract Development
The concept is a simple betting game of forecasting the seven 'heads/tails' combination that the contract produces. It uses the IOTA Rebased Clock to generate some level of randomness.
Basic contract preparation
In a folder called 'sevens' there is a 'source' folder containing sevens.move, and a Move.toml file.
First build ...
% iota move build
Then publish ...
iota client publish \
--gas-budget 60000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922
[2] Key contract parameters
Transaction Digest: 4AN3LyoURzPbgK2ReMLRAyxBkuP4B1GTgJ5nk23z2pnp
Sender: 0xc196e256a58a1ea07e2cb27887090c3cf6a1ad1ec189c8a40575e7cde6c3dc4a
PackageID: 0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7
UpgradeCap: 0x7d171a1cd6f2a45c7ddafd610504d920a6975da33a0584e5f8a0c093fd349c32
Initialisation
iota client call \
--gas-budget 10000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922 \
--function initialize \
--module game \
--package 0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7
After initialisation we get
Transaction Digest: BiuP2tPbXKPsCQMfZhoAoLzbcqvrbzDniUWuB64TMs6e
GlobalConfig: 0x56696e0f6f1644000be472ea082e13fa95225fce81d95d3803cbacd0b9401692
(has fields: initialized, bet_size and test_mode)
AdminCap: 0xadd757133e800d5cbc504588064cc34bfaffc07b83b9efac373b5aba87d87882
The AdminCap is an object owned by sender address. Move's ownership system enforces only owner can use their objects.
[3] Setup Call
iota client call \
--gas-budget 10000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922 \
--function setup \
--module game \
--package 0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7 \
--args "0xadd757133e800d5cbc504588064cc34bfaffc07b83b9efac373b5aba87d87882" \
"0x56696e0f6f1644000be472ea082e13fa95225fce81d95d3803cbacd0b9401692"
Transaction Digest: 4jJ4AkLxQGYEpGT1F6QitfBvLCFAYnk7VzUEZd7yau5u
Treasury: 0x7f3adc8be31df99ee346e54f2765b40372d11c7f83a32f8908f431ca377ca95b
[4] Funding treasury
iota client call \
--gas-budget 10000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922 \
--function add_funds \
--module game \
--package 0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7 \
--args "0xadd757133e800d5cbc504588064cc34bfaffc07b83b9efac373b5aba87d87882" \
"0x7f3adc8be31df99ee346e54f2765b40372d11c7f83a32f8908f431ca377ca95b" \
"0x88688bee8b3bbb26021d7ce9c3b865e7908961eb79fbf2e9f3010c11fa72a795"
Transaction Digest: D5wxZs4f9tgRRSNbCJyGopkZfDHWorD78yXM9BG3C7yd
[5] Put into Test Mode
iota client call \
--gas-budget 10000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922 \
--function toggle_test_mode \
--module game \
--package 0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7 \
--args "0xadd757133e800d5cbc504588064cc34bfaffc07b83b9efac373b5aba87d87882" \
"0x56696e0f6f1644000be472ea082e13fa95225fce81d95d3803cbacd0b9401692"
Transaction Digest: F1qJbeyeomy3nFLeKDY2iKPp4CXkM7GAzzigv5mWrut7
Playing a round
iota client call \
--gas-budget 10000000 \
--gas 0x034010c356652f1f28e41ef45831aa983f920cbdb799c974fa69ced276454922 \
--function play \
--module game \
--package 0x6be8f571710d2c8f9729cab672e0ba9b1e587ed022ad50caee2f43378027c053 \
--args \
"0x2bcc0bb8debc70afed41230609a7bef61d7828a918237d3c95860e6cbfbaa3ba" \
"0x519c01c5974ca6bdd54afde788bd426fe957c7412ee9518da5403db7fff5cbac" \
"0x6" \
"true" "false" "true" "false" "true" "false" "true" \
"0x3ba7de8981bbaa90f8040579c1dea10ed93b96d35768734a0d49d3960a0f774c"
Transaction Digests: (test mode)
C7MhiFhwaVLwzbHmbaCLskf9nUpyvDbaTz5di8Ajbca3 (lose)
BcYYH2zvxofNs2iD14EYNo7vKaxbM4qu9ATHW7RaGdDv (lose)
AEBSrouA9JRGEtwFtQWEuJF4TYmxgBNpnCYN8xREzC6W (win 1 IOTA)
[6] A result from the call
This is Test Mode so need just three correct guesses, as in this example
{
"id": {
"txDigest": "AEBSrouA9JRGEtwFtQWEuJF4TYmxgBNpnCYN8xREzC6W",
"eventSeq": "0"
},
"packageId": "0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7",
"transactionModule": "game",
"sender": "0xc196e256a58a1ea07e2cb27887090c3cf6a1ad1ec189c8a40575e7cde6c3dc4a",
"type": "0x7cf8acc5650377cf7de884082a528294f3be31269c180a151935630b6cd709b7::game::GameResult",
"parsedJson": {
"amount_wagered": "10000000",
"clock_digits": [
2,
3,
9,
8,
9,
0,
8
],
"clock_value": "1735498098932",
"contract_sequence": [
72,
72,
84,
84,
84,
84,
72
],
"matches": "3",
"payout": "1000000000",
"player": "0xc196e256a58a1ea07e2cb27887090c3cf6a1ad1ec189c8a40575e7cde6c3dc4a",
"player_sequence": [
72,
72,
84,
84,
72,
84,
72
],
"random_digits": [
2,
9,
6,
3,
0,
7,
4
]
},
"bcs": "29gpzUQbBnPVPkY7YoF4FDJzfX94ztFEYbfCXmAcy2djhF3An37QyoF5NC6F1ArPrnvEAhbedC97gFsDgAjctf7pwqTXosCfybwa3Dt731Bzgj2wPE16QEYF5iMHUvXtXWxP",
"timestampMs": "1735498098932"
}
[7] The code for sevens.move
module sevens::game {
use iota::coin::{Self, Coin};
use iota::event;
use iota::balance::{Self, Balance};
use iota::clock::{Self, Clock};
use iota::iota::IOTA;
// ======== Capability and Config ========
public struct AdminCap has key, store {
id: UID
}
public struct GlobalConfig has key {
id: UID,
initialized: bool,
bet_size: u64,
test_mode: bool
}
// ======== Treasury ========
public struct Treasury has key, store {
id: UID,
funds: Balance
}
// ======== Game Result Event ========
public struct GameResult has copy, drop {
player: address,
player_sequence: vector,
contract_sequence: vector,
clock_value: u64,
clock_digits: vector,
random_digits: vector,
amount_wagered: u64,
payout: u64,
matches: u64
}
// ======== Error Codes ========
const ALREADY_INITIALIZED: u64 = 1;
const INSUFFICIENT_FUNDS: u64 = 2;
const INVALID_SEQUENCE: u64 = 3;
// ======== Constants ========
const SEQUENCE_LENGTH: u64 = 7;
const TEST_SEQUENCE_LENGTH: u64 = 3;
const PERFECT_MATCH_MULTIPLIER: u64 = 100;
const ZERO_MATCH_MULTIPLIER: u64 = 20;
const DEFAULT_BET_SIZE: u64 = 10000000; // 0.01 IOTA
// ======== Initialize System ========
public entry fun initialize(ctx: &mut TxContext) {
transfer::share_object(
GlobalConfig {
id: object::new(ctx),
initialized: false,
bet_size: DEFAULT_BET_SIZE,
test_mode: false
}
);
transfer::public_transfer(
AdminCap {
id: object::new(ctx)
},
tx_context::sender(ctx)
);
}
// ======== Setup Game System ========
public entry fun setup(
_admin_cap: &AdminCap,
config: &mut GlobalConfig,
ctx: &mut TxContext
) {
assert!(!config.initialized, ALREADY_INITIALIZED);
let treasury = Treasury {
id: object::new(ctx),
funds: balance::zero()
};
config.initialized = true;
transfer::share_object(treasury);
}
// ======== Admin Functions ========
public entry fun set_bet_size(
_admin_cap: &AdminCap,
config: &mut GlobalConfig,
new_bet_size: u64
) {
config.bet_size = new_bet_size;
}
public entry fun toggle_test_mode(
_admin_cap: &AdminCap,
config: &mut GlobalConfig
) {
config.test_mode = !config.test_mode;
}
public entry fun add_funds(
_admin_cap: &AdminCap,
treasury: &mut Treasury,
coin: Coin,
_ctx: &mut TxContext
) {
balance::join(&mut treasury.funds, coin::into_balance(coin));
}
public entry fun withdraw_funds(
_admin_cap: &AdminCap,
treasury: &mut Treasury,
amount: u64,
ctx: &mut TxContext
) {
let treasury_balance = balance::value(&treasury.funds);
assert!(treasury_balance >= amount, INSUFFICIENT_FUNDS);
let withdrawal = coin::from_balance(
balance::split(&mut treasury.funds, amount),
ctx
);
transfer::public_transfer(withdrawal, tx_context::sender(ctx));
}
// ======== Play Game ========
public entry fun play(
treasury: &mut Treasury,
config: &GlobalConfig,
clock: &Clock,
pos1: bool, pos2: bool, pos3: bool, pos4: bool, pos5: bool, pos6: bool, pos7: bool,
bet: Coin,
ctx: &mut TxContext
) {
let player = tx_context::sender(ctx);
let bet_amount = coin::value(&bet);
// Verify bet amount matches configured size
assert!(bet_amount == config.bet_size, INVALID_SEQUENCE);
// Convert boolean sequence to vector (H=72, T=84)
let mut player_sequence = vector::empty();
vector::push_back(&mut player_sequence, if (pos1) 72 else 84);
vector::push_back(&mut player_sequence, if (pos2) 72 else 84);
vector::push_back(&mut player_sequence, if (pos3) 72 else 84);
vector::push_back(&mut player_sequence, if (pos4) 72 else 84);
vector::push_back(&mut player_sequence, if (pos5) 72 else 84);
vector::push_back(&mut player_sequence, if (pos6) 72 else 84);
vector::push_back(&mut player_sequence, if (pos7) 72 else 84);
// Add bet to treasury
balance::join(&mut treasury.funds, coin::into_balance(bet));
// Generate contract sequence using new randomization method
let clock_value = clock::timestamp_ms(clock);
let (contract_sequence, clock_digits, random_digits) = generate_sequence(clock_value);
// Count matches
let matches = count_matches(&player_sequence, &contract_sequence, config.test_mode);
// Calculate payout
let payout = calculate_payout(matches, bet_amount, config.test_mode);
// Process payout if any
if (payout > 0) {
let treasury_balance = balance::value(&treasury.funds);
assert!(treasury_balance >= payout, INSUFFICIENT_FUNDS);
let payment = coin::from_balance(
balance::split(&mut treasury.funds, payout),
ctx
);
transfer::public_transfer(payment, player);
};
// Emit game result event
event::emit(GameResult {
player,
player_sequence,
contract_sequence,
clock_value,
clock_digits,
random_digits,
amount_wagered: bet_amount,
payout,
matches
});
}
// ======== Helper Functions ========
fun generate_sequence(clock_value: u64): (vector, vector, vector) {
// Extract last 7 digits from clock
let mut clock_digits = vector::empty();
let mut temp_clock = clock_value;
let mut i = 0;
while (i < SEQUENCE_LENGTH) {
let digit = ((temp_clock % 10) as u8);
vector::push_back(&mut clock_digits, digit);
temp_clock = temp_clock / 10;
i = i + 1;
};
// Generate random digits
let seed = clock_value % 10000000; // Last 7 digits for seed
let mut random_digits = vector::empty();
let mut i = 0;
while (i < SEQUENCE_LENGTH) {
let digit = (((seed + (i * 17)) % 10) as u8);
vector::push_back(&mut random_digits, digit);
i = i + 1;
};
// Generate sequence based on digit parity comparison
let mut sequence = vector::empty();
let mut i = 0;
while (i < SEQUENCE_LENGTH) {
let clock_digit = *vector::borrow(&clock_digits, i);
let random_digit = *vector::borrow(&random_digits, i);
let clock_even = (clock_digit % 2) == 0;
let random_even = (random_digit % 2) == 0;
let is_heads = clock_even == random_even;
vector::push_back(&mut sequence, if (is_heads) 72 else 84);
i = i + 1;
};
(sequence, clock_digits, random_digits)
}
fun count_matches(player_seq: &vector, contract_seq: &vector, test_mode: bool): u64 {
let mut matches = 0;
let length = if (test_mode) { TEST_SEQUENCE_LENGTH } else { SEQUENCE_LENGTH };
let mut i = 0;
while (i < length) {
if (*vector::borrow(player_seq, i) == *vector::borrow(contract_seq, i)) {
matches = matches + 1;
};
i = i + 1;
};
matches
}
fun calculate_payout(matches: u64, bet_amount: u64, test_mode: bool): u64 {
let required_matches = if (test_mode) { TEST_SEQUENCE_LENGTH } else { SEQUENCE_LENGTH };
if (matches == required_matches) {
bet_amount * PERFECT_MATCH_MULTIPLIER
} else if (matches == 0) {
bet_amount * ZERO_MATCH_MULTIPLIER
} else {
0
}
}
}