API Reference
API Reference
Try it out
The API Reference example code can be found in Github repo icpp-demos.
CandidTypes
Candid / C++ conversion tables
CandidType | Candid | C++ | Example |
---|---|---|---|
CandidTypeBool | bool | bool | example |
CandidTypeEmpty | empty | - | - |
CandidTypeFloat32 | float32 | float | example |
CandidTypeFloat64 | float64 | double | example |
CandidTypeInt8 | int8 | int8_t | example |
CandidTypeInt16 | int16 | int16_t | example |
CandidTypeInt32 | int32 | int32_t | example |
CandidTypeInt64 | int64 | int64_t | example |
CandidTypeInt | int | __int128_t | example |
CandidTypeNat8 | nat8 | uint8_t | example |
CandidTypeNat16 | nat16 | uint16_t | example |
CandidTypeNat32 | nat32 | uint32_t | example |
CandidTypeNat64 | nat64 | uint64_t | example |
CandidTypeNat | nat | __uint128_t | example |
CandidTypeNull | null | - | - |
CandidTypeOpt | opt | std::optional | example |
CandidTypePrincipal | principal | std::string | example |
CandidTypeRecord | record | custom | example |
CandidTypeText | text | std::string | example |
CandidTypeVec | vec | std::vector | example |
CandidTypeVariant | variant | custom | example |
CandidTypeBool
A class to convert between Candid bool
and C++ bool
.
Declaration:
// candid.h
class CandidTypeBool {
public:
CandidTypeBool(bool *v);
CandidTypeBool(const bool v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_bool.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_bool '(true)'
(true)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_bool received value '1'
$ dfx canister call --type idl --output idl demo demo_candid_type_bools '(true, false)'
(true, false)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_bools received values '1' & '0'
*/
#include "demo_candid_type_bool.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_bool() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
bool in{false};
ic_api.from_wire(CandidTypeBool{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeBool{in});
}
void demo_candid_type_bools() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
bool in1{false};
bool in2{false};
CandidArgs args_in;
args_in.append(CandidTypeBool(&in1));
args_in.append(CandidTypeBool(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeBool(in1));
args_out.append(CandidTypeBool(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_bool.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_bool()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_bool");
void demo_candid_type_bools()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_bools");
// file: src/demo.did
service : {
"demo_candid_type_bool" : (bool) -> (bool) query;
"demo_candid_type_bools" : (bool, bool) -> (bool, bool) query;
};
CandidTypeFloat32
A class to convert between Candid float32
and C++ float
.
Declaration:
// candid.h
class CandidTypeFloat32 {
public:
CandidTypeFloat32(float *v);
CandidTypeFloat32(const float v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_float32.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_float32 '(0.1 : float32)'
(0.1 : float32)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_float32 received value '0.100000'
$ dfx canister call --type idl --output idl demo demo_candid_type_float32s '(0.1 : float32, -1.2 : float32)'
(0.1 : float32, -1.2 : float32)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_float32s received values '0.100000' & '-1.200000'
*/
#include "demo_candid_type_float32.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_float32() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
float in{0.0};
ic_api.from_wire(CandidTypeFloat32{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeFloat32{in});
}
void demo_candid_type_float32s() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
float in1{0.0};
float in2{0.0};
CandidArgs args_in;
args_in.append(CandidTypeFloat32(&in1));
args_in.append(CandidTypeFloat32(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeFloat32(in1));
args_out.append(CandidTypeFloat32(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_float32.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_float32()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_float32");
void demo_candid_type_float32s()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_float32s");
// file: src/demo.did
service : {
"demo_candid_type_float32" : (float32) -> (float32) query;
"demo_candid_type_float32s" : (float32, float32) -> (float32, float32) query;
};
CandidTypeFloat64
A class to convert between Candid float64
and C++ double
.
Declaration:
// candid.h
class CandidTypeFloat64 {
public:
CandidTypeFloat64(double *v);
CandidTypeFloat64(const double v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_float64.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_float64 '(0.1 : float64)'
(0.1 : float64)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_float64 received value '0.100000'
$ dfx canister call --type idl --output idl demo demo_candid_type_float64s '(0.1 : float64, -1.2 : float64)'
(0.1 : float64, -1.2 : float64)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_float64s received values '0.100000' & '-1.200000'
*/
#include "demo_candid_type_float64.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_float64() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
double in{0.0};
ic_api.from_wire(CandidTypeFloat64{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeFloat64{in});
}
void demo_candid_type_float64s() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
double in1{0.0};
double in2{0.0};
CandidArgs args_in;
args_in.append(CandidTypeFloat64(&in1));
args_in.append(CandidTypeFloat64(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeFloat64(in1));
args_out.append(CandidTypeFloat64(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_float64.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_float64()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_float64");
void demo_candid_type_float64s()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_float64s");
// file: src/demo.did
service : {
"demo_candid_type_float64" : (float64) -> (float64) query;
"demo_candid_type_float64s" : (float64, float64) -> (float64, float64) query;
};
CandidTypeInt8
A class to convert between Candid int8
and C++ int8_t
.
Declaration:
// candid.h
class CandidTypeInt8 {
public:
CandidTypeInt8(int8_t *v);
CandidTypeInt8(const int8_t v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_int8.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_int8 '(101 : int8)'
(101 : int8)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int8 received value '101'
$ dfx canister call --type idl --output idl demo demo_candid_type_int8s '(101 : int8, -102 : int8)'
(101 : int8, -102 : int8)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int8s received values '101' & '-102'
*/
#include "demo_candid_type_int8.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_int8() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
int8_t in{0};
ic_api.from_wire(CandidTypeInt8{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeInt8{in});
}
void demo_candid_type_int8s() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
int8_t in1{0};
int8_t in2{0};
CandidArgs args_in;
args_in.append(CandidTypeInt8(&in1));
args_in.append(CandidTypeInt8(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeInt8(in1));
args_out.append(CandidTypeInt8(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_int8.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_int8()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int8");
void demo_candid_type_int8s()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int8s");
// file: src/demo.did
service : {
"demo_candid_type_int8" : (int8) -> (int8) query;
"demo_candid_type_int8s" : (int8, int8) -> (int8, int8) query;
};
CandidTypeInt16
A class to convert between Candid int16
and C++ int16_t
.
Declaration:
// candid.h
class CandidTypeInt16 {
public:
CandidTypeInt16(int16_t *v);
CandidTypeInt16(const int16_t v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_int16.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_int16 '(101 : int16)'
(101 : int16)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int16 received value '101'
$ dfx canister call --type idl --output idl demo demo_candid_type_int16s '(101 : int16, -102 : int16)'
(101 : int16, -102 : int16)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int16s received values '101' & '-102'
*/
#include "demo_candid_type_int16.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_int16() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
int16_t in{0};
ic_api.from_wire(CandidTypeInt16{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeInt16{in});
}
void demo_candid_type_int16s() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
int16_t in1{0};
int16_t in2{0};
CandidArgs args_in;
args_in.append(CandidTypeInt16(&in1));
args_in.append(CandidTypeInt16(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeInt16(in1));
args_out.append(CandidTypeInt16(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_int16.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_int16()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int16");
void demo_candid_type_int16s()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int16s");
// file: src/demo.did
service : {
"demo_candid_type_int16" : (int16) -> (int16) query;
"demo_candid_type_int16s" : (int16, int16) -> (int16, int16) query;
};
CandidTypeInt32
A class to convert between Candid int32
and C++ int32_t
.
Declaration:
// candid.h
class CandidTypeInt32 {
public:
CandidTypeInt32(int32_t *v);
CandidTypeInt32(const int32_t v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_int32.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_int32 '(101 : int32)'
(101 : int32)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int32 received value '101'
$ dfx canister call --type idl --output idl demo demo_candid_type_int32s '(101 : int32, -102 : int32)'
(101 : int32, -102 : int32)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int32s received values '101' & '-102'
*/
#include "demo_candid_type_int32.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_int32() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
int32_t in{0};
ic_api.from_wire(CandidTypeInt32{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeInt32{in});
}
void demo_candid_type_int32s() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
int32_t in1{0};
int32_t in2{0};
CandidArgs args_in;
args_in.append(CandidTypeInt32(&in1));
args_in.append(CandidTypeInt32(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeInt32(in1));
args_out.append(CandidTypeInt32(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_int32.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_int32()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int32");
void demo_candid_type_int32s()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int32s");
// file: src/demo.did
service : {
"demo_candid_type_int32" : (int32) -> (int32) query;
"demo_candid_type_int32s" : (int32, int32) -> (int32, int32) query;
};
CandidTypeInt64
A class to convert between Candid int64
and C++ int64_t
.
Declaration:
// candid.h
class CandidTypeInt64 {
public:
CandidTypeInt64(int64_t *v);
CandidTypeInt64(const int64_t v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_int64.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_int64 '(101 : int64)'
(101 : int64)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int64 received value '101'
$ dfx canister call --type idl --output idl demo demo_candid_type_int64s '(101 : int64, -102 : int64)'
(101 : int64, -102 : int64)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int64s received values '101' & '-102'
*/
#include "demo_candid_type_int64.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_int64() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
int64_t in{0};
ic_api.from_wire(CandidTypeInt64{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeInt64{in});
}
void demo_candid_type_int64s() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
int64_t in1{0};
int64_t in2{0};
CandidArgs args_in;
args_in.append(CandidTypeInt64(&in1));
args_in.append(CandidTypeInt64(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeInt64(in1));
args_out.append(CandidTypeInt64(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_int64.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_int64()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int64");
void demo_candid_type_int64s()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int64s");
// file: src/demo.did
service : {
"demo_candid_type_int64" : (int64) -> (int64) query;
"demo_candid_type_int64s" : (int64, int64) -> (int64, int64) query;
};
CandidTypeInt
A class to convert between Candid int
and C++ __int128_t
.
Declaration:
// candid.h
class CandidTypeInt {
public:
CandidTypeInt(__int128_t *v);
CandidTypeInt(const __int128_t &v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_int.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_int '(101 : int)'
(101 : int)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_int received value '101'
$ dfx canister call --type idl --output idl demo demo_candid_type_ints '(101 : int, -102 : int)'
(101 : int, -102 : int)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_ints received values '101' & '-102'
*/
#include "demo_candid_type_int.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_int() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
__int128_t in{0};
ic_api.from_wire(CandidTypeInt{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
IC_API::to_string_128(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeInt{in});
}
void demo_candid_type_ints() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
__int128_t in1{0};
__int128_t in2{0};
CandidArgs args_in;
args_in.append(CandidTypeInt(&in1));
args_in.append(CandidTypeInt(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
IC_API::to_string_128(in1) + "' & '" +
IC_API::to_string_128(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeInt(in1));
args_out.append(CandidTypeInt(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_int.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_int()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_int");
void demo_candid_type_ints()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_ints");
// file: src/demo.did
service : {
"demo_candid_type_int" : (int) -> (int) query;
"demo_candid_type_ints" : (int, int) -> (int, int) query;
};
CandidTypeNat8
A class to convert between Candid int8
and C++ uint8_t
.
Declaration:
// candid.h
class CandidTypeNat8 {
public:
CandidTypeNat8(uint8_t *v);
CandidTypeNat8(const uint8_t v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_nat8.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_nat8 '(101 : nat8)'
(101 : nat8)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat8 received value '101'
$ dfx canister call --type idl --output idl demo demo_candid_type_nat8s '(101 : nat8, 102 : nat8)'
(101 : nat8, 102 : nat8)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat8s received values '101' & '102'
*/
#include "demo_candid_type_nat8.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_nat8() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
uint8_t in{0};
ic_api.from_wire(CandidTypeNat8{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeNat8{in});
}
void demo_candid_type_nat8s() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
uint8_t in1{0};
uint8_t in2{0};
CandidArgs args_in;
args_in.append(CandidTypeNat8(&in1));
args_in.append(CandidTypeNat8(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeNat8(in1));
args_out.append(CandidTypeNat8(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_nat8.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_nat8()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat8");
void demo_candid_type_nat8s()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat8s");
// file: src/demo.did
service : {
"demo_candid_type_nat8" : (nat8) -> (nat8) query;
"demo_candid_type_nat8s" : (nat8, nat8) -> (nat8, nat8) query;
};
CandidTypeNat16
A class to convert between Candid int16
and C++ uint16_t
.
Declaration:
// candid.h
class CandidTypeNat16 {
public:
CandidTypeNat16(uint16_t *v);
CandidTypeNat16(const uint16_t v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_nat16.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_nat16 '(101 : nat16)'
(101 : nat16)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat16 received value '101'
$ dfx canister call --type idl --output idl demo demo_candid_type_nat16s '(101 : nat16, 102 : nat16)'
(101 : nat16, 102 : nat16)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat16s received values '101' & '102'
*/
#include "demo_candid_type_nat16.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_nat16() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
uint16_t in{0};
ic_api.from_wire(CandidTypeNat16{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeNat16{in});
}
void demo_candid_type_nat16s() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
uint16_t in1{0};
uint16_t in2{0};
CandidArgs args_in;
args_in.append(CandidTypeNat16(&in1));
args_in.append(CandidTypeNat16(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeNat16(in1));
args_out.append(CandidTypeNat16(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_nat16.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_nat16()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat16");
void demo_candid_type_nat16s()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat16s");
// file: src/demo.did
service : {
"demo_candid_type_nat16" : (nat16) -> (nat16) query;
"demo_candid_type_nat16s" : (nat16, nat16) -> (nat16, nat16) query;
};
CandidTypeNat32
A class to convert between Candid int32
and C++ uint32_t
.
Declaration:
// candid.h
class CandidTypeNat32 {
public:
CandidTypeNat32(uint32_t *v);
CandidTypeNat32(const uint32_t v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_nat32.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_nat32 '(101 : nat32)'
(101 : nat32)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat32 received value '101'
$ dfx canister call --type idl --output idl demo demo_candid_type_nat32s '(101 : nat32, 102 : nat32)'
(101 : nat32, 102 : nat32)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat32s received values '101' & '102'
*/
#include "demo_candid_type_nat32.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_nat32() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
uint32_t in{0};
ic_api.from_wire(CandidTypeNat32{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeNat32{in});
}
void demo_candid_type_nat32s() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
uint32_t in1{0};
uint32_t in2{0};
CandidArgs args_in;
args_in.append(CandidTypeNat32(&in1));
args_in.append(CandidTypeNat32(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeNat32(in1));
args_out.append(CandidTypeNat32(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_nat32.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_nat32()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat32");
void demo_candid_type_nat32s()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat32s");
// file: src/demo.did
service : {
"demo_candid_type_nat32" : (nat32) -> (nat32) query;
"demo_candid_type_nat32s" : (nat32, nat32) -> (nat32, nat32) query;
};
CandidTypeNat64
A class to convert between Candid int64
and C++ int64_t
.
Declaration:
// candid.h
class CandidTypeNat64 {
public:
CandidTypeNat64(uint64_t *v);
CandidTypeNat64(const uint64_t v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_nat64.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_nat64 '(101 : nat64)'
(101 : nat64)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat64 received value '101'
$ dfx canister call --type idl --output idl demo demo_candid_type_nat64s '(101 : nat64, 102 : nat64)'
(101 : nat64, 102 : nat64)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat64s received values '101' & '102'
*/
#include "demo_candid_type_nat64.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_nat64() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
uint64_t in{0};
ic_api.from_wire(CandidTypeNat64{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
std::to_string(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeNat64{in});
}
void demo_candid_type_nat64s() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
uint64_t in1{0};
uint64_t in2{0};
CandidArgs args_in;
args_in.append(CandidTypeNat64(&in1));
args_in.append(CandidTypeNat64(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
std::to_string(in1) + "' & '" + std::to_string(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeNat64(in1));
args_out.append(CandidTypeNat64(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_nat64.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_nat64()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat64");
void demo_candid_type_nat64s()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat64s");
// file: src/demo.did
service : {
"demo_candid_type_nat64" : (nat64) -> (nat64) query;
"demo_candid_type_nat64s" : (nat64, nat64) -> (nat64, nat64) query;
};
CandidTypeNat
A class to convert between Candid int
and C++ __uint128_t
.
Declaration:
// candid.h
class CandidTypeNat {
public:
CandidTypeNat(__uint128_t *v);
CandidTypeNat(const __uint128_t v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_nat.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_nat '(101 : nat)'
(101 : nat)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nat received value '101'
$ dfx canister call --type idl --output idl demo demo_candid_type_nats '(101 : nat, 102 : nat)'
(101 : nat, 102 : nat)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_nats received values '101' & '102'
*/
#include "demo_candid_type_nat.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_nat() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
__uint128_t in{0};
ic_api.from_wire(CandidTypeNat{&in});
std::cout << "Method " + std::string(__func__) + " received value '" +
IC_API::to_string_128(in) + "'"
<< std::endl;
ic_api.to_wire(CandidTypeNat{in});
}
void demo_candid_type_nats() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
__uint128_t in1{0};
__uint128_t in2{0};
CandidArgs args_in;
args_in.append(CandidTypeNat(&in1));
args_in.append(CandidTypeNat(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" +
IC_API::to_string_128(in1) + "' & '" +
IC_API::to_string_128(in2) + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeNat(in1));
args_out.append(CandidTypeNat(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_nat.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_nat()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nat");
void demo_candid_type_nats()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_nats");
// file: src/demo.did
service : {
"demo_candid_type_nat" : (nat) -> (nat) query;
"demo_candid_type_nats" : (nat, nat) -> (nat, nat) query;
};
CandidTypeOpt
A group of classes to convert between Candid opt candid-type
and C++ std::optional<C++ type>
.
CandidTypeOpt | Candid | C++ | Example |
---|---|---|---|
CandidTypeOptBool | opt bool | std::optional<bool> | example |
not supported | opt empty | not supported | - |
CandidTypeOptFloat32 | opt float32 | std::optional<float> | example |
CandidTypeOptFloat64 | opt float64 | std::optional<double> | example |
CandidTypeOptInt8 | opt int8 | std::optional<int8_t> | example |
CandidTypeOptInt16 | opt int16 | std::optional<int16_t> | example |
CandidTypeOptInt32 | opt int32 | std::optional<int32_t> | example |
CandidTypeOptInt64 | opt int64 | std::optional<int64_t> | example |
CandidTypeOptInt | opt int | std::optional<__int128_t> | example |
CandidTypeOptNat8 | opt nat8 | std::optional<uint8_t> | example |
CandidTypeOptNat16 | opt nat16 | std::optional<uint16_t> | example |
CandidTypeOptNat32 | opt nat32 | std::optional<uint32_t> | example |
CandidTypeOptNat64 | opt nat64 | std::optional<uint64_t> | example |
CandidTypeOptNat | opt nat | std::optional<__uint128_t> | example |
not supported | opt null | not supported | - |
CandidTypeOptPrincipal | opt principal | std::optional<std::string> | example |
CandidTypeOptText | opt text | std::optional<std::string> | example |
Declaration:
// candid.h
class CandidTypeOptBool {
public:
CandidTypeOptBool(std::optional<bool> *v);
CandidTypeOptBool(const std::optional<bool> v);
}
class CandidTypeOptFloat32 {
public:
CandidTypeOptFloat32(std::optional<float> *v);
CandidTypeOptFloat32(const std::optional<float> v);
}
class CandidTypeOptFloat64 {
public:
CandidTypeOptFloat64(std::optional<double> *v);
CandidTypeOptFloat64(const std::optional<double> v);
}
class CandidTypeOptInt8 {
public:
CandidTypeOptInt8(std::optional<int8_t> *v);
CandidTypeOptInt8(const std::optional<int8_t> v);
}
class CandidTypeOptInt16 {
public:
CandidTypeOptInt16(std::optional<int16_t> *v);
CandidTypeOptInt16(const std::optional<int16_t> v);
}
class CandidTypeOptInt32 {
public:
CandidTypeOptInt32(std::optional<int32_t> *p_v);
CandidTypeOptInt32(const std::optional<int32_t> v);
}
class CandidTypeOptInt64 {
public:
CandidTypeOptInt64(std::optional<int64_t> *v);
CandidTypeOptInt64(const std::optional<int64_t> v);
}
class CandidTypeOptInt {
public:
CandidTypeOptInt(std::optional<__int128_t> *v);
CandidTypeOptInt(const std::optional<__int128_t> v);
}
class CandidTypeOptNat8 {
public:
CandidTypeOptNat8(std::optional<uint8_t> *v);
CandidTypeOptNat8(const std::optional<uint8_t> v);
}
class CandidTypeOptNat16 {
public:
CandidTypeOptNat16(std::optional<uint16_t> *v);
CandidTypeOptNat16(const std::optional<uint16_t> v);
}
class CandidTypeOptNat32 {
public:
CandidTypeOptNat32(std::optional<uint32_t> *v);
CandidTypeOptNat32(const std::optional<uint32_t> v);
}
class CandidTypeOptNat64 {
public:
CandidTypeOptNat64(std::optional<uint64_t> *v);
CandidTypeOptNat64(const std::optional<uint64_t> v);
}
class CandidTypeOptNat {
public:
CandidTypeOptNat(std::optional<__uint128_t> *v);
CandidTypeOptNat(const std::optional<__uint128_t> v);
}
class CandidTypeOptPrincipal {
public:
CandidTypeOptPrincipal(std::optional<std::string> *p_v);
CandidTypeOptPrincipal(const std::optional<std::string> v);
}
class CandidTypeOptText {
public:
CandidTypeOptText(std::optional<std::string> *v);
CandidTypeOptText(const std::optional<std::string> v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_opt.cpp
// ----------------------------------------------------------
// The (opt bool) has three possible values:
// - true
// - false
// - no value
// Let's try them all out:
$ dfx canister call --type idl --output idl demo demo_candid_type_opt '(opt (true : bool))'
(opt true)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opt received bool value '1'
$ dfx canister call --type idl --output idl demo demo_candid_type_opt '(opt (false : bool))'
(opt false)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opt received bool value '0'
$ dfx canister call --type idl --output idl demo demo_candid_type_opt '(null)'
(null)
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opt did not receive a bool value
// ----------------------------------------------------------
// The other method has as argument a list of all opt types, eg.
$ dfx canister call --type idl --output idl demo demo_candid_type_opts '(opt (true : bool), opt (0.1 : float32), opt (0.2 : float64), opt (-8 : int8), opt (-16 : int16), opt (-32 : int32), opt (-64 : int64), opt (-128 : int), opt (8 : nat8), opt (16 : nat16), opt (32 : nat32), opt (64 : nat64), opt (128 : nat), opt (principal "2ibo7-dia"), opt ("demo" : text))'
(opt true, opt (0.1 : float32), opt (0.2 : float64), opt (-8 : int8), opt (-16 : int16), opt (-32 : int32), opt (-64 : int64), opt (-128 : int), opt (8 : nat8), opt (16 : nat16), opt (32 : nat32), opt (64 : nat64), opt (128 : nat), opt principal "2ibo7-dia", opt "demo")
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received bool value '1'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received float32 value '0.100000'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received float64 value '0.200000'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received int8 value '-8'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received int16 value '-16'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received int32 value '-32'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received int64 value '-64'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received int value '-128'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received nat8 value '8'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received nat16 value '16'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received nat32 value '32'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received nat64 value '64'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received nat value '128'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received principal value '2ibo7-dia'
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_opts received text value 'demo'
*/
#include "demo_candid_type_opt.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_opt() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
std::optional<bool> in;
ic_api.from_wire(CandidTypeOptBool{&in});
if (in.has_value()) {
std::cout << "Method " + std::string(__func__) + " received bool value '" +
std::to_string(in.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a bool value"
<< std::endl;
}
ic_api.to_wire(CandidTypeOptBool{in});
}
void demo_candid_type_opts() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
// ---------------------------------------------------------------------------
// Get the data from the wire
std::optional<bool> in_bool;
std::optional<float> in_float32;
std::optional<double> in_float64;
std::optional<int8_t> in_int8;
std::optional<int16_t> in_int16;
std::optional<int32_t> in_int32;
std::optional<int64_t> in_int64;
std::optional<__int128_t> in_int;
std::optional<uint8_t> in_nat8;
std::optional<uint16_t> in_nat16;
std::optional<uint32_t> in_nat32;
std::optional<uint64_t> in_nat64;
std::optional<__uint128_t> in_nat;
std::optional<std::string> in_principal;
std::optional<std::string> in_text;
CandidArgs args_in;
args_in.append(CandidTypeOptBool{&in_bool});
args_in.append(CandidTypeOptFloat32{&in_float32});
args_in.append(CandidTypeOptFloat64{&in_float64});
args_in.append(CandidTypeOptInt8{&in_int8});
args_in.append(CandidTypeOptInt16{&in_int16});
args_in.append(CandidTypeOptInt32{&in_int32});
args_in.append(CandidTypeOptInt64{&in_int64});
args_in.append(CandidTypeOptInt{&in_int});
args_in.append(CandidTypeOptNat8{&in_nat8});
args_in.append(CandidTypeOptNat16{&in_nat16});
args_in.append(CandidTypeOptNat32{&in_nat32});
args_in.append(CandidTypeOptNat64{&in_nat64});
args_in.append(CandidTypeOptNat{&in_nat});
args_in.append(CandidTypeOptPrincipal{&in_principal});
args_in.append(CandidTypeOptText{&in_text});
ic_api.from_wire(args_in);
// ---------------------------------------------------------------------------
// print the data
if (in_bool.has_value()) {
std::cout << "Method " + std::string(__func__) + " received bool value '" +
std::to_string(in_bool.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a bool value"
<< std::endl;
}
if (in_float32.has_value()) {
std::cout << "Method " + std::string(__func__) +
" received float32 value '" +
std::to_string(in_float32.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a float32 value"
<< std::endl;
}
if (in_float64.has_value()) {
std::cout << "Method " + std::string(__func__) +
" received float64 value '" +
std::to_string(in_float64.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a float64 value"
<< std::endl;
}
if (in_int8.has_value()) {
std::cout << "Method " + std::string(__func__) + " received int8 value '" +
std::to_string(in_int8.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a int8 value"
<< std::endl;
}
if (in_int16.has_value()) {
std::cout << "Method " + std::string(__func__) + " received int16 value '" +
std::to_string(in_int16.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a int16 value"
<< std::endl;
}
if (in_int32.has_value()) {
std::cout << "Method " + std::string(__func__) + " received int32 value '" +
std::to_string(in_int32.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a int32 value"
<< std::endl;
}
if (in_int64.has_value()) {
std::cout << "Method " + std::string(__func__) + " received int64 value '" +
std::to_string(in_int64.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a int64 value"
<< std::endl;
}
if (in_int.has_value()) {
std::cout << "Method " + std::string(__func__) + " received int value '" +
IC_API::to_string_128(in_int.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a int value"
<< std::endl;
}
if (in_nat8.has_value()) {
std::cout << "Method " + std::string(__func__) + " received nat8 value '" +
std::to_string(in_nat8.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a nat8 value"
<< std::endl;
}
if (in_nat16.has_value()) {
std::cout << "Method " + std::string(__func__) + " received nat16 value '" +
std::to_string(in_nat16.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a nat16 value"
<< std::endl;
}
if (in_nat32.has_value()) {
std::cout << "Method " + std::string(__func__) + " received nat32 value '" +
std::to_string(in_nat32.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a nat32 value"
<< std::endl;
}
if (in_nat64.has_value()) {
std::cout << "Method " + std::string(__func__) + " received nat64 value '" +
std::to_string(in_nat64.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a nat64 value"
<< std::endl;
}
if (in_nat.has_value()) {
std::cout << "Method " + std::string(__func__) + " received nat value '" +
IC_API::to_string_128(in_nat.value()) + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a nat value"
<< std::endl;
}
if (in_principal.has_value()) {
std::cout << "Method " + std::string(__func__) +
" received principal value '" + in_principal.value() + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a principal value"
<< std::endl;
}
if (in_text.has_value()) {
std::cout << "Method " + std::string(__func__) + " received text value '" +
in_text.value() + "'"
<< std::endl;
} else {
std::cout << "Method " + std::string(__func__) +
" did not receive a text value"
<< std::endl;
}
// ---------------------------------------------------------------------------
// Return the data
CandidArgs args_out;
args_out.append(CandidTypeOptBool{in_bool});
args_out.append(CandidTypeOptFloat32{in_float32});
args_out.append(CandidTypeOptFloat64{in_float64});
args_out.append(CandidTypeOptInt8{in_int8});
args_out.append(CandidTypeOptInt16{in_int16});
args_out.append(CandidTypeOptInt32{in_int32});
args_out.append(CandidTypeOptInt64{in_int64});
args_out.append(CandidTypeOptInt{in_int});
args_out.append(CandidTypeOptNat8{in_nat8});
args_out.append(CandidTypeOptNat16{in_nat16});
args_out.append(CandidTypeOptNat32{in_nat32});
args_out.append(CandidTypeOptNat64{in_nat64});
args_out.append(CandidTypeOptNat{in_nat});
args_out.append(CandidTypeOptPrincipal{in_principal});
args_out.append(CandidTypeOptText{in_text});
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_opt.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_opt()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_opt");
void demo_candid_type_opts()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_opts");
// file: src/demo.did
service : {
"demo_candid_type_opt" : (opt bool) -> (opt bool) query;
"demo_candid_type_opts" : (opt bool, opt float32, opt float64, opt int8, opt int16, opt int32, opt int64, opt int, opt nat8, opt nat16, opt nat32, opt nat64, opt nat, opt principal, opt text) ->
(opt bool, opt float32, opt float64, opt int8, opt int16, opt int32, opt int64, opt int, opt nat8, opt nat16, opt nat32, opt nat64, opt nat, opt principal, opt text)
query;
};
CandidTypePrincipal
A class to convert between Candid principal
and C++ std::string
.
The value of the string follows the ID Encoding Specification.
Declaration:
// candid.h
class CandidTypePrincipal {
public:
CandidTypePrincipal(const std::string v);
// Checks if the user is anonymous
// If the user is not anonymous, that means that the user is authenticated
// with Internet Identity or other mechanisms that assign a unique
// principal ID.
bool is_anonymous();
// Get the principal ID in text format
std::string get_text();
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_principal.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_principal '(principal "2ibo7-dia")'
(principal "2ibo7-dia")
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_principal received value '2ibo7-dia'
$ dfx canister call --type idl --output idl demo demo_candid_type_principals '(principal "2ibo7-dia", principal "w3gef-eqbai")'
(principal "2ibo7-dia", principal "w3gef-eqbai")
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_principals received values '2ibo7-dia' & 'w3gef-eqbai'
*/
#include "demo_candid_type_principal.h"
#include <iostream>
#include <string>
#include "ic_api.h"
// Whitelisted principals, saved in Orthogonal Persisted Memory
std::vector<std::string> whitelisted_principals = {
"expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae", // User
"iineg-fibai-bqibi-ga4ea-searc-ijrif-iwc4m-bsibb-eirsi-jjge4-ucs", // User
"6uwoh-vaaaa-aaaag-amema-cai", // Another canister
};
void demo_candid_type_principal() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
// This demo shows how to implement whitelisting of users.
// 1. Get the caller's authenticated principal
// - The calling message was signed using the caller's private key
// - The IC verified the signature using the corresponding public key
CandidTypePrincipal caller = ic_api.get_caller();
// 2. Check if the user is anonymous
// If the user is not anonymous, that means that the user is authenticated
// with Internet Identity or other mechanisms that assign a unique
// principal ID.
if (caller.is_anonymous()) {
std::cout << "User is not logged in " << std::endl;
} else {
std::cout << "User is logged in " << std::endl;
}
// 3. Get the caller's principal id in text format
std::string principal_id_caller = caller.get_text();
std::cout << "User's principal id is " << principal_id_caller << std::endl;
// 4. Check if the caller's principal is whitelisted using std::find
auto it = std::find(whitelisted_principals.begin(),
whitelisted_principals.end(), principal_id_caller);
if (it != whitelisted_principals.end()) {
std::cout << "User is whitelisted." << std::endl;
} else {
std::cout << "User is not whitelisted." << std::endl;
}
// This call expects as argument a string in the format of a Candid principal
// Note that this is NOT the caller's principal ID, it is just an argument.
// For example, if the caller wants to send some ICP to another principal,
// the caller's principal id is authenticated as above, while the receiving
// principal's id is passed in as argument:
// - Value follows the rules for text representation of a Candid principal
// - It's just a string, nothing more. Not authenticated by IC using keys
std::string principal_id_from_arg{""};
ic_api.from_wire(CandidTypePrincipal{&principal_id_from_arg});
std::cout << "Principal id passed by argument is " + principal_id_from_arg
<< std::endl;
// For QA purposes, send the principal id back to the wire
ic_api.to_wire(CandidTypePrincipal{principal_id_from_arg});
}
void demo_candid_type_principals() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
std::string in1{""};
std::string in2{""};
CandidArgs args_in;
args_in.append(CandidTypePrincipal(&in1));
args_in.append(CandidTypePrincipal(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" + in1 +
"' & '" + in2 + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypePrincipal(in1));
args_out.append(CandidTypePrincipal(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_principal.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_principal()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_principal");
void demo_candid_type_principals()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_principals");
// file: src/demo.did
service : {
"demo_candid_type_principal" : (principal) -> (principal) query;
"demo_candid_type_principals" : (principal, principal) -> (principal, principal) query;
};
CandidTypeRecord
A class to convert between Candid record
and C++.
A CandidTypeRecord is a like a dictionary, constructed from multiple Candid types.
Declaration:
// candid.h
class CandidTypeRecord {
public:
CandidTypeRecord();
void append(uint32_t field_id, CandidType field);
void append(std::string field_name, CandidType field);
void append(CandidType field);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_record.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_record '(record {"field 1" = true : bool; "field 2" = 0.1 : float32; "field 3" = 0.2 : float64; "field 4" = -8 : int8; "field 5" = -16 : int16; "field 6" = -32 : int32; "field 7" = -64 : int64; "field 8" = -128 : int; "field 9" = 8 : nat8; "field 10" = 16 : nat16; "field A" = 32 : nat32; "field B" = 64 : nat64; "field C" = 128 : nat; "field D" = principal "2ibo7-dia"; "field E" = "demo" : text;})'
(record { field 10 = 16 : nat16; field 1 = true; field 2 = 0.1 : float32; field 3 = 0.2 : float64; field 4 = -8 : int8; field 5 = -16 : int16; field 6 = -32 : int32; field 7 = -64 : int64; field 8 = -128 : int; field 9 = 8 : nat8; field A = 32 : nat32; field B = 64 : nat64; field C = 128 : nat; field D = principal "2ibo7-dia"; field E = "demo";})
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_record
received a record with fields:
field 1 - bool value '1
field 2 - float value '0.100000
field 3 - double value '0.200000
field 4 - int8_t value '-8
field 5 - int16_t value '-16
field 6 - int32_t value '-32
field 7 - int64_t value '-64
field 8 - __int128_t value '-128
field 9 - uint8_t value '8
field 10 - uint16_t value '16
field A - uint32_t value '32
field B - uint64_t value '64
field C - __uint128_t value '128
field D - std::string value '2ibo7-dia
field E - std::string value 'demo'
$ dfx canister call --type idl --output idl demo demo_candid_type_records '(record {"field 1A" = -8 : int8;}, record {"field 2A" = -16 : int16;})'
(record { field 1A = -8 : int8;}, record { field 2A = -16 : int16;})
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_records
received a record with field:
field 1A - int8_t value '-8
and a record with field:
field 1B - int16_t value '-16'
*/
#include "demo_candid_type_record.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_record() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
// ---------------------------------------------------------------------------
// Get the data from the wire
bool f1{false};
float f2{0.0};
double f3{0.0};
int8_t f4{0};
int16_t f5{0};
int32_t f6{0};
int64_t f7{0};
__int128_t f8{0};
uint8_t f9{0};
uint16_t f10{0};
uint32_t fa{0};
uint64_t fb{0};
__uint128_t fc{0};
std::string fd{""};
std::string fe{""};
CandidTypeRecord r_in;
r_in.append("field 1", CandidTypeBool{&f1});
r_in.append("field 2", CandidTypeFloat32{&f2});
r_in.append("field 3", CandidTypeFloat64{&f3});
r_in.append("field 4", CandidTypeInt8{&f4});
r_in.append("field 5", CandidTypeInt16{&f5});
r_in.append("field 6", CandidTypeInt32{&f6});
r_in.append("field 7", CandidTypeInt64{&f7});
r_in.append("field 8", CandidTypeInt{&f8});
r_in.append("field 9", CandidTypeNat8{&f9});
r_in.append("field 10", CandidTypeNat16{&f10});
r_in.append("field A", CandidTypeNat32{&fa});
r_in.append("field B", CandidTypeNat64{&fb});
r_in.append("field C", CandidTypeNat{&fc});
r_in.append("field D", CandidTypePrincipal{&fd});
r_in.append("field E", CandidTypeText{&fe});
ic_api.from_wire(r_in);
// ---------------------------------------------------------------------------
std::cout << "Method " + std::string(__func__) +
"\n received a record with fields: \n" +
"\n field 1 - bool value '" + std::to_string(f1) +
"\n field 2 - float value '" + std::to_string(f2) +
"\n field 3 - double value '" + std::to_string(f3) +
"\n field 4 - int8_t value '" + std::to_string(f4) +
"\n field 5 - int16_t value '" + std::to_string(f5) +
"\n field 6 - int32_t value '" + std::to_string(f6) +
"\n field 7 - int64_t value '" + std::to_string(f7) +
"\n field 8 - __int128_t value '" +
IC_API::to_string_128(f8) +
"\n field 9 - uint8_t value '" + std::to_string(f9) +
"\n field 10 - uint16_t value '" + std::to_string(f10) +
"\n field A - uint32_t value '" + std::to_string(fa) +
"\n field B - uint64_t value '" + std::to_string(fb) +
"\n field C - __uint128_t value '" +
IC_API::to_string_128(fc) +
"\n field D - std::string value '" + fd +
"\n field E - std::string value '" + fe + "'"
<< std::endl;
// ---------------------------------------------------------------------------
CandidTypeRecord r_out;
r_out.append("field 1", CandidTypeBool{f1});
r_out.append("field 2", CandidTypeFloat32{f2});
r_out.append("field 3", CandidTypeFloat64{f3});
r_out.append("field 4", CandidTypeInt8{f4});
r_out.append("field 5", CandidTypeInt16{f5});
r_out.append("field 6", CandidTypeInt32{f6});
r_out.append("field 7", CandidTypeInt64{f7});
r_out.append("field 8", CandidTypeInt{f8});
r_out.append("field 9", CandidTypeNat8{f9});
r_out.append("field 10", CandidTypeNat16{f10});
r_out.append("field A", CandidTypeNat32{fa});
r_out.append("field B", CandidTypeNat64{fb});
r_out.append("field C", CandidTypeNat{fc});
r_out.append("field D", CandidTypePrincipal{fd});
r_out.append("field E", CandidTypeText{fe});
ic_api.to_wire(r_out);
}
void demo_candid_type_records() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
// ---------------------------------------------------------------------------
// Get the data from the wire
int8_t f1a{0};
CandidTypeRecord r1_in;
r1_in.append("field 1A", CandidTypeInt8{&f1a});
int16_t f2a{0};
CandidTypeRecord r2_in;
r2_in.append("field 2A", CandidTypeInt16{&f2a});
CandidArgs args_in;
args_in.append(r1_in);
args_in.append(r2_in);
ic_api.from_wire(args_in);
// ---------------------------------------------------------------------------
std::cout << "Method " + std::string(__func__) +
"\n received a record with field:" +
"\n field 1A - int8_t value '" + std::to_string(f1a) +
"\n and a record with field:" +
"\n field 2A - int16_t value '" + std::to_string(f2a) +
"'"
<< std::endl;
// ---------------------------------------------------------------------------
// Return the data
CandidTypeRecord r1_out;
r1_out.append("field 1A", CandidTypeInt8{f1a});
CandidTypeRecord r2_out;
r2_out.append("field 2A", CandidTypeInt16{f2a});
CandidArgs args_out;
args_out.append(r1_out);
args_out.append(r2_out);
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_record.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_record()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_record");
void demo_candid_type_records()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_records");
// file: src/demo.did
service : {
"demo_candid_type_record" : (record {"field 1" : bool; "field 2" : float32; "field 3" : float64; "field 4" : int8; "field 5" : int16; "field 6" : int32; "field 7": int64; "field 8" : int; "field 9" : nat8; "field 10" : nat16; "field A" : nat32; "field B" : nat64; "field C" : nat; "field D" : principal; "field E" : text;}) ->
(record {"field 1" : bool; "field 2" : float32; "field 3" : float64; "field 4" : int8; "field 5" : int16; "field 6" : int32; "field 7": int64; "field 8" : int; "field 9" : nat8; "field 10" : nat16; "field A" : nat32; "field B" : nat64; "field C" : nat; "field D" : principal; "field E" : text;})
query;
"demo_candid_type_records" : (record {"field 1A" : int8;}, record {"field 2A" : int16;}) ->
(record {"field 1A" : int8;}, record {"field 2A" : int16;})
query;
};
CandidTypeText
A class to convert between Candid text
and C++ std::string
.
Declaration:
// candid.h
class CandidTypeText {
public:
CandidTypeText(std::string *v);
CandidTypeText(const std::string v);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_text.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_text '("demo A" : text)'
("demo A")
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_text received value 'demo A'
$ dfx canister call --type idl --output idl demo demo_candid_type_texts '("demo A" : text, "demo B" : text)'
("demo A", "demo B")
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_texts received values 'demo A' & 'demo B'
*/
#include "demo_candid_type_text.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_text() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
std::string in{""};
ic_api.from_wire(CandidTypeText{&in});
std::cout << "Method " + std::string(__func__) + " received value '" + in +
"'"
<< std::endl;
ic_api.to_wire(CandidTypeText{in});
}
void demo_candid_type_texts() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
std::string in1{""};
std::string in2{""};
CandidArgs args_in;
args_in.append(CandidTypeText(&in1));
args_in.append(CandidTypeText(&in2));
ic_api.from_wire(args_in);
std::cout << "Method " + std::string(__func__) + " received values '" + in1 +
"' & '" + in2 + "'"
<< std::endl;
CandidArgs args_out;
args_out.append(CandidTypeText(in1));
args_out.append(CandidTypeText(in2));
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_text.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_text()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_text");
void demo_candid_type_texts()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_texts");
// file: src/demo.did
service : {
"demo_candid_type_text" : (text) -> (text) query;
"demo_candid_type_texts" : (text, text) -> (text, text) query;
};
CandidTypeVariant
A class to convert between Candid variant
and C++.
A CandidTypeVariant is similar to the CandidTypeRecord, but it can only store one 'label: value' out of multiple allowed variants.
Declaration:
// candid.h
class CandidTypeVariant {
public:
CandidTypeVariant();
CandidTypeVariant(std::string *p_label);
CandidTypeVariant(const std::string label);
CandidTypeVariant(const std::string label, const CandidType field);
}
/* file: https://github.com/icppWorld/icpp-demos/tree/main/canisters/api_reference/src/demo_candid_type_variant.cpp
$ dfx canister call --type idl --output idl demo demo_candid_type_variant '(variant {"field 2" = 0.1 : float32;})'
(variant { field 2 = 0.1 : float32;})
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_variant
received a variant with label: field 2
field 2 - float value '0.100000
$ dfx canister call --type idl --output idl demo demo_candid_type_variants '(variant {"field 1" = true : bool;}, variant {"field 2" = 0.1 : float32;})'
(variant {"field 1" = true : bool;}, variant {"field 2" = 0.1 : float32;})
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] Method demo_candid_type_variants
received a variant with label: field 2
field 2 - float value '0.100000
received another variant with label: field 2
field 2 - float value '0.100000
*/
#include "demo_candid_type_variant.h"
#include <iostream>
#include <string>
#include "ic_api.h"
void demo_candid_type_variant() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
// ---------------------------------------------------------------------------
// Get the data from the wire
bool f1{false};
float f2{0.0};
double f3{0.0};
int8_t f4{0};
int16_t f5{0};
int32_t f6{0};
int64_t f7{0};
__int128_t f8{0};
uint8_t f9{0};
uint16_t f10{0};
uint32_t fa{0};
uint64_t fb{0};
__uint128_t fc{0};
std::string fd{""};
std::string fe{""};
std::string label{""};
CandidTypeVariant v_in(&label);
v_in.append("field 1", CandidTypeBool{&f1});
v_in.append("field 2", CandidTypeFloat32{&f2});
v_in.append("field 3", CandidTypeFloat64{&f3});
v_in.append("field 4", CandidTypeInt8{&f4});
v_in.append("field 5", CandidTypeInt16{&f5});
v_in.append("field 6", CandidTypeInt32{&f6});
v_in.append("field 7", CandidTypeInt64{&f7});
v_in.append("field 8", CandidTypeInt{&f8});
v_in.append("field 9", CandidTypeNat8{&f9});
v_in.append("field 10", CandidTypeNat16{&f10});
v_in.append("field A", CandidTypeNat32{&fa});
v_in.append("field B", CandidTypeNat64{&fb});
v_in.append("field C", CandidTypeNat{&fc});
v_in.append("field D", CandidTypePrincipal{&fd});
v_in.append("field E", CandidTypeText{&fe});
ic_api.from_wire(v_in);
// ---------------------------------------------------------------------------
// Debug print of the values
std::string msg;
msg.append("Method " + std::string(__func__) +
"\n received a variant with label: " + label + "\n");
if (label == "field 1") {
msg.append("\n field 1 - bool value '" + std::to_string(f1));
} else if (label == "field 2") {
msg.append("\n field 2 - float value '" + std::to_string(f2));
} else if (label == "field 3") {
msg.append("\n field 3 - double value '" + std::to_string(f3));
} else if (label == "field 4") {
msg.append("\n field 4 - int8_t value '" + std::to_string(f4));
} else if (label == "field 5") {
msg.append("\n field 5 - int16_t value '" + std::to_string(f5));
} else if (label == "field 6") {
msg.append("\n field 6 - int32_t value '" + std::to_string(f6));
} else if (label == "field 7") {
msg.append("\n field 7 - int64_t value '" + std::to_string(f7));
} else if (label == "field 8") {
msg.append("\n field 8 - __int128_t value '" + IC_API::to_string_128(f8));
} else if (label == "field 9") {
msg.append("\n field 9 - uint8_t value '" + std::to_string(f9));
} else if (label == "field 10") {
msg.append("\n field 10 - uint16_t value '" + std::to_string(f10));
} else if (label == "field A") {
msg.append("\n field A - uint32_t value '" + std::to_string(fa));
} else if (label == "field B") {
msg.append("\n field B - uint64_t value '" + std::to_string(fb));
} else if (label == "field C") {
msg.append("\n field C - __uint128_t value '" + IC_API::to_string_128(fc));
} else if (label == "field D") {
msg.append("\n field D - std::string value '" + fd);
} else if (label == "field E") {
msg.append("\n field E - std::string value '" + fe);
}
std::cout << msg << std::endl;
// ---------------------------------------------------------------------------
// Return the data
CandidTypeVariant v_out{label};
if (label == "field 1") {
v_out.append("field 1", CandidTypeBool{f1});
} else if (label == "field 2") {
v_out.append("field 2", CandidTypeFloat32{f2});
} else if (label == "field 3") {
v_out.append("field 3", CandidTypeFloat64{f3});
} else if (label == "field 4") {
v_out.append("field 4", CandidTypeInt8{f4});
} else if (label == "field 5") {
v_out.append("field 5", CandidTypeInt16{f5});
} else if (label == "field 6") {
v_out.append("field 6", CandidTypeInt32{f6});
} else if (label == "field 7") {
v_out.append("field 7", CandidTypeInt64{f7});
} else if (label == "field 8") {
v_out.append("field 8", CandidTypeInt{f8});
} else if (label == "field 9") {
v_out.append("field 9", CandidTypeNat8{f9});
} else if (label == "field 10") {
v_out.append("field 10", CandidTypeNat16{f10});
} else if (label == "field A") {
v_out.append("field A", CandidTypeNat32{fa});
} else if (label == "field B") {
v_out.append("field B", CandidTypeNat64{fb});
} else if (label == "field C") {
v_out.append("field C", CandidTypeNat{fc});
} else if (label == "field D") {
v_out.append("field D", CandidTypePrincipal{fd});
} else if (label == "field E") {
v_out.append("field E", CandidTypeText{fe});
}
ic_api.to_wire(v_out);
}
void demo_candid_type_variants() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
// ---------------------------------------------------------------------------
// Get the data from the wire for two variants
// variant a
bool f1a{false};
float f2a{0.0};
std::string labela{""};
CandidTypeVariant va_in(&labela);
va_in.append("field 1", CandidTypeBool{&f1a});
va_in.append("field 2", CandidTypeFloat32{&f2a});
// variant b
bool f1b{false};
float f2b{0.0};
std::string labelb{""};
CandidTypeVariant vb_in(&labelb);
vb_in.append("field 1", CandidTypeBool{&f1b});
vb_in.append("field 2", CandidTypeFloat32{&f2b});
CandidArgs args_in;
args_in.append(va_in);
args_in.append(vb_in);
ic_api.from_wire(args_in);
// ---------------------------------------------------------------------------
// Debug print of the values
// variant a
std::string msg;
msg.append("Method " + std::string(__func__) +
"\n received a variant with label: " + labela + "\n");
if (labela == "field 1") {
msg.append("\n field 1 - bool value '" + std::to_string(f1a));
} else if (labela == "field 2") {
msg.append("\n field 2 - float value '" + std::to_string(f2a));
}
// variant b
msg.append("\n\n received another variant with label: " + labelb + "\n");
if (labelb == "field 1") {
msg.append("\n field 1 - bool value '" + std::to_string(f1b));
} else if (labelb == "field 2") {
msg.append("\n field 2 - float value '" + std::to_string(f2b));
}
std::cout << msg << std::endl;
// ---------------------------------------------------------------------------
// Return the data
// variant a
CandidTypeVariant va_out{labela};
if (labela == "field 1") {
va_out.append("field 1", CandidTypeBool{f1a});
} else if (labela == "field 2") {
va_out.append("field 2", CandidTypeFloat32{f2a});
}
// variant b
CandidTypeVariant vb_out{labelb};
if (labelb == "field 1") {
vb_out.append("field 1", CandidTypeBool{f1b});
} else if (labelb == "field 2") {
vb_out.append("field 2", CandidTypeFloat32{f2b});
}
CandidArgs args_out;
args_out.append(va_out);
args_out.append(vb_out);
ic_api.to_wire(args_out);
}
// file: src/demo_candid_type_variant.h
#pragma once
#include "wasm_symbol.h"
void demo_candid_type_variant()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_variant");
void demo_candid_type_variants()
WASM_SYMBOL_EXPORTED("canister_query demo_candid_type_variants");
// file: src/demo.did
service : {
"demo_candid_type_variant" : (variant {"field 1" : bool; "field 2" : float32; "field 3" : float64; "field 4" : int8; "field 5" : int16; "field 6" : int32; "field 7": int64; "field 8" : int; "field 9" : nat8; "field 10" : nat16; "field A" : nat32; "field B" : nat64; "field C" : nat; "field D" : principal; "field E" : text;}) ->
(variant {"field 1" : bool; "field 2" : float32; "field 3" : float64; "field 4" : int8; "field 5" : int16; "field 6" : int32; "field 7": int64; "field 8" : int; "field 9" : nat8; "field 10" : nat16; "field A" : nat32; "field B" : nat64; "field C" : nat; "field D" : principal; "field E" : text;})
query;
"demo_candid_type_variants" : (variant {"field 1" : bool; "field 2" : float32;}, variant {"field 1" : bool; "field 2" : float32;}) ->
(variant {"field 1" : bool; "field 2" : float32;}, variant {"field 1" : bool; "field 2" : float32;})
query;
};
CandidTypeVec
A group of classes to convert between Candid vec candid-type
and C++ std::vector<C++ type>
.
CandidTypeVec | Candid | C++ | Example |
---|---|---|---|
CandidTypeVecBool | vec bool | std::vector<bool> | |
not supported | vec empty | not supported | |
CandidTypeVecFloat32 | vec float32 | std::vector<float> | |
CandidTypeVecFloat64 | vec float64 | std::vector<double> | |
CandidTypeVecInt8 | vec int8 | std::vector<int8_t> | |
CandidTypeVecInt16 | vec int16 | std::vector<int16_t> | |
CandidTypeVecInt32 | vec int32 | std::vector<int32_t> | |
CandidTypeVecInt64 | vec int64 | std::vector<int64_t> | |
CandidTypeVecInt | vec int | std::vector<__int128_t> | |
CandidTypeVecNat8 | vec nat8 | std::vector<uint8_t> | |
CandidTypeVecNat16 | vec nat16 | std::vector<uint16_t> | |
CandidTypeVecNat32 | vec nat32 | std::vector<uint32_t> | |
CandidTypeVecNat64 | vec nat64 | std::vector<uint64_t> | |
CandidTypeVecNat | vec nat | std::vector<__uint128_t> | |
not supported | vec null | not supported | |
CandidTypeVecPrincipal | vec principal | std::vector<std::string> | |
CandidTypeVecText | vec text | std::vector<std::string> |
🚧 This documentation section is currently under construction! 🚧
IC_API
When developing a C++ Smart Contract for the Internet Computer, you engage with the canister system API using the IC_API. Among other things, it facilitates:
- Verifying the identity of the caller
- Acquiring incoming data from the wire
- Dispatching data back to the wire
- Printing to the console, when deployed to the local network
- Getting the Internet Computer system time
- Trapping a canister to stop execution and return an error
The message data is organized in the Candid format, and the IC_API seamlessly aligns this with standard C++ data structures.
ic0 system interface
The methods detailed in this section encapsulate the functionality of the ic0 system interface.
As a C++ developer, you can interact with the Internet Computer Interface seamlessly, without delving into its intricacies, thanks to the abstraction provided by icpp-pro's C++ interface methods.
debug_print
Declaration:
// ic_api.h
class IC_API {
public:
static void debug_print(const char *msg);
static void debug_print(const std::string &msg);
}
The debug_print
method is a utility function used to print debug messages during the local execution of your application. This is helpful for tracing and debugging your application's flow. The debug_print
method accepts a C-style string (const char *
) or a C++ string (const std::string &
).
Note that the debug_print
messages only appear during local development and testing. In a production environment on the IC mainnet, these messages are disregarded.
/* file: src/demo_debug_print.cpp
$ dfx canister call --type idl --output idl demo demo_debug_print '()'
...nothing is printed here
-> check the console of the local network. The canister will print:
Hello expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae
-> when deployed to IC's mainnet, nothing is printed.
*/
#include "demo_debug_print.h"
#include "ic_api.h"
#include <iostream>
void demo_debug_print() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
// You can use this
std::cout << "Hello " + caller.get_text() << std::endl;
// Or this.
IC_API::debug_print("Hello " + caller.get_text());
ic_api.from_wire();
ic_api.to_wire();
}
// file: src/demo_debug_print.h
#pragma once
#include "wasm_symbol.h"
void demo_debug_print() WASM_SYMBOL_EXPORTED("canister_query demo_debug_print");
// file: src/demo.did
service : {
"demo_debug_print": () -> () query;
};
In this example, the debug_print()
method is used to display a greeting message containing the caller's principal. The demo_debug_print()
method retrieves the CandidTypePrincipal
of the caller using theget_caller()
method, then it creates a message and passes it to debug_print()
.
The demo.did
file describes the interface of the service in the Candid Interface Description Language. It declares one query function, demo_debug_print
, accepting no arguments and returning no values.
IC_API constructor
Declaration:
// ic_api.h
class IC_API {
public:
IC_API(const CanisterBase &canister_entry,
const bool &debug_print);
}
This constructor creates an IC_API
instance and initializes the debug printing flag according to the debug_print
parameter passed to it.
Here's an example of how to create an IC_API
instance at the start of your public C++ method:
/* file: src/demo_ic_api.cpp
$ dfx canister call --type idl --output idl demo demo_ic_api '()'
*/
#include "demo_ic_api.h"
#include "ic_api.h"
void demo_ic_api() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
ic_api.from_wire();
ic_api.to_wire();
}
// file: src/demo_ic_api.h
#pragma once
#include "wasm_symbol.h"
void demo_ic_api() WASM_SYMBOL_EXPORTED("canister_query demo_ic_api");
// file: src/demo.did
service : {
"demo_ic_api": () -> () query;
};
In this example, an IC_API
object named ic_api
is created with the debug_print
flag set to false
. If the debug_print
attribute is toggled to true
, it results in more detailed console outputs during local execution. However, this flag is disregarded when the application operates in a production environment on the IC mainnet.
The demo_ic_api
canister method does not expect an argument, and does not return an argument. This is expressed by the empty argument lists for the from_wire
& to_wire
methods.
The from_wire
method is not required, but recommended, because it will verify that the incoming message indeed has zero arguments, and if not, it will trap explicitly and reject the message.
The to_wire
method is required to return the response. If omitted, the canister will will return Error: Failed query call
, with message that the canister did not reply to the call.
The demo.did
file describes the interface of the service in the Candid Interface Description Language. It declares one query function, demo_ic_api
, accepting no arguments and returning no values.
get_caller
Declaration:
// ic_api.h
class IC_API {
public:
CandidTypePrincipal get_caller();
}
This method returns the principal of the caller. The return type is CandidTypePrincipal
.
In the Internet Computer, principals are unique identifiers. They are derived from a public key and are cryptographically secure. When a user (or another canister) calls a canister method, they sign the message with their private key. The Internet Computer then uses the corresponding public key to verify the signature and thus the authenticity of the call.
Here is how to use get_caller()
in your C++ method:
/* file: src/demo_get_caller.cpp
$ dfx canister call --type idl --output idl demo demo_get_caller '()'
(Hello! Your principal is expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae)
*/
#include "demo_get_caller.h"
#include "ic_api.h"
void demo_get_caller() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
// The caller's authenticated principal
// - The calling message was signed using the caller's private key
// - The IC verified the signature using the corresponding public key
CandidTypePrincipal caller = ic_api.get_caller();
ic_api.from_wire();
std::string msg;
msg.append("Hello! Your principal is ");
msg.append(caller.get_text());
ic_api.to_wire(CandidTypeText(msg));
}
// file: src/demo_get_caller.h
#pragma once
#include "wasm_symbol.h"
void demo_get_caller() WASM_SYMBOL_EXPORTED("canister_query demo_get_caller");
// file: src/demo.did
service : {
"demo_get_caller": () -> (text) query;
};
In this example, get_caller()
is used to retrieve the CandidTypePrincipal
representing the caller. This could be useful in methods where you need to identify or verify the caller.
The demo.did
file describes the interface of the service in the Candid Interface Description Language. It declares one query function, demo_get_caller
, accepting no arguments and returning a Candid text
value.
get_canister_self
🚧 This documentation section is currently under construction! 🚧
Returns the principal (id) of the canister.
See QA canister_1 for an example.
get_canister_self_cycle_balance
🚧 This documentation section is currently under construction! 🚧
Returns the cycle balance of the canister.
See QA canister_1 for an example.
from_wire
Declaration:
// ic_api.h
class IC_API {
public:
void from_wire();
void from_wire(CandidType arg_in);
void from_wire(CandidArgs args_in);
void from_wire(IC_HttpRequest &request);
void from_wire(IC_HttpUpdateRequest &request);
}
Receives data from the wire in Candid format, assuming either no arguments, a single argument of a CandidType
object, or a CandidArgs
object to receive multiple arguments.
C++ data structures are passed into the CandidType
objects as pointers, and the from_wire
will deserialize the Candid byte stream and map the values on them.
Here is how to use from_wire()
in your C++ method:
/* file: src/demo_from_wire.cpp
$ dfx canister call --type idl --output idl demo demo_from_wire_no_arg '()'
$ dfx canister call --type idl --output idl demo demo_from_wire_one_arg '("Neuron Staking")'
("Hello expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae, your hobby is Neuron Staking")
$ dfx canister call --type idl --output idl demo demo_from_wire_multiple_args '("Neuron Staking", 3000 : nat64)'
("Hello expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae, you earned 3000 ICP from your hobby, Neuron Staking")
*/
#include "demo_from_wire.h"
#include "ic_api.h"
void demo_from_wire_no_arg() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
ic_api.from_wire();
ic_api.to_wire();
}
void demo_from_wire_one_arg() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
std::string hobby{""};
ic_api.from_wire(CandidTypeText{&hobby});
std::string msg;
msg.append("Hello " + caller.get_text() + ", ");
msg.append("your hobby is " + hobby);
ic_api.to_wire(CandidTypeText(msg));
}
void demo_from_wire_multiple_args() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
std::string hobby{""};
uint64_t num_icp{0};
CandidArgs args_in;
args_in.append(CandidTypeText(&hobby));
args_in.append(CandidTypeNat64(&num_icp));
ic_api.from_wire(args_in);
std::string msg;
msg.append("Hello " + caller.get_text() + ", ");
msg.append("you earned " + std::to_string(num_icp) +
" ICP from your hobby, " + hobby);
ic_api.to_wire(CandidTypeText(msg));
}
// file: src/demo_from_wire.h
#pragma once
#include "wasm_symbol.h"
void demo_from_wire_no_arg()
WASM_SYMBOL_EXPORTED("canister_query demo_from_wire_no_arg");
void demo_from_wire_one_arg()
WASM_SYMBOL_EXPORTED("canister_query demo_from_wire_one_arg");
void demo_from_wire_multiple_args()
WASM_SYMBOL_EXPORTED("canister_query demo_from_wire_multiple_args");
// file: src/demo.did
service : {
"demo_from_wire_no_arg": () -> () query;
"demo_from_wire_one_arg": (text) -> (text) query;
"demo_from_wire_multiple_args": (text, nat64) -> (text) query;
};
In this example, demo_from_wire()
is used to receive and process data in various scenarios - with no arguments, with one argument, and with multiple arguments.
The demo_from_wire_no_arg()
function simply creates an IC_API
instance, calls from_wire()
without any arguments, and then calls to_wire()
. This is a basic use case where no data is received or sent over the wire.
The demo_from_wire_one_arg()
function, on the other hand, receives a single CandidTypeText
object. The from_wire()
function is called with this object, which then deserializes the incoming Candid byte stream and maps the value onto the hobby
string. After creating a response message using the received hobby and the caller's principal, it calls to_wire()
to send the response.
Lastly, the demo_from_wire_multiple_args()
function handles a scenario where multiple arguments are received. It defines a CandidArgs
object, appends the expected arguments to that, and calls from_wire()
with this object. The function then constructs a response using the received hobby and the number of ICP earned from it, and calls to_wire()
to send this response.
This demonstrates how from_wire()
can be used effectively to receive and process data in a variety of use cases in your C++ methods.
The demo.did
file describes the interface of the service in the Candid Interface Description Language. It declares the three query functions and specifies the Candid types each functions accepts as arguments and returns as values.
is_controller
🚧 This documentation section is currently under construction! 🚧
Returns true if the principal passed as argument is a controller of the canister.
See QA canister_1 for an example.
time
Declaration:
// ic_api.h
class IC_API {
public:
static uint64_t time();
}
The time
method is used to retrieve the current system time from the Internet Computer. The time is returned as a uint64_t
representing the number of nanoseconds since the Unix epoch (1970-01-01). The time, as observed by the canister, is guaranteed to be monotonically increasing, even across canister upgrades. Additionally, within a single invocation of one entry point, the time is constant.
Please note that due to the decentralized nature of the Internet Computer, the system times of different canisters are unrelated and there is no notion of a timezone. Hence, calls from one canister to another may appear to travel “backwards in time”.
Here is how to use time()
in your C++ method:
/* file: src/demo_time.cpp
$ dfx canister call --type idl --output idl demo demo_time '()'
("The current system time in nanoseconds: 1684265399218380314 (2023-5-16 19:29:59)")
*/
#include "demo_time.h"
#include "ic_api.h"
#include <ctime>
#include <iostream>
#include <sstream>
std::string format_time(uint64_t time_in_ns) {
/*
https://internetcomputer.org/docs/current/references/ic-interface-spec#system-api-time
The time is given as nanoseconds since 1970-01-01.
- The IC guarantees that the time, as observed by the canister, is monotonically increasing,
even across canister upgrades.
- Within an invocation of one entry point, the time is constant.
- The system times of different canisters are unrelated, and calls from one canister to another
may appear to travel “backwards in time”.
- The IC is decentralized and has no notion of a timezone.
*/
std::time_t time_in_s = time_in_ns / 1'000'000'000;
std::tm *tm = std::gmtime(&time_in_s);
// Format the time manually
std::string formatted_time = "";
formatted_time += std::to_string(tm->tm_year + 1900); // Year
formatted_time += "-";
formatted_time += std::to_string(tm->tm_mon + 1); // Month
formatted_time += "-";
formatted_time += std::to_string(tm->tm_mday); // Day
formatted_time += " ";
formatted_time += std::to_string(tm->tm_hour); // Hour
formatted_time += ":";
formatted_time += std::to_string(tm->tm_min); // Minute
formatted_time += ":";
formatted_time += std::to_string(tm->tm_sec); // Second
return formatted_time;
}
void demo_time() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
uint64_t time_in_ns = ic_api.time();
std::string msg;
msg.append("The current system time in nanoseconds: " +
std::to_string(time_in_ns) + " (" + format_time(time_in_ns) + ")");
std::cout << msg << std::endl;
ic_api.to_wire(CandidTypeText(msg));
}
// file: src/demo_time.h
#pragma once
#include "wasm_symbol.h"
void demo_time() WASM_SYMBOL_EXPORTED("canister_query demo_time");
// file: src/demo.did
service : {
"demo_time": () -> (text) query;
};
In this example, the time()
method is used to retrieve the current system time in nanoseconds. The demo_time() method formats this time into a human-readable string and sends it back as a response to the caller.
The demo.did
file describes the interface of the service in the Candid Interface Description Language. It declares one query function, demo_time
, accepting no arguments and returning a Candid text
value.
to_wire
Declaration:
// ic_api.h
class IC_API {
public:
void to_wire();
void to_wire(const CandidType &arg_out);
void to_wire(const CandidArgs &args_out);
void to_wire(const IC_HttpResponse response);
}
Sends data to the wire in Candid format, assuming either no arguments, a single argument of a CandidType
object, or aCandidArgs
object to send multiple arguments.
C++ data structures are passed into the CandidType
objects, and the to_wire
function will serialize these values into a Candid byte stream to be sent over the wire.
Here is how to use to_wire()
in your C++ method:
/* file: src/demo_to_wire.cpp
$ dfx canister call --type idl --output idl demo demo_to_wire_no_arg '()'
$ dfx canister call --type idl --output idl demo demo_to_wire_one_arg '("Neuron Staking")'
("Hello expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae, your hobby is Neuron Staking")
$ dfx canister call --type idl --output idl demo demo_to_wire_multiple_args '("Neuron Staking", 3000 : nat64)'
("expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae", "Neuron Staking", 3_000 : nat64, "ICP")
*/
#include "demo_to_wire.h"
#include "ic_api.h"
void demo_to_wire_no_arg() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
ic_api.from_wire();
ic_api.to_wire();
}
void demo_to_wire_one_arg() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
std::string hobby{""};
ic_api.from_wire(CandidTypeText{&hobby});
std::string msg;
msg.append("Hello " + caller.get_text() + ", ");
msg.append("your hobby is " + hobby);
ic_api.to_wire(CandidTypeText(msg));
}
void demo_to_wire_multiple_args() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
std::string hobby{""};
uint64_t num_icp{0};
CandidArgs args_in;
args_in.append(CandidTypeText(&hobby));
args_in.append(CandidTypeNat64(&num_icp));
ic_api.from_wire(args_in);
CandidArgs args_out;
args_out.append(CandidTypeText(caller.get_text()));
args_out.append(CandidTypeText(hobby));
args_out.append(CandidTypeNat64(num_icp));
args_out.append(CandidTypeText("ICP"));
ic_api.to_wire(args_out);
}
// file: src/demo_to_wire.h
#pragma once
#include "wasm_symbol.h"
void demo_to_wire_no_arg()
WASM_SYMBOL_EXPORTED("canister_query demo_to_wire_no_arg");
void demo_to_wire_one_arg()
WASM_SYMBOL_EXPORTED("canister_query demo_to_wire_one_arg");
void demo_to_wire_multiple_args()
WASM_SYMBOL_EXPORTED("canister_query demo_to_wire_multiple_args");
// file: src/demo.did
service : {
"demo_to_wire_no_arg": () -> () query;
"demo_to_wire_one_arg": (text) -> (text) query;
"demo_to_wire_multiple_args": (text, nat64) -> (text, text, nat64, text) query;
};
In this example, to_wire()
is used to send data in various scenarios - with no arguments, with one argument, and with multiple arguments.
The demo_to_wire_no_arg()
function creates an IC_API
instance, calls from_wire()
without any arguments, and then calls to_wire()
. This is a basic use case where no data is received or sent over the wire.
The demo_to_wire_one_arg()
function receives and sends a single CandidTypeText
object. The from_wire()
function is called with this object, which then deserializes the incoming Candid byte stream and maps the value onto the hobby
string. After creating a response message using the received hobby and the caller's principal, it calls to_wire()
to send the response.
Lastly, the demo_to_wire_multiple_args()
function handles a scenario where multiple arguments are received and send. It defines a CandidArgs
object, appends the expected arguments to that, and calls from_wire()
with this object. After constructing a response using the received hobby and the number of ICP earned from it, it calls to_wire()
to send this response as a CandidArgs
object.
This demonstrates how to_wire()
can be effectively used to send data in a variety of use cases in your C++ methods.
The demo.did
file describes the interface of the service in the Candid Interface Description Language. It declares the three query functions and specifies the Candid types each functions accepts as arguments and returns as values.
trap
Declaration:
// ic_api.h
class IC_API {
public:
static void trap(const char *msg);
static void trap(const std::string &msg);
}
The trap
method is a utility function that causes the canister's execution to be immediately terminated. It takes a string as an argument, which is used as the error message that's returned to the caller. This method can be called in situations where an unrecoverable error has occurred and you wish to halt execution and report an error to the user. The trap
method accepts a C-style string (const char *
) or a C++ string (const std::string &
).
Here is how to use trap()
in your C++ method:
/* file: src/demo_trap.cpp
$ dfx canister call --type idl --output idl demo demo_trap '()'
Error: Failed query call.
Caused by: Failed query call.
The Replica returned an error: code 5, message: "IC0503: Canister bkyz2-fmaaa-aaaaa-qaaaq-cai trapped explicitly:
Hello expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae
This is a trap demo."
*/
#include "demo_trap.h"
#include "ic_api.h"
void demo_trap() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
IC_API::trap("\n Hello " + caller.get_text() + "\n This is a trap demo.");
}
// file: src/demo_trap.h
#pragma once
#include "wasm_symbol.h"
void demo_trap() WASM_SYMBOL_EXPORTED("canister_query demo_trap");
// file: src/demo.did
service : {
"demo_trap": () -> () query;
};
In this example, the trap()
method is used to halt the execution of the canister and return an error message to the caller. The demo_trap()
method retrieves the CandidTypePrincipal
of the caller using the get_caller()
method, then it creates an error message and passes it to trap()
.
The demo.did
file describes the interface of the service in the Candid Interface Description Language. It declares one query function, demo_trap
, accepting no arguments and returning no values.
utilities
This section outlines various utility functions designed to streamline the process of interacting with data structures within C++ smart contracts developed using icpp-pro.
string_to_uint128_t and string_to_int128_t
Declaration:
// ic_api.h
class IC_API {
public:
static std::optional<__uint128_t> string_to_uint128_t(const std::string &str);
static std::optional<__int128_t> string_to_int128_t(const std::string &str);
}
The string_to_uint128_t
& string_to_int128_t
functions serve to transform std::string
values into __uint128_t
and __int128_t
format.
As clang++
compiler extensions, these types cannot be initialized from string literals, thus necessitating these custom functions.
/* file: src/demo_string_to_int128.cpp
$ dfx canister call --type idl --output idl demo demo_string_to_int128 '()'
...nothing is printed here
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] max__uint128_t=340282366920938463463374607431768211455
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] max__int128_t=170141183460469231731687303715884105727
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] min__int128_t=-170141183460469231731687303715884105727
-> when deployed to IC's mainnet, nothing is printed.
*/
#include "demo_string_to_int128.h"
#include <iostream>
#include <optional>
#include <string>
#include "ic_api.h"
void demo_string_to_int128() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
// ----------------------------------------------------------------------------
// Numbers larger than uint64_t & int64_t can NOT be initialized with a literal
// - Use IC_API utility methods `string_to_uint128_t` & `string_to_int128_t`,
// to initialize them from a string.
// - These utility methods return an `std::optional`, which succeeds only if the
// conversion is successful.
std::optional<__uint128_t> max__uint128_t = IC_API::string_to_uint128_t(
"340282366920938463463374607431768211455"); // 2^128 - 1
std::optional<__int128_t> max__int128_t = IC_API::string_to_int128_t(
"170141183460469231731687303715884105727"); // +2^127 - 1
std::optional<__int128_t> min__int128_t = IC_API::string_to_int128_t(
"-170141183460469231731687303715884105727"); // -2^127 - 1
// Now we first check the content of the std::optional, before using to_string_128
// This is a roundtrip from string -> value -> string
if (max__uint128_t.has_value()) {
std::cout << "max__uint128_t=" +
IC_API::to_string_128(max__uint128_t.value())
<< std::endl;
} else {
IC_API::trap("ERROR: string_to_uint128_t failed for max__uint128_t");
}
if (max__int128_t.has_value()) {
std::cout << "max__int128_t=" + IC_API::to_string_128(max__int128_t.value())
<< std::endl;
} else {
IC_API::trap("ERROR: string_to_int128_t failed for max__int128_t");
}
if (min__int128_t.has_value()) {
std::cout << "min__int128_t=" + IC_API::to_string_128(min__int128_t.value())
<< std::endl;
} else {
IC_API::trap("ERROR: string_to_int128_t failed for min__int128_t");
}
// ----------------------------------------------------------------------------
ic_api.from_wire();
ic_api.to_wire();
}
// file: src/demo_string_to_int128.h
#pragma once
#include "wasm_symbol.h"
void demo_string_to_int128()
WASM_SYMBOL_EXPORTED("canister_query demo_string_to_int128");
// file: src/demo.did
service : {
"demo_string_to_int128": () -> () query;
};
to_string_128
Declaration:
// ic_api.h
class IC_API {
public:
static std::string to_string_128(__uint128_t v);
static std::string to_string_128(__int128_t v);
}
The to_string_128
function serves to transform __uint128_t
and __int128_t
values into std::string
format. As clang++
compiler extensions, these types are not handled by std::to_string
, thus necessitating this custom function.
/* file: src/demo_to_string_128.cpp
$ dfx canister call --type idl --output idl demo demo_to_string_128 '()'
...nothing is printed here
-> check the console of the local network. The canister will print:
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] v1 =101
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] v2 =102
[Canister bkyz2-fmaaa-aaaaa-qaaaq-cai] v3 =-103
-> when deployed to IC's mainnet, nothing is printed.
*/
#include "demo_to_string_128.h"
#include <iostream>
#include <optional>
#include <string>
#include "ic_api.h"
void demo_to_string_128() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
CandidTypePrincipal caller = ic_api.get_caller();
// -------------------------------------------------------------------------
// Numbers smaller than uint64_t & int64_t can be initialized with a literal
__uint128_t v1{101};
__int128_t v2{102};
__int128_t v3{-103};
// To create a string, use the IC_API utility method `to_string_128`
std::cout << "v1 =" + IC_API::to_string_128(v1) << std::endl;
std::cout << "v2 =" + IC_API::to_string_128(v2) << std::endl;
std::cout << "v3 =" + IC_API::to_string_128(v3) << std::endl;
// ----------------------------------------------------------------------------
// Numbers larger than uint64_t & int64_t can NOT be initialized with a literal
// See `demo_string_to_int128.cpp`
ic_api.from_wire();
ic_api.to_wire();
}
// file: src/demo_to_string_128.h
#pragma once
#include "wasm_symbol.h"
void demo_to_string_128()
WASM_SYMBOL_EXPORTED("canister_query demo_to_string_128");
// file: src/demo.did
service : {
"demo_to_string_128": () -> () query;
};
icpp.toml
The icpp.toml configuration file resides in the root of your project, and it defines the source files, and what compile & link flags to use.
You can select a different configuration toml file using the --config
option
icpp-pro has the following build commands:
1. icpp build-wasm
- builds a wasm to deploy to an IC canister
- reads the
[build-wasm]
section of theicpp.toml
- run
icpp build-wasm --help
for options - use
icpp build-wasm --config TOML_FILE_NAME
to use a different file for icpp.toml - use
icpp build-wasm --to-compile mine-no-lib
to skip building the libraries
2. icpp build-library
- builds a static library for inclusion in the wasm
- reads the
[[build-library]]
sections of theicpp.toml
- run
icpp build-library --help
for options - use
icpp build-library --config TOML_FILE_NAME
to use a different file for icpp.toml
3. icpp build-native
- builds a native debug executable, for interactive debugging in VS Code
- reads the
[build-native]
section of theicpp.toml
- run
icpp build-native --help
for options - use
icpp build-native --config TOML_FILE_NAME
to use a different file for icpp.toml - use
icpp build-native --to-compile mine-no-lib
to skip building the libraries
4. icpp build-library-native
- builds a static library for inclusion in the native executable
- reads the
[[build-library-native]]
sections of theicpp.toml
- run
icpp build-library-native --help
for options - use
icpp build-library-native --config TOML_FILE_NAME
to use a different file for icpp.toml
The content of the icpp.toml
is best explained by this annotated example.
Note that for the lists of paths & dirs, you can use python glob operators, like "scr/*.cpp"
[build-wasm]
# The name of the canister, must match definition in dfx.json
canister = "my_canister"
# The relative path to the candid file of the canister
did_path = "src/my_canister.did"
# A list of relative paths to the C++ files of the canister
cpp_paths = ["src/*.cpp", "src1/more/*.cpp"]
# A list of relative directory paths, where the compiler must look for C++ header files.
# - by default, the compiler already checks the directory where the toml file resides and the src sub-directory
cpp_include_dirs = ["src/vendors/*"]
# A list of additional C++ compile flags.
cpp_compile_flags = ["-D JSON_HAS_FILESYSTEM=0"]
# A list of additional link flags.
cpp_link_flags = []
# A list of relative paths to the C files of the canister
c_paths = []
# A list of relative directory paths, where the compiler must look for C header files.
# - by default, the compiler already checks the directory where the toml file resides and the src sub-directory
c_include_dirs = []
# A list of additional C compile flags.
c_compile_flags = []
# hook: pretend to optimize the wasm via this function
post_wasm_function = "scripts.optimize_wasm.main"
# Seldomly used - if non-empty, this will overwrite internal default compile & link flags
cpp_compile_flags_defaults = []
cpp_link_flags_defaults = []
c_compile_flags_defaults = []
[build-native]
# A list of relative paths to the additional C++ files to include in the native debug build.
# - the native debug build uses ALL the files listed in the [build-wasm] section, plus these
# - typically, it is just a main program with calls to canister methods, using the MockIC
cpp_paths = ["native/main.cpp"]
# A list of relative directory paths, where the compiler must look for C++ header files.
# - the native debug build uses ALL the include_dirs listed in the [build-wasm] section, pluse these
cpp_include_dirs = []
# A list of additional C++ compile flags.
# - the native debug build has it's own unique flags, and is NOT using the ones listed in the [build-wasm] section
cpp_compile_flags = []
# A list of additional link flags.
# - the native debug build has it's own unique flags, and is NOT using the ones listed in the [build-wasm] section
cpp_link_flags = []
# A list of relative paths to the additional C files to include in the native debug build.
# - the native debug build uses ALL the files listed in the [build-wasm] section, plus these
c_paths = []
# A list of relative directory paths, where the compiler must look for C header files.
# - the native debug build uses ALL the include_dirs listed in the [build-wasm] section, pluse these
c_include_dirs = []
# A list of additional C compile flags.
# - the native debug build has it's own unique flags, and is NOT using the ones listed in the [build-wasm] section
c_compile_flags = []
# Seldomly used - if non-empty, this will overwrite internal default compile & link flags
cpp_compile_flags_defaults = []
cpp_link_flags_defaults = []
c_compile_flags_defaults = []
[[build-library]]
# Name of the static library
lib_name = "libhello"
# Meaning of all fields below are same as described in [build-wasm]
cpp_paths = ["libhello/*.cpp"]
cpp_include_dirs = []
cpp_compile_flags = []
c_paths = []
c_include_dirs = []
c_compile_flags = []
[[build-library]]
# Name of the static library
lib_name = "libworld"
# Meaning of all fields below are same as described in [build-wasm]
cpp_paths = ["libworld/*.cpp"]
cpp_include_dirs = []
cpp_compile_flags = []
c_paths = []
c_include_dirs = []
c_compile_flags = []
hooks
icpp-pro has a build pipeline to go from C++ code to an Internet Computer compatible wasm:
cpp > objects > libs > wasi.wasm > wasm
You can write your own Python function, and tell icpp-pro about it in the icpp.toml project file.
An example can be found in canister_hooks:
# File structure:
|- icpp.toml # the icpp-pro project file
|- scripts # a folder with your custom scripts
| |- __init__.py # turns this folder into a python package
| |- optimize_wasm.py # your python module, with a fuction `main`
# file: icpp.toml
[build-wasm]
post_wasm_function = "scripts.optimize_wasm.main"
icpp-pro will then call your post_wasm_function at the appropriate time.
MockIC
The MockIC is a unique feature of icpp-pro. It allows you to test & interactively debug your C++ code using a native debug executable.
You use it in the following developer friendly workflow:
- create a set of unit tests in a main program, typically
native/main.cpp
- build a native debug executable using the
icpp build-native
command - this creates the executable
build-native/mockic.exe
- debug it in VS Code, using CodeLLDB
- you can also run it from the command line, which is recommended to do in a CI/CD pipeline
This approach accelerates development:
- native debug builds are much faster than the optimized wasm builds
- you can skip the deploy step
- you can debug with VS Code, setting breakpoints and explore the data
- you do NOT have to rely on debug_print statements, although you can use them as usual
Because the MockIC uses the exact same code for Candid decoding and encoding as when your code is running in a canister using WebAssembly, the data flow of messages coming in over the wire and going out to the wire is accurately modeled. You can build realistic test scenarios, and then debug them interactively.
We rely on this capability extensively and it increases development velocity tremendously. Being able to debug with breakpoints, explore the content of your data and step through the code as it runs is simply invaluable.
A good example how we used this capability is by looking at the native/main.cpp file for icpp_llama2, the backend canister of ICGPT. It would have been very difficult to port this LLM to the IC without native debug capability.
The way you use it is best explained by these code snipperts of the native/main.cpp file from our api-reference demo canister.
You need to pass in the serialized candid arguments as a hex string. For that, we recommend using didc
.
// file: native/main.cpp
#include "main.h"
#include <iostream>
// include files declaring the canister methods we want to test
#include "../src/demo_candid_type_bool.h"
#include "../src/demo_trap.h"
// The Mock IC coming with icpp-pro
#include "mock_ic.h"
int main() {
MockIC mockIC(true); // 'true' will debug_print
// -----------------------------------------------------------------------------
// Configs for the tests:
// The pretend principal of the caller
std::string my_principal{
"expmt-gtxsw-inftj-ttabj-qhp5s-nozup-n3bbo-k7zvn-dg4he-knac3-lae"};
bool silent_on_trap = true;
// -----------------------------------------------------------------------------
// Call a canister method and check the response:
// Arguments to the run_test method of the mockIC:
// "demo_candid_type_bool" : name of the unit test
// demo_candid_type_bool : the method to call:
// -> must be defined in include above
// -> mockIC internally uses a callback mechanism
// "4449444c00017e01" : the hex string of the encoded argument.
// "4449444c00017e01" : the hex string of the encoded expected result.
// silent_on_trap : true - when canister method traps, mockIC will not print the trap message
// false - when canister method traps, mockIC will print the trap message
// my_principal : a pretend principal of the caller
//
mockIC.run_test("demo_candid_type_bool", demo_candid_type_bool,
"4449444c00017e01", // didc encode '(true)'
"4449444c00017e01", // didc decode 4449444c00017e01
silent_on_trap, my_principal);
// -----------------------------------------------------------------------------
// Call a canister method and verify it traps:
// Arguments to the run_trap_test method of the mockIC:
// "demo_trap" : name of the unit test
// demo_trap : the method to call:
// -> must be defined in include above
// -> mockIC internally uses a callback mechanism
// "4449444c0000" : the hex string of the encoded argument.
// silent_on_trap : true - when canister method traps, mockIC will not print the trap message
// false - when canister method traps, mockIC will print the trap message
// my_principal : a pretend principal of the caller
// Verify that a this method traps
// '()' -> trap message
mockIC.run_trap_test("demo_trap", demo_trap, "4449444c0000", silent_on_trap,
my_principal);
// -----------------------------------------------------------------------------
// prints a summary of the unit tests.
// - returns 0 if all tests pass
// - returns 1 if any tests failed
return mockIC.test_summary();
}
Smoke Test
🚧 This documentation section is currently under construction! 🚧
Orthogonal Persistence
The IC automatically persists static/globally managed data after an update call.
see memory: Orthogonal Persistence of C++ Data Structures on the Internet Computer: A Study
Static data structures (stack)
These data structures are known at compile time and all data lives on the stack. You can just define them directly in the static/global section, and Orthogonal Persistence will work.
primitives
See counter: An Orthogonal Persistence demo for uint64_t
std::array
see memory: Orthogonal Persistence of C++ Data Structures on the Internet Computer: A Study
Dynamic data structures (heap)
These STL container keeps their metadata on the stack, while the data itself lives on the heap. You must define a pointer to the STL container in the static/global section, and use new/delete in your functions.
std::string
see memory: Orthogonal Persistence of C++ Data Structures on the Internet Computer: A Study
std::unordered_map
*see counter4me: An Orthogonal Persistence demo for std::unordered_map
(With your principal as the key!!)
std::vector
See counters: An Orthogonal Persistence demo for std::vector
std::deque
🚧 This documentation section is currently under construction! 🚧
std::list
🚧 This documentation section is currently under construction! 🚧
std::map
🚧 This documentation section is currently under construction! 🚧
std::set
🚧 This documentation section is currently under construction! 🚧