- {NOTIFICATION_ITEMS.map((item) => (
-
-
-
{item.label}
-
{item.description}
+ {NOTIFICATION_ITEMS.map((item) => {
+ const isChecked = notifications[item.key];
+ return (
+
+
+
{tSettings(item.labelKey)}
+
+ {isChecked ? tSettings(item.descEnabledKey) : tSettings(item.descDisabledKey)}
+
+
+
setNotifications({ [item.key]: next })}
+ ariaLabel={`Toggle ${tSettings(item.labelKey)}`}
+ />
-
setNotifications({ [item.key]: next })}
- ariaLabel={`Toggle ${item.label}`}
- />
-
- ))}
+ );
+ })}
-
Choose when to alert before maturity date.
+
+ {notifications.maturityReminder ? tSettings("timing.descEnabled") : tSettings("timing.descDisabled")}
+
@@ -113,36 +125,36 @@ export function NotificationSettings() {
leftIcon={
}
onClick={resetNotifications}
>
- Reset to defaults
+ {tSettings("resetDefaults")}
{/* ── Onboarding Tour Settings ────────────────────────────────────── */}
-
Onboarding Tour
+
{tOnboarding("skipLabel")}
- {t("restartTourDesc")}
+ {tOnboarding("restartTourDesc")}
Status:
- {tour.completed ? t("tourCompleted") : tour.skipped ? t("tourSkipped") : `Step ${(tour.stepIndex ?? 0) + 1}`}
+ {tour.completed ? tOnboarding("tourCompleted") : tour.skipped ? tOnboarding("tourSkipped") : tOnboarding("step", { current: (tour.stepIndex ?? 0) + 1, total: tour.persona === "sme" ? 3 : 4 })}
@@ -153,24 +165,26 @@ export function NotificationSettings() {
leftIcon={
}
onClick={() => restartTour(tour.persona)}
>
- {t("restartTour")}
+ {tOnboarding("restartTour")}
{/* ── Keyboard shortcuts toggle ──────────────────────────────────────── */}
-
Keyboard Shortcuts
+
{tSettings("keyboard.title")}
- Enable global keyboard shortcuts for faster navigation.
+ {tSettings("keyboard.desc")}
-
Enable shortcuts
+
{tSettings("keyboard.label")}
- Press ? to view all shortcuts.
+ {tSettings("keyboard.hintPrefix")}{" "}
+ ?{" "}
+ {tSettings("keyboard.hintSuffix")}
@@ -183,4 +197,3 @@ export function NotificationSettings() {
);
}
-
diff --git a/components/transactions/InProgressOverlay.tsx b/components/transactions/InProgressOverlay.tsx
index b325fa3..d28e401 100644
--- a/components/transactions/InProgressOverlay.tsx
+++ b/components/transactions/InProgressOverlay.tsx
@@ -51,7 +51,7 @@ export function InProgressOverlay() {
label: "Buyer Deposit",
desc: "Escrow funds locked in smart contract",
active: escrowState.step === "buyer_funding",
- success: escrowState.step !== "idle" && escrowState.step !== "buyer_funding" && escrowState.step !== "failed" && !(escrowState.errorStep === "buyer_funding"),
+ success: escrowState.step !== "idle" && escrowState.step !== "buyer_funding" && !(escrowState.errorStep === "buyer_funding"),
failed: escrowState.errorStep === "buyer_funding",
},
{
diff --git a/examples/tokens/04-snapshot-token/Cargo.lock b/examples/tokens/04-snapshot-token/Cargo.lock
new file mode 100644
index 0000000..27f0539
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/Cargo.lock
@@ -0,0 +1,1995 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "ahash"
+version = "0.8.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "arbitrary"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "ark-bls12-381"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-bn254"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc"
+dependencies = [
+ "ark-ec",
+ "ark-ff",
+ "ark-std",
+]
+
+[[package]]
+name = "ark-ec"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce"
+dependencies = [
+ "ahash",
+ "ark-ff",
+ "ark-poly",
+ "ark-serialize",
+ "ark-std",
+ "educe",
+ "fnv",
+ "hashbrown 0.15.5",
+ "itertools",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70"
+dependencies = [
+ "ark-ff-asm",
+ "ark-ff-macros",
+ "ark-serialize",
+ "ark-std",
+ "arrayvec",
+ "digest 0.10.7",
+ "educe",
+ "itertools",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-poly"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27"
+dependencies = [
+ "ahash",
+ "ark-ff",
+ "ark-serialize",
+ "ark-std",
+ "educe",
+ "fnv",
+ "hashbrown 0.15.5",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7"
+dependencies = [
+ "ark-serialize-derive",
+ "ark-std",
+ "arrayvec",
+ "digest 0.10.7",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-serialize-derive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a"
+dependencies = [
+ "num-traits",
+ "rand",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "base16ct"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64ct"
+version = "1.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes-lit"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b04f2b1d34cb428043f14aa4c853d14294532e8bbde3b6a3bc2faaaae31a1dd"
+dependencies = [
+ "num-bigint",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "cc"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_eval"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
+dependencies = [
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-link",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crate-git-revision"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "crate-git-revision"
+version = "0.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54851b5b3f24621804b1cded2820975623c205e3055d2d44031cdb1237339ac8"
+dependencies = [
+ "serde",
+ "serde_derive",
+ "serde_json",
+]
+
+[[package]]
+name = "crypto-bigint"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+dependencies = [
+ "generic-array",
+ "rand_core",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "ctor"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb"
+dependencies = [
+ "ctor-proc-macro",
+ "dtor",
+]
+
+[[package]]
+name = "ctor-proc-macro"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2"
+
+[[package]]
+name = "curve25519-dalek"
+version = "4.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "curve25519-dalek-derive",
+ "digest 0.10.7",
+ "fiat-crypto 0.2.9",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "curve25519-dalek-derive",
+ "digest 0.11.3",
+ "fiat-crypto 0.3.0",
+ "rustc_version",
+ "subtle",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "darling"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
+dependencies = [
+ "darling_core 0.20.11",
+ "darling_macro 0.20.11",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core 0.23.0",
+ "darling_macro 0.23.0",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.20.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
+dependencies = [
+ "darling_core 0.20.11",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core 0.23.0",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
+
+[[package]]
+name = "der"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer 0.10.4",
+ "const-oid",
+ "crypto-common 0.1.6",
+ "subtle",
+]
+
+[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer 0.12.1",
+ "crypto-common 0.2.2",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
+[[package]]
+name = "dtor"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301"
+dependencies = [
+ "dtor-proc-macro",
+]
+
+[[package]]
+name = "dtor-proc-macro"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5"
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
+
+[[package]]
+name = "ecdsa"
+version = "0.16.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
+dependencies = [
+ "der",
+ "digest 0.10.7",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+]
+
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8",
+ "signature",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
+dependencies = [
+ "curve25519-dalek 4.1.3",
+ "ed25519",
+ "rand_core",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "educe"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417"
+dependencies = [
+ "enum-ordinalize",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "either"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
+
+[[package]]
+name = "elliptic-curve"
+version = "0.13.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "digest 0.10.7",
+ "ff",
+ "generic-array",
+ "group",
+ "rand_core",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "enum-ordinalize"
+version = "4.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677"
+dependencies = [
+ "enum-ordinalize-derive",
+]
+
+[[package]]
+name = "enum-ordinalize-derive"
+version = "4.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "escape-bytes"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2"
+
+[[package]]
+name = "ethnum"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f"
+
+[[package]]
+name = "ff"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
+dependencies = [
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
+[[package]]
+name = "fiat-crypto"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "futures-core"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
+
+[[package]]
+name = "futures-util"
+version = "0.3.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
+dependencies = [
+ "typenum",
+ "version_check",
+ "zeroize",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "libc",
+ "wasi",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "group"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+dependencies = [
+ "ff",
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "hash32"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+dependencies = [
+ "allocator-api2",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "heapless"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
+dependencies = [
+ "hash32",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "hex-literal"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "hybrid-array"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.17.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "indexmap-nostd"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590"
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "js-sys"
+version = "0.3.103"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "k256"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
+dependencies = [
+ "cfg-if",
+ "ecdsa",
+ "elliptic-curve",
+ "sha2",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
+dependencies = [
+ "cpufeatures 0.2.17",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "log"
+version = "0.4.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
+
+[[package]]
+name = "macro-string"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "num-bigint"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
+
+[[package]]
+name = "num-derive"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "p256"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
+dependencies = [
+ "ecdsa",
+ "elliptic-curve",
+ "primeorder",
+ "sha2",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "primeorder"
+version = "0.13.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
+dependencies = [
+ "elliptic-curve",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "ref-cast"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "216e8f773d7923bcba9ceb86a86c93cabb3903a11872fc3f138c49630e50b96d"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9283685feec7d69af75fb0e858d5e7378f33fe4fc699383b2916ab9273e03c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "rfc6979"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
+dependencies = [
+ "hmac",
+ "subtle",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
+name = "schemars"
+version = "0.8.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
+dependencies = [
+ "dyn-clone",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "sec1"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
+[[package]]
+name = "serde"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c"
+dependencies = [
+ "base64",
+ "bs58",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.14.0",
+ "schemars 0.8.22",
+ "schemars 0.9.0",
+ "schemars 1.2.2",
+ "serde_core",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
+dependencies = [
+ "darling 0.23.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.2.17",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "sha3"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
+dependencies = [
+ "digest 0.10.7",
+ "keccak",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest 0.10.7",
+ "rand_core",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+
+[[package]]
+name = "soroban-builtin-sdk-macros"
+version = "27.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b77bc93d930032c487cb1506b6ed166b2af49db76d52678ec4887ac621ecce01"
+dependencies = [
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "soroban-env-common"
+version = "27.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b22e9981cdd444f3aa6734bc58d76195bf7eca3ccf1dd432b875af5d02da068"
+dependencies = [
+ "arbitrary",
+ "crate-git-revision 0.0.6",
+ "ethnum",
+ "num-derive",
+ "num-traits",
+ "serde",
+ "soroban-env-macros",
+ "soroban-wasmi",
+ "static_assertions",
+ "stellar-xdr",
+ "wasmparser",
+]
+
+[[package]]
+name = "soroban-env-guest"
+version = "27.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b6072f99ca6bf8e8d5b04e05d083dac785e5357d9c0f36a6658f819c2fd7d67"
+dependencies = [
+ "soroban-env-common",
+ "static_assertions",
+]
+
+[[package]]
+name = "soroban-env-host"
+version = "27.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c06afd7c75ce150ce53e4d77a77645b18e3fb61856a0ddc42bfcecdc39fa3b9"
+dependencies = [
+ "ark-bls12-381",
+ "ark-bn254",
+ "ark-ec",
+ "ark-ff",
+ "ark-serialize",
+ "curve25519-dalek 5.0.0",
+ "ecdsa",
+ "ed25519-dalek",
+ "elliptic-curve",
+ "generic-array",
+ "getrandom",
+ "hex-literal",
+ "hmac",
+ "k256",
+ "num-derive",
+ "num-integer",
+ "num-traits",
+ "p256",
+ "rand",
+ "rand_chacha",
+ "sec1",
+ "sha2",
+ "sha3",
+ "soroban-builtin-sdk-macros",
+ "soroban-env-common",
+ "soroban-wasmi",
+ "static_assertions",
+ "stellar-strkey 0.0.13",
+ "wasmparser",
+]
+
+[[package]]
+name = "soroban-env-macros"
+version = "27.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "647811bdd28a3ec40296987f6635781e5e1141c8f5affbbd53ba12b6295b7bb6"
+dependencies = [
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "stellar-xdr",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "soroban-ledger-snapshot"
+version = "27.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61d9941568d447ed905d151106b5e09463450a5dab5f3ed79173d853f6bfbc60"
+dependencies = [
+ "serde",
+ "serde_json",
+ "serde_with",
+ "soroban-env-common",
+ "soroban-env-host",
+ "thiserror",
+]
+
+[[package]]
+name = "soroban-sdk"
+version = "27.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adeb733dc6b5a65fbe312a1a9f692454fa417d34cdae36e50a0d587c4c848496"
+dependencies = [
+ "arbitrary",
+ "bytes-lit",
+ "crate-git-revision 0.0.9",
+ "ctor",
+ "derive_arbitrary",
+ "ed25519-dalek",
+ "rand",
+ "rustc_version",
+ "serde",
+ "serde_json",
+ "soroban-env-guest",
+ "soroban-env-host",
+ "soroban-ledger-snapshot",
+ "soroban-sdk-macros",
+ "stellar-strkey 0.0.16",
+ "visibility",
+]
+
+[[package]]
+name = "soroban-sdk-macros"
+version = "27.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ee66832a0f050ea780f8fdc6d9f958dd7ad0c6165068ef2c49c05ab00546d76"
+dependencies = [
+ "darling 0.20.11",
+ "heck",
+ "itertools",
+ "macro-string",
+ "proc-macro2",
+ "quote",
+ "sha2",
+ "soroban-env-common",
+ "soroban-spec",
+ "soroban-spec-rust",
+ "stellar-xdr",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "soroban-spec"
+version = "27.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "909f33af6771044b30f87d278d904ebded67061ec4ed12b7052e12bd20afa874"
+dependencies = [
+ "base64",
+ "sha2",
+ "stellar-xdr",
+ "thiserror",
+ "wasmparser",
+]
+
+[[package]]
+name = "soroban-spec-rust"
+version = "27.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3285d7f0393c79d8a7ff5fc6a781524fb6cc524ba7305fc245383f60122cfa66"
+dependencies = [
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "sha2",
+ "soroban-spec",
+ "stellar-xdr",
+ "syn 2.0.119",
+ "thiserror",
+]
+
+[[package]]
+name = "soroban-wasmi"
+version = "0.31.1-soroban.20.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "710403de32d0e0c35375518cb995d4fc056d0d48966f2e56ea471b8cb8fc9719"
+dependencies = [
+ "smallvec",
+ "spin",
+ "wasmi_arena",
+ "wasmi_core",
+ "wasmparser-nostd",
+]
+
+[[package]]
+name = "soroban_snapshot_token"
+version = "0.1.0"
+dependencies = [
+ "soroban-sdk",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "stellar-strkey"
+version = "0.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee1832fb50c651ad10f734aaf5d31ca5acdfb197a6ecda64d93fcdb8885af913"
+dependencies = [
+ "crate-git-revision 0.0.6",
+ "data-encoding",
+]
+
+[[package]]
+name = "stellar-strkey"
+version = "0.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "084afcb0d458c3d5d5baa2d294b18f881e62cc258ef539d8fdf68be7dbe45520"
+dependencies = [
+ "crate-git-revision 0.0.6",
+ "data-encoding",
+ "heapless",
+]
+
+[[package]]
+name = "stellar-xdr"
+version = "27.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05ff843326969bdf1ef673dcdba94c08f4a3c8f1e58d6e6ef39b1bd4f749179a"
+dependencies = [
+ "arbitrary",
+ "base64",
+ "cfg_eval",
+ "crate-git-revision 0.0.6",
+ "escape-bytes",
+ "ethnum",
+ "hex",
+ "serde",
+ "serde_with",
+ "sha2",
+ "stellar-strkey 0.0.13",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "time"
+version = "0.3.54"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244"
+dependencies = [
+ "deranged",
+ "num-conv",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
+
+[[package]]
+name = "time-macros"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "visibility"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasmi_arena"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073"
+
+[[package]]
+name = "wasmi_core"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a"
+dependencies = [
+ "downcast-rs",
+ "libm",
+ "num-traits",
+ "paste",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.116.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50"
+dependencies = [
+ "indexmap 2.14.0",
+ "semver",
+]
+
+[[package]]
+name = "wasmparser-nostd"
+version = "0.100.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa"
+dependencies = [
+ "indexmap-nostd",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.8.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/examples/tokens/04-snapshot-token/Cargo.toml b/examples/tokens/04-snapshot-token/Cargo.toml
new file mode 100644
index 0000000..a32447c
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "soroban_snapshot_token"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+soroban-sdk = { version = "27.0.3" }
+
+[dev-dependencies]
+soroban-sdk = { version = "27.0.3", features = ["testutils"] }
+
+[profile.release]
+opt-level = "z"
+overflow-checks = true
diff --git a/examples/tokens/04-snapshot-token/README.md b/examples/tokens/04-snapshot-token/README.md
new file mode 100644
index 0000000..899aa70
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/README.md
@@ -0,0 +1,95 @@
+# Soroban Snapshot Token (Governance-Enabled)
+
+A Soroban fungible token contract featuring state-of-the-art **balance snapshotting** support. This contract allows governance organizations (like DAOs) to record point-in-time balance snapshots and subsequently query any account's historical balance at a specific snapshot ID, primarily for secure, flash-loan-resistant voting-power calculations.
+
+## Overview
+
+In decentralized governance, naively querying current token balances for voting weight exposes the system to **flash loan attacks**—where a user borrows a large amount of tokens, votes, and returns the tokens in a single transaction block.
+
+By utilizing a snapshot token, a DAO can announce a proposal and take a snapshot of all balances at a specific block or moment in time. When voting begins, each voter's voting power is read from that historical snapshot ID via `balance_at_snapshot(account, snapshot_id)`. Subsequent token transfers have no effect on the captured voting weights.
+
+## Contract Interface
+
+The smart contract exposes the following public interface:
+
+- **`initialize(env: Env, admin: Address, decimals: u32, name: String, symbol: String)`**
+ Initializes the contract metadata, set the admin, and sets total supply and snapshot counter to `0`. Can only be called once.
+- **`mint(env: Env, to: Address, amount: i128)`**
+ Mints a positive amount of new tokens to the specified receiver. Requires admin authorization. Records pre-change balance for the sparse-snapshot pattern.
+- **`transfer(env: Env, from: Address, to: Address, amount: i128)`**
+ Transfers `amount` of tokens from the sender to the receiver. Requires authorization of the `from` account. Records pre-change balances for both sender and receiver.
+- **`balance(env: Env, account: Address) -> i128`**
+ Returns the current balance of the specified account.
+- **`create_snapshot(env: Env, caller: Address) -> u32`**
+ Creates a new snapshot and returns the incremented snapshot ID. Requires admin authorization.
+- **`balance_at_snapshot(env: Env, account: Address, snapshot_id: u32) -> i128`**
+ Queries the balance of `account` exactly at the moment `snapshot_id` was created. Handles all edge cases gracefully.
+- **`current_snapshot(env: Env) -> u32`**
+ Returns the latest created snapshot ID.
+- **`total_snapshots(env: Env) -> u32`**
+ Returns the total number of snapshots created.
+- **`decimals(env: Env) -> u32`**
+ Returns the number of decimals.
+- **`name(env: Env) -> String`**
+ Returns the token name.
+- **`symbol(env: Env) -> String`**
+ Returns the token symbol.
+- **`total_supply(env: Env) -> i128`**
+ Returns the current total supply of the token.
+- **`admin(env: Env) -> Address`**
+ Returns the administrator's address.
+
+## The Sparse-Snapshot Pattern
+
+Naively copying and storing the balance of *every* account at *every* snapshot would require `O(holders * snapshot_count)` storage, resulting in astronomical transaction fees and unbound storage growth.
+
+To avoid this, we implement the standard **sparse-snapshot pattern** (inspired by OpenZeppelin's `ERC20Snapshot`):
+1. Balance histories are stored lazily: we only write a new snapshot entry for an account when its balance actually changes *after* a snapshot has been taken.
+2. During `mint` or `transfer`, the contract checks if the current snapshot has already recorded the pre-change balance of the involved accounts. If not, the current balance is written to their snapshot history map first before the balance is updated.
+3. This guarantees that each account's snapshot history grows at most by `1` entry per snapshot boundary crossed with transaction activity, completely independent of the total number of token holders.
+
+### Historical Query Resolution
+
+When querying `balance_at_snapshot(account, snapshot_id)`:
+1. If the requested `snapshot_id` does not exist yet (i.e. is `0` or greater than `current_snapshot`), the contract returns a `SnapshotNotFound` error.
+2. The contract searches the account's sparse snapshot records (stored as a sorted map of `snapshot_id -> balance`):
+ - It finds the smallest recorded snapshot ID `k` such that `k >= snapshot_id`.
+ - If found, the balance during `snapshot_id` is exactly the value recorded at `k` (because that was the pre-change balance stored during the first transaction after `snapshot_id` existed).
+ - If no such key exists, it means the account has had **no activity** since `snapshot_id` was created, so its current balance is still exactly what it was during `snapshot_id`.
+3. If an account has never held any tokens, querying returns `0` (given a valid snapshot ID), not an error.
+
+## Step-by-Step Build & Test Guide
+
+To compile and test the snapshot token example:
+
+```bash
+# 1. Navigate to the snapshot token directory
+cd examples/tokens/04-snapshot-token
+
+# 2. Run all unit tests
+cargo test
+
+# 3. Compile the contract to WASM release target
+cargo build --target wasm32-unknown-unknown --release
+```
+
+## Concrete Governance Walkthrough
+
+Below is a step-by-step example of how a DAO governance flow utilizes this token:
+
+1. **Setup**: The DAO initializes the `SnapshotToken` and mints `100 GT` to Alice and `50 GT` to Bob.
+2. **Proposal Created**: A new proposal is submitted to the DAO. To prevent voting manipulation, the DAO admin creates a snapshot:
+ ```rust
+ let snapshot_id = client.create_snapshot(&admin); // returns 1
+ ```
+3. **Voting Power Queried**: Alice has `100` votes and Bob has `50` votes at `snapshot_id = 1`.
+4. **Subsequent Transfer**: Alice transfers `40 GT` to Bob after the snapshot was taken. Alice's current balance becomes `60 GT` and Bob's current balance becomes `90 GT`.
+5. **Vote Counting**: When the voting contract tallies the votes:
+ - Alice's voting weight = `client.balance_at_snapshot(&alice, &1)` -> returns `100`.
+ - Bob's voting weight = `client.balance_at_snapshot(&bob, &1)` -> returns `50`.
+ - The transfer that occurred after the snapshot did not alter their voting weight.
+
+## Design Notes
+
+- **Access Control for `create_snapshot`**: Creating snapshots is strictly restricted to the token `admin` using `caller.require_auth()`. This prevents griefing/spamming snapshot creation to block storage.
+- **Unbounded Storage Prevention**: Balance history entries are entirely activity-driven. If an account is inactive, its history takes `0` additional bytes of storage regardless of how many snapshots are taken.
diff --git a/examples/tokens/04-snapshot-token/src/lib.rs b/examples/tokens/04-snapshot-token/src/lib.rs
new file mode 100644
index 0000000..2088710
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/src/lib.rs
@@ -0,0 +1,280 @@
+#![no_std]
+#![allow(deprecated)]
+use soroban_sdk::{
+ contract, contracterror, contractimpl, contracttype, Address, Env, Map, String, Symbol,
+};
+
+#[contracterror]
+#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
+pub enum SnapshotTokenError {
+ AlreadyInitialized = 1,
+ NotAuthorized = 2,
+ InsufficientBalance = 3,
+ SnapshotNotFound = 4,
+ InvalidAmount = 5,
+}
+
+#[contracttype]
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub enum DataKey {
+ Admin,
+ Decimals,
+ Name,
+ Symbol,
+ TotalSupply,
+ Balances(Address),
+ SnapshotCounter,
+ SnapshotBalances(Address),
+}
+
+#[contract]
+pub struct SnapshotToken;
+
+#[contractimpl]
+impl SnapshotToken {
+ pub fn initialize(
+ env: Env,
+ admin: Address,
+ decimals: u32,
+ name: String,
+ symbol: String,
+ ) -> Result<(), SnapshotTokenError> {
+ if env.storage().persistent().has(&DataKey::Admin) {
+ return Err(SnapshotTokenError::AlreadyInitialized);
+ }
+ env.storage().persistent().set(&DataKey::Admin, &admin);
+ env.storage()
+ .persistent()
+ .set(&DataKey::Decimals, &decimals);
+ env.storage().persistent().set(&DataKey::Name, &name);
+ env.storage().persistent().set(&DataKey::Symbol, &symbol);
+ env.storage()
+ .persistent()
+ .set(&DataKey::TotalSupply, &0i128);
+ env.storage()
+ .persistent()
+ .set(&DataKey::SnapshotCounter, &0u32);
+ Ok(())
+ }
+
+ pub fn mint(env: Env, to: Address, amount: i128) -> Result<(), SnapshotTokenError> {
+ let admin = get_admin(&env)?;
+ admin.require_auth();
+
+ if amount <= 0 {
+ return Err(SnapshotTokenError::InvalidAmount);
+ }
+
+ record_snapshot_balance(&env, &to);
+
+ let current_bal = get_current_balance(&env, &to);
+ let new_bal = current_bal
+ .checked_add(amount)
+ .ok_or(SnapshotTokenError::InvalidAmount)?;
+ env.storage()
+ .persistent()
+ .set(&DataKey::Balances(to.clone()), &new_bal);
+
+ let total_supply: i128 = env
+ .storage()
+ .persistent()
+ .get(&DataKey::TotalSupply)
+ .unwrap_or(0);
+ let new_supply = total_supply
+ .checked_add(amount)
+ .ok_or(SnapshotTokenError::InvalidAmount)?;
+ env.storage()
+ .persistent()
+ .set(&DataKey::TotalSupply, &new_supply);
+
+ env.events()
+ .publish((Symbol::new(&env, "mint"), admin, to), amount);
+
+ Ok(())
+ }
+
+ pub fn transfer(
+ env: Env,
+ from: Address,
+ to: Address,
+ amount: i128,
+ ) -> Result<(), SnapshotTokenError> {
+ from.require_auth();
+
+ if amount <= 0 {
+ return Err(SnapshotTokenError::InvalidAmount);
+ }
+
+ let from_bal = get_current_balance(&env, &from);
+ if from_bal < amount {
+ return Err(SnapshotTokenError::InsufficientBalance);
+ }
+
+ record_snapshot_balance(&env, &from);
+ record_snapshot_balance(&env, &to);
+
+ let new_from_bal = from_bal - amount;
+ env.storage()
+ .persistent()
+ .set(&DataKey::Balances(from.clone()), &new_from_bal);
+
+ let to_bal = get_current_balance(&env, &to);
+ let new_to_bal = to_bal
+ .checked_add(amount)
+ .ok_or(SnapshotTokenError::InvalidAmount)?;
+ env.storage()
+ .persistent()
+ .set(&DataKey::Balances(to.clone()), &new_to_bal);
+
+ env.events()
+ .publish((Symbol::new(&env, "transfer"), from, to), amount);
+
+ Ok(())
+ }
+
+ pub fn balance(env: Env, account: Address) -> i128 {
+ get_current_balance(&env, &account)
+ }
+
+ pub fn create_snapshot(env: Env, caller: Address) -> Result
{
+ let admin = get_admin(&env)?;
+ if caller != admin {
+ return Err(SnapshotTokenError::NotAuthorized);
+ }
+ caller.require_auth();
+
+ let current_snap = get_current_snapshot(&env);
+ let next_snap = current_snap
+ .checked_add(1)
+ .ok_or(SnapshotTokenError::InvalidAmount)?;
+
+ env.storage()
+ .persistent()
+ .set(&DataKey::SnapshotCounter, &next_snap);
+
+ env.events()
+ .publish((Symbol::new(&env, "snapshot"), caller), next_snap);
+
+ Ok(next_snap)
+ }
+
+ pub fn balance_at_snapshot(
+ env: Env,
+ account: Address,
+ snapshot_id: u32,
+ ) -> Result {
+ get_balance_at_snapshot(&env, account, snapshot_id)
+ }
+
+ pub fn current_snapshot(env: Env) -> u32 {
+ get_current_snapshot(&env)
+ }
+
+ pub fn total_snapshots(env: Env) -> u32 {
+ get_current_snapshot(&env)
+ }
+
+ pub fn decimals(env: Env) -> u32 {
+ env.storage()
+ .persistent()
+ .get(&DataKey::Decimals)
+ .unwrap_or(0)
+ }
+
+ pub fn name(env: Env) -> String {
+ env.storage()
+ .persistent()
+ .get(&DataKey::Name)
+ .unwrap_or_else(|| String::from_str(&env, ""))
+ }
+
+ pub fn symbol(env: Env) -> String {
+ env.storage()
+ .persistent()
+ .get(&DataKey::Symbol)
+ .unwrap_or_else(|| String::from_str(&env, ""))
+ }
+
+ pub fn total_supply(env: Env) -> i128 {
+ env.storage()
+ .persistent()
+ .get(&DataKey::TotalSupply)
+ .unwrap_or(0)
+ }
+
+ pub fn admin(env: Env) -> Result {
+ get_admin(&env)
+ }
+}
+
+#[cfg(test)]
+mod test;
+
+// Private helper functions
+
+fn get_admin(env: &Env) -> Result {
+ env.storage()
+ .persistent()
+ .get(&DataKey::Admin)
+ .ok_or(SnapshotTokenError::NotAuthorized)
+}
+
+fn get_current_balance(env: &Env, account: &Address) -> i128 {
+ env.storage()
+ .persistent()
+ .get(&DataKey::Balances(account.clone()))
+ .unwrap_or(0)
+}
+
+fn get_current_snapshot(env: &Env) -> u32 {
+ env.storage()
+ .persistent()
+ .get(&DataKey::SnapshotCounter)
+ .unwrap_or(0)
+}
+
+fn record_snapshot_balance(env: &Env, account: &Address) {
+ let current_snap = get_current_snapshot(env);
+ if current_snap == 0 {
+ return;
+ }
+
+ let history_key = DataKey::SnapshotBalances(account.clone());
+ let mut history = env
+ .storage()
+ .persistent()
+ .get::<_, Map>(&history_key)
+ .unwrap_or_else(|| Map::new(env));
+
+ if !history.contains_key(current_snap) {
+ let current_bal = get_current_balance(env, account);
+ history.set(current_snap, current_bal);
+ env.storage().persistent().set(&history_key, &history);
+ }
+}
+
+fn get_balance_at_snapshot(
+ env: &Env,
+ account: Address,
+ snapshot_id: u32,
+) -> Result {
+ let current_snap = get_current_snapshot(env);
+ if snapshot_id == 0 || snapshot_id > current_snap {
+ return Err(SnapshotTokenError::SnapshotNotFound);
+ }
+
+ let history_key = DataKey::SnapshotBalances(account.clone());
+ if let Some(history) = env
+ .storage()
+ .persistent()
+ .get::<_, Map>(&history_key)
+ {
+ for key in history.keys() {
+ if key >= snapshot_id {
+ return Ok(history.get(key).unwrap_or(0));
+ }
+ }
+ }
+
+ Ok(get_current_balance(env, &account))
+}
diff --git a/examples/tokens/04-snapshot-token/src/test.rs b/examples/tokens/04-snapshot-token/src/test.rs
new file mode 100644
index 0000000..d5e46f3
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/src/test.rs
@@ -0,0 +1,180 @@
+#![cfg(test)]
+#![allow(deprecated)]
+
+use super::*;
+use soroban_sdk::{testutils::Address as _, Address, Env, String};
+
+fn setup_test() -> (Env, SnapshotTokenClient<'static>, Address, Address, Address) {
+ let env = Env::default();
+ env.mock_all_auths();
+
+ let contract_id = env.register_contract(None, SnapshotToken);
+ let client = SnapshotTokenClient::new(&env, &contract_id);
+
+ let admin = Address::generate(&env);
+ let user1 = Address::generate(&env);
+ let user2 = Address::generate(&env);
+
+ client.initialize(
+ &admin,
+ &7,
+ &String::from_str(&env, "GovToken"),
+ &String::from_str(&env, "GT"),
+ );
+
+ (env, client, admin, user1, user2)
+}
+
+#[test]
+fn test_initialization() {
+ let (env, client, admin, _, _) = setup_test();
+ assert_eq!(client.decimals(), 7);
+ assert_eq!(client.name(), String::from_str(&env, "GovToken"));
+ assert_eq!(client.symbol(), String::from_str(&env, "GT"));
+ assert_eq!(client.admin(), admin);
+ assert_eq!(client.total_supply(), 0);
+ assert_eq!(client.current_snapshot(), 0);
+}
+
+#[test]
+fn test_mint_admin() {
+ let (_, client, _, user1, _) = setup_test();
+ let amount = 1000i128;
+ client.mint(&user1, &amount);
+ assert_eq!(client.balance(&user1), amount);
+ assert_eq!(client.total_supply(), amount);
+}
+
+#[test]
+fn test_mint_invalid_amount() {
+ let (_, client, _, user1, _) = setup_test();
+ let err = client.try_mint(&user1, &-100);
+ assert!(err.is_err());
+
+ let err2 = client.try_mint(&user1, &0);
+ assert!(err2.is_err());
+}
+
+#[test]
+fn test_transfer_success() {
+ let (_, client, _, user1, user2) = setup_test();
+ client.mint(&user1, &100);
+ client.transfer(&user1, &user2, &40);
+ assert_eq!(client.balance(&user1), 60);
+ assert_eq!(client.balance(&user2), 40);
+}
+
+#[test]
+fn test_transfer_insufficient_balance() {
+ let (_, client, _, user1, user2) = setup_test();
+ client.mint(&user1, &50);
+ let err = client.try_transfer(&user1, &user2, &100);
+ assert!(err.is_err());
+}
+
+#[test]
+fn test_snapshot_id_incrementing() {
+ let (_, client, admin, _, _) = setup_test();
+ assert_eq!(client.create_snapshot(&admin), 1);
+ assert_eq!(client.current_snapshot(), 1);
+ assert_eq!(client.create_snapshot(&admin), 2);
+ assert_eq!(client.current_snapshot(), 2);
+ assert_eq!(client.total_snapshots(), 2);
+}
+
+#[test]
+fn test_create_snapshot_not_authorized() {
+ let (_, client, _, user1, _) = setup_test();
+ let err = client.try_create_snapshot(&user1);
+ assert!(err.is_err());
+}
+
+#[test]
+fn test_balance_at_snapshot_no_activity() {
+ let (_, client, admin, user1, _) = setup_test();
+ client.mint(&user1, &500);
+
+ assert_eq!(client.create_snapshot(&admin), 1);
+
+ assert_eq!(client.balance_at_snapshot(&user1, &1), 500);
+}
+
+#[test]
+fn test_balance_at_snapshot_change_before() {
+ let (_, client, admin, user1, _) = setup_test();
+ client.mint(&user1, &100);
+ client.mint(&user1, &200);
+
+ assert_eq!(client.create_snapshot(&admin), 1);
+
+ assert_eq!(client.balance_at_snapshot(&user1, &1), 300);
+}
+
+#[test]
+fn test_balance_at_snapshot_unaffected_by_subsequent() {
+ let (_, client, admin, user1, user2) = setup_test();
+ client.mint(&user1, &500);
+
+ assert_eq!(client.create_snapshot(&admin), 1);
+
+ client.transfer(&user1, &user2, &200);
+
+ assert_eq!(client.balance(&user1), 300);
+ assert_eq!(client.balance(&user2), 200);
+
+ assert_eq!(client.balance_at_snapshot(&user1, &1), 500);
+ assert_eq!(client.balance_at_snapshot(&user2, &1), 0);
+}
+
+#[test]
+fn test_multi_snapshot_complex() {
+ let (_, client, admin, user1, user2) = setup_test();
+
+ client.mint(&user1, &100);
+
+ assert_eq!(client.create_snapshot(&admin), 1);
+
+ client.transfer(&user1, &user2, &30);
+
+ assert_eq!(client.create_snapshot(&admin), 2);
+
+ client.mint(&user2, &50);
+
+ assert_eq!(client.create_snapshot(&admin), 3);
+
+ assert_eq!(client.balance_at_snapshot(&user1, &1), 100);
+ assert_eq!(client.balance_at_snapshot(&user2, &1), 0);
+
+ assert_eq!(client.balance_at_snapshot(&user1, &2), 70);
+ assert_eq!(client.balance_at_snapshot(&user2, &2), 30);
+
+ assert_eq!(client.balance_at_snapshot(&user1, &3), 70);
+ assert_eq!(client.balance_at_snapshot(&user2, &3), 80);
+
+ assert_eq!(client.balance(&user1), 70);
+ assert_eq!(client.balance(&user2), 80);
+}
+
+#[test]
+fn test_query_non_existent_snapshot() {
+ let (_, client, admin, user1, _) = setup_test();
+ let err1 = client.try_balance_at_snapshot(&user1, &1);
+ assert!(err1.is_err());
+
+ let err2 = client.try_balance_at_snapshot(&user1, &0);
+ assert!(err2.is_err());
+
+ assert_eq!(client.create_snapshot(&admin), 1);
+ let err3 = client.try_balance_at_snapshot(&user1, &2);
+ assert!(err3.is_err());
+}
+
+#[test]
+fn test_query_user_with_no_history() {
+ let (_, client, admin, _, _) = setup_test();
+ let stranger = Address::generate(&client.env);
+
+ assert_eq!(client.create_snapshot(&admin), 1);
+
+ assert_eq!(client.balance_at_snapshot(&stranger, &1), 0);
+}
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_balance_at_snapshot_change_before.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_balance_at_snapshot_change_before.1.json
new file mode 100644
index 0000000..30a7cc9
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_balance_at_snapshot_change_before.1.json
@@ -0,0 +1,356 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "i128": "100"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "i128": "200"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "create_snapshot",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "300"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 1
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "300"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "801925984706572462"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "1033654523790656264"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "5541220902715666415"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_balance_at_snapshot_no_activity.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_balance_at_snapshot_no_activity.1.json
new file mode 100644
index 0000000..9c2fc9f
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_balance_at_snapshot_no_activity.1.json
@@ -0,0 +1,314 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "i128": "500"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "create_snapshot",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "500"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 1
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "500"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "801925984706572462"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "5541220902715666415"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_balance_at_snapshot_unaffected_by_subsequent.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_balance_at_snapshot_unaffected_by_subsequent.1.json
new file mode 100644
index 0000000..d21163c
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_balance_at_snapshot_unaffected_by_subsequent.1.json
@@ -0,0 +1,461 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "i128": "500"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "create_snapshot",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": "200"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "300"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "200"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotBalances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "u32": 1
+ },
+ "val": {
+ "i128": "500"
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotBalances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "u32": 1
+ },
+ "val": {
+ "i128": "0"
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 1
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "500"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "801925984706572462"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "5541220902715666415"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "1033654523790656264"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_create_snapshot_not_authorized.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_create_snapshot_not_authorized.1.json
new file mode 100644
index 0000000..40c6a14
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_create_snapshot_not_authorized.1.json
@@ -0,0 +1,206 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 0
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "0"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_initialization.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_initialization.1.json
new file mode 100644
index 0000000..2970b89
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_initialization.1.json
@@ -0,0 +1,211 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [],
+ [],
+ [],
+ [],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 0
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "0"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_mint_admin.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_mint_admin.1.json
new file mode 100644
index 0000000..4fc72ec
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_mint_admin.1.json
@@ -0,0 +1,276 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "i128": "1000"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "1000"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 0
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "1000"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "801925984706572462"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_mint_invalid_amount.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_mint_invalid_amount.1.json
new file mode 100644
index 0000000..04c0d31
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_mint_invalid_amount.1.json
@@ -0,0 +1,207 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 0
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "0"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_multi_snapshot_complex.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_multi_snapshot_complex.1.json
new file mode 100644
index 0000000..83389d6
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_multi_snapshot_complex.1.json
@@ -0,0 +1,593 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "i128": "100"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "create_snapshot",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": "30"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "create_snapshot",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": "50"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "create_snapshot",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [],
+ [],
+ [],
+ [],
+ [],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "70"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "80"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotBalances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "u32": 1
+ },
+ "val": {
+ "i128": "100"
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotBalances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "u32": 1
+ },
+ "val": {
+ "i128": "0"
+ }
+ },
+ {
+ "key": {
+ "u32": 2
+ },
+ "val": {
+ "i128": "30"
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 3
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "150"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "801925984706572462"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "2032731177588607455"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "4270020994084947596"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "4837995959683129791"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "5541220902715666415"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "1033654523790656264"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_query_non_existent_snapshot.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_query_non_existent_snapshot.1.json
new file mode 100644
index 0000000..5d0d839
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_query_non_existent_snapshot.1.json
@@ -0,0 +1,247 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "create_snapshot",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 1
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "0"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "801925984706572462"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_query_user_with_no_history.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_query_user_with_no_history.1.json
new file mode 100644
index 0000000..e4c9e25
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_query_user_with_no_history.1.json
@@ -0,0 +1,245 @@
+{
+ "generators": {
+ "address": 5,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "create_snapshot",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 1
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "0"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "801925984706572462"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_snapshot_id_incrementing.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_snapshot_id_incrementing.1.json
new file mode 100644
index 0000000..deaf303
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_snapshot_id_incrementing.1.json
@@ -0,0 +1,286 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "create_snapshot",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "create_snapshot",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 2
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "0"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "801925984706572462"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "5541220902715666415"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_transfer_insufficient_balance.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_transfer_insufficient_balance.1.json
new file mode 100644
index 0000000..3a9bdf2
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_transfer_insufficient_balance.1.json
@@ -0,0 +1,275 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "i128": "50"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "50"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 0
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "50"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "801925984706572462"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/04-snapshot-token/test_snapshots/test/test_transfer_success.1.json b/examples/tokens/04-snapshot-token/test_snapshots/test/test_transfer_success.1.json
new file mode 100644
index 0000000..3761f9c
--- /dev/null
+++ b/examples/tokens/04-snapshot-token/test_snapshots/test/test_transfer_success.1.json
@@ -0,0 +1,348 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0,
+ "mux_id": 0
+ },
+ "auth": [
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "i128": "100"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": "40"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 27,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "60"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balances"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "40"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Decimals"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 7
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Name"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GovToken"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "SnapshotCounter"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "u32": 0
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Symbol"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "string": "GT"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "vec": [
+ {
+ "symbol": "TotalSupply"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "i128": "100"
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "801925984706572462"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": "5541220902715666415"
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 6311999
+ },
+ {
+ "entry": {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ "live_until": 4095
+ }
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/examples/tokens/README.md b/examples/tokens/README.md
new file mode 100644
index 0000000..072b37b
--- /dev/null
+++ b/examples/tokens/README.md
@@ -0,0 +1,8 @@
+# Soroban Cookbook - Tokens Category
+
+This category contains cookbook recipes and example contracts demonstrating how to implement and extend token standards on Stellar's Soroban smart contract platform.
+
+## Examples
+
+- **[04-snapshot-token](./04-snapshot-token)**
+ A complete SEP-41-style fungible token with efficient snapshot balance tracking. It implements the sparse-snapshot pattern to record point-in-time balance snapshots, which are essential for historical queries and decentralized governance voting-power calculations.
diff --git a/hooks/__tests__/useReminderPrefs.test.tsx b/hooks/__tests__/useReminderPrefs.test.tsx
new file mode 100644
index 0000000..4bdb4a1
--- /dev/null
+++ b/hooks/__tests__/useReminderPrefs.test.tsx
@@ -0,0 +1,311 @@
+import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
+import { renderHook, act } from "@testing-library/react";
+import { useMaturityReminder } from "../useMaturityReminder";
+import { useFundingReminder } from "../useFundingReminder";
+import { useRepaymentReminder } from "../useRepaymentReminder";
+import { useSettingsStore, DEFAULT_NOTIFICATION_PREFS } from "../../store/settingsStore";
+import { useToast } from "@/hooks/useToast";
+import type { Invoice } from "../../types/invoice";
+
+vi.mock("@/lib/env", () => ({
+ env: {
+ NEXT_PUBLIC_STELLAR_NETWORK: "testnet",
+ NEXT_PUBLIC_STELLAR_RPC_URL: "https://soroban-testnet.stellar.org",
+ NEXT_PUBLIC_STELLAR_HORIZON_URL: "https://horizon-testnet.stellar.org",
+ NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE: "Test SDF Network ; September 2015",
+ NEXT_PUBLIC_INVOICE_CONTRACT_ID: "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
+ NEXT_PUBLIC_MARKETPLACE_CONTRACT_ID: "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
+ NEXT_PUBLIC_TOKEN_CONTRACT_ID: "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA",
+ NEXT_PUBLIC_IPFS_GATEWAY: "https://ipfs.io/ipfs/",
+ NEXT_PUBLIC_ENABLE_MOCK_DATA: true,
+ },
+}));
+
+vi.mock("next-intl", () => ({
+ useTranslations: () => (key: string) => key,
+}));
+
+vi.mock("@/hooks/useToast", () => {
+ const success = vi.fn();
+ const error = vi.fn();
+ const dismiss = vi.fn();
+ return {
+ useToast: () => ({
+ success,
+ error,
+ dismiss,
+ }),
+ };
+});
+
+const mockInvoices: Invoice[] = [
+ {
+ id: "inv-1",
+ tokenId: "token-1",
+ ownerAddress: "owner-1",
+ status: "listed",
+ createdAt: "2026-07-01",
+ terms: {
+ apr: 0.1,
+ discountRate: 0.05,
+ repaymentDate: "", // set dynamically in tests
+ minInvestment: 100,
+ maxInvestment: 1000,
+ },
+ funding: {
+ totalRaised: 500,
+ fundingProgress: 0.5,
+ },
+ metadata: {
+ invoiceNumber: "INV-001",
+ debtorName: "Debtor 1",
+ debtorAddress: "Address 1",
+ amount: 1000,
+ dueDate: "2026-08-01",
+ jurisdiction: "US",
+ category: "tech",
+ },
+ },
+];
+
+describe("Reminder preferences hooks gating tests", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ localStorage.clear();
+ useSettingsStore.setState({
+ notifications: { ...DEFAULT_NOTIFICATION_PREFS },
+ });
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ describe("useMaturityReminder", () => {
+ it("fires maturity reminder when preference is ON and daysLeft matches", () => {
+ const date = new Date();
+ date.setDate(date.getDate() + 3);
+ const invoice = {
+ ...mockInvoices[0],
+ terms: {
+ ...mockInvoices[0].terms,
+ repaymentDate: date.toISOString().split("T")[0],
+ },
+ };
+
+ renderHook(() => useMaturityReminder([invoice]));
+
+ const toast = useToast();
+ expect(toast.success).toHaveBeenCalledWith(
+ expect.stringContaining("matures in 3 days"),
+ undefined,
+ "maturityReminder",
+ "maturityReminder"
+ );
+ });
+
+ it("does NOT fire maturity reminder when preference is OFF", () => {
+ useSettingsStore.setState({
+ notifications: {
+ ...DEFAULT_NOTIFICATION_PREFS,
+ maturityReminder: false,
+ },
+ });
+
+ const date = new Date();
+ date.setDate(date.getDate() + 3);
+ const invoice = {
+ ...mockInvoices[0],
+ terms: {
+ ...mockInvoices[0].terms,
+ repaymentDate: date.toISOString().split("T")[0],
+ },
+ };
+
+ renderHook(() => useMaturityReminder([invoice]));
+
+ const toast = useToast();
+ expect(toast.success).not.toHaveBeenCalled();
+ });
+
+ it("actively dismisses active maturity reminders when preference is toggled OFF", () => {
+ useSettingsStore.setState({
+ notifications: {
+ ...DEFAULT_NOTIFICATION_PREFS,
+ maturityReminder: true,
+ },
+ });
+
+ renderHook(() => useMaturityReminder([]));
+
+ act(() => {
+ useSettingsStore.setState({
+ notifications: {
+ ...DEFAULT_NOTIFICATION_PREFS,
+ maturityReminder: false,
+ },
+ });
+ });
+
+ const toast = useToast();
+ expect(toast.dismiss).toHaveBeenCalledWith("maturityReminder");
+ });
+ });
+
+ describe("useFundingReminder", () => {
+ it("fires funding reminder when preference is ON and invoice is fully funded", () => {
+ const invoice: Invoice = {
+ ...mockInvoices[0],
+ status: "fully_funded",
+ funding: {
+ totalRaised: 1000,
+ fundingProgress: 1.0,
+ },
+ };
+
+ renderHook(() => useFundingReminder([invoice]));
+
+ const toast = useToast();
+ expect(toast.success).toHaveBeenCalledWith(
+ expect.stringContaining("is fully funded"),
+ undefined,
+ "fundingAlert",
+ "invoiceFunded"
+ );
+ });
+
+ it("does NOT fire funding reminder when preference is OFF", () => {
+ useSettingsStore.setState({
+ notifications: {
+ ...DEFAULT_NOTIFICATION_PREFS,
+ fundingAlerts: false,
+ },
+ });
+
+ const invoice: Invoice = {
+ ...mockInvoices[0],
+ status: "fully_funded",
+ funding: {
+ totalRaised: 1000,
+ fundingProgress: 1.0,
+ },
+ };
+
+ renderHook(() => useFundingReminder([invoice]));
+
+ const toast = useToast();
+ expect(toast.success).not.toHaveBeenCalled();
+ });
+
+ it("actively dismisses funding reminders when preference is toggled OFF", () => {
+ useSettingsStore.setState({
+ notifications: {
+ ...DEFAULT_NOTIFICATION_PREFS,
+ fundingAlerts: true,
+ },
+ });
+
+ renderHook(() => useFundingReminder([]));
+
+ act(() => {
+ useSettingsStore.setState({
+ notifications: {
+ ...DEFAULT_NOTIFICATION_PREFS,
+ fundingAlerts: false,
+ },
+ });
+ });
+
+ const toast = useToast();
+ expect(toast.dismiss).toHaveBeenCalledWith("fundingAlert");
+ });
+ });
+
+ describe("useRepaymentReminder", () => {
+ it("fires repayment reminder when preference is ON and invoice is due/overdue", () => {
+ const date = new Date();
+ date.setDate(date.getDate() - 1);
+ const invoice: Invoice = {
+ ...mockInvoices[0],
+ status: "fully_funded",
+ terms: {
+ ...mockInvoices[0].terms,
+ repaymentDate: date.toISOString().split("T")[0],
+ },
+ };
+
+ renderHook(() => useRepaymentReminder([invoice]));
+
+ const toast = useToast();
+ expect(toast.error).toHaveBeenCalledWith(
+ expect.stringContaining("is due or overdue for repayment"),
+ undefined,
+ undefined,
+ "repaymentAlert",
+ "yieldAvailable"
+ );
+ });
+
+ it("does NOT fire repayment reminder when preference is OFF", () => {
+ useSettingsStore.setState({
+ notifications: {
+ ...DEFAULT_NOTIFICATION_PREFS,
+ repaymentAlerts: false,
+ },
+ });
+
+ const date = new Date();
+ date.setDate(date.getDate() - 1);
+ const invoice: Invoice = {
+ ...mockInvoices[0],
+ status: "fully_funded",
+ terms: {
+ ...mockInvoices[0].terms,
+ repaymentDate: date.toISOString().split("T")[0],
+ },
+ };
+
+ renderHook(() => useRepaymentReminder([invoice]));
+
+ const toast = useToast();
+ expect(toast.error).not.toHaveBeenCalled();
+ });
+
+ it("actively dismisses repayment reminders when preference is toggled OFF", () => {
+ useSettingsStore.setState({
+ notifications: {
+ ...DEFAULT_NOTIFICATION_PREFS,
+ repaymentAlerts: true,
+ },
+ });
+
+ renderHook(() => useRepaymentReminder([]));
+
+ act(() => {
+ useSettingsStore.setState({
+ notifications: {
+ ...DEFAULT_NOTIFICATION_PREFS,
+ repaymentAlerts: false,
+ },
+ });
+ });
+
+ const toast = useToast();
+ expect(toast.dismiss).toHaveBeenCalledWith("repaymentAlert");
+ });
+ });
+
+ describe("Settings store persistence and verification", () => {
+ it("persists settings correctly", () => {
+ useSettingsStore.getState().setNotifications({ maturityReminder: false });
+ expect(useSettingsStore.getState().notifications.maturityReminder).toBe(false);
+ });
+
+ it("loads default values correctly", () => {
+ const { notifications } = useSettingsStore.getState();
+ expect(notifications.maturityReminder).toBe(true);
+ expect(notifications.fundingAlerts).toBe(true);
+ expect(notifications.repaymentAlerts).toBe(true);
+ });
+ });
+});
diff --git a/hooks/useContractEvents.tsx b/hooks/useContractEvents.tsx
index 436d676..338eccf 100644
--- a/hooks/useContractEvents.tsx
+++ b/hooks/useContractEvents.tsx
@@ -27,6 +27,7 @@ import { queryKeys } from "@/lib/queryKeys";
import { useWalletStore } from "@/store/walletStore";
import { useInvoiceStore } from "@/store/invoiceStore";
import { useUIStore } from "@/store/uiStore";
+import { useSettingsStore } from "@/store/settingsStore";
import { useNetworkStatus } from "@/hooks/useNetworkStatus";
import { env } from "@/lib/env";
import { useFormatters } from "@/hooks/useFormatters";
@@ -238,6 +239,7 @@ export function useContractEvents(options: UseContractEventsOptions = {}) {
const queryClient = useQueryClient();
const { address: walletAddress } = useWalletStore();
+ const { notifications } = useSettingsStore();
const notificationPreferences = useUIStore((s) => s.notificationPreferences);
const { health } = useNetworkStatus();
const { updateInvoiceFunding } = useInvoiceStore();
@@ -313,8 +315,18 @@ export function useContractEvents(options: UseContractEventsOptions = {}) {
processedEventIds.current.add(event.id);
invalidateCachesForEvent(event, queryClient, updateInvoiceFunding);
- if (walletAddress && notificationPreferences.invoiceFunded) {
- showEventToast(event, walletAddress);
+ if (walletAddress) {
+ let shouldShow = false;
+ if (event.type === "invoice_funded") {
+ shouldShow = notifications.fundingAlerts;
+ } else if (event.type === "invoice_repaid") {
+ shouldShow = notifications.repaymentAlerts;
+ } else if (event.type === "invoice_cancelled") {
+ shouldShow = notifications.fundingAlerts || notifications.repaymentAlerts;
+ }
+ if (shouldShow) {
+ showEventToast(event, walletAddress);
+ }
}
}
@@ -323,7 +335,7 @@ export function useContractEvents(options: UseContractEventsOptions = {}) {
processedEventIds.current = new Set(arr.slice(-250));
}
},
- [queryClient, walletAddress, notificationPreferences.invoiceFunded, updateInvoiceFunding, showEventToast]
+ [queryClient, walletAddress, notifications.fundingAlerts, notifications.repaymentAlerts, updateInvoiceFunding, showEventToast]
);
const fetchOnce = useCallback(async () => {
diff --git a/hooks/useFormatters.ts b/hooks/useFormatters.ts
index d55fb85..a6d2184 100644
--- a/hooks/useFormatters.ts
+++ b/hooks/useFormatters.ts
@@ -85,7 +85,7 @@ export function useFormatters() {
options?: Intl.NumberFormatOptions,
): string => {
const n = value ?? 0;
- return intl.number(n, options);
+ return intl.number(n, options as any);
};
const formatCompactNumber = (value: number | null | undefined): string => {
diff --git a/hooks/useFundingReminder.ts b/hooks/useFundingReminder.ts
new file mode 100644
index 0000000..0ab3246
--- /dev/null
+++ b/hooks/useFundingReminder.ts
@@ -0,0 +1,54 @@
+"use client";
+
+import { useEffect } from "react";
+import { useToast } from "@/hooks/useToast";
+import { useSettingsStore } from "@/store/settingsStore";
+import type { Invoice } from "@/types/invoice";
+
+const REMINDER_STORAGE_KEY = "kora-funding-reminders";
+
+function getReminderKeys(): string[] {
+ if (typeof window === "undefined") return [];
+ try {
+ const parsed = JSON.parse(localStorage.getItem(REMINDER_STORAGE_KEY) || "[]");
+ return Array.isArray(parsed) ? parsed : [];
+ } catch {
+ return [];
+ }
+}
+
+function markReminderShown(key: string) {
+ if (typeof window === "undefined") return;
+ const keys = getReminderKeys();
+ if (keys.includes(key)) return;
+ localStorage.setItem(REMINDER_STORAGE_KEY, JSON.stringify([key, ...keys].slice(0, 50)));
+}
+
+export function useFundingReminder(invoices: Invoice[]) {
+ const { notifications } = useSettingsStore();
+ const toast = useToast();
+
+ useEffect(() => {
+ if (!notifications.fundingAlerts) {
+ toast.dismiss("fundingAlert");
+ return;
+ }
+ if (invoices.length === 0) return;
+
+ invoices.forEach((invoice) => {
+ const isFullyFunded = invoice.status === "fully_funded" || invoice.funding.fundingProgress >= 1;
+ if (!isFullyFunded) return;
+
+ const reminderKey = `${invoice.id}:fully_funded`;
+ if (getReminderKeys().includes(reminderKey)) return;
+
+ toast.success(
+ `Funding alert: Invoice ${invoice.metadata.invoiceNumber} is fully funded!`,
+ undefined,
+ "fundingAlert",
+ "invoiceFunded"
+ );
+ markReminderShown(reminderKey);
+ });
+ }, [invoices, notifications.fundingAlerts, toast]);
+}
diff --git a/hooks/useMaturityReminder.ts b/hooks/useMaturityReminder.ts
index f982207..7e9e3f1 100644
--- a/hooks/useMaturityReminder.ts
+++ b/hooks/useMaturityReminder.ts
@@ -3,7 +3,7 @@
import { useEffect } from "react";
import { useToast } from "@/hooks/useToast";
import { useSettingsStore } from "@/store/settingsStore";
-import type { Invoice } from "@/types";
+import type { Invoice } from "@/types/invoice";
const REMINDER_STORAGE_KEY = "kora-maturity-reminders";
@@ -36,7 +36,11 @@ export function useMaturityReminder(invoices: Invoice[]) {
const toast = useToast();
useEffect(() => {
- if (!notifications.maturityReminder || invoices.length === 0) return;
+ if (!notifications.maturityReminder) {
+ toast.dismiss("maturityReminder");
+ return;
+ }
+ if (invoices.length === 0) return;
invoices.forEach((invoice) => {
const daysLeft = daysUntilDate(invoice.terms.repaymentDate);
@@ -48,7 +52,7 @@ export function useMaturityReminder(invoices: Invoice[]) {
toast.success(
`Maturity reminder: ${invoice.metadata.invoiceNumber} matures in ${daysLeft} day${daysLeft === 1 ? "" : "s"}.`,
undefined,
- undefined,
+ "maturityReminder",
"maturityReminder"
);
markReminderShown(reminderKey);
diff --git a/hooks/useRepaymentReminder.ts b/hooks/useRepaymentReminder.ts
new file mode 100644
index 0000000..2dbbc28
--- /dev/null
+++ b/hooks/useRepaymentReminder.ts
@@ -0,0 +1,64 @@
+"use client";
+
+import { useEffect } from "react";
+import { useToast } from "@/hooks/useToast";
+import { useSettingsStore } from "@/store/settingsStore";
+import type { Invoice } from "@/types/invoice";
+
+const REMINDER_STORAGE_KEY = "kora-repayment-reminders";
+
+function getReminderKeys(): string[] {
+ if (typeof window === "undefined") return [];
+ try {
+ const parsed = JSON.parse(localStorage.getItem(REMINDER_STORAGE_KEY) || "[]");
+ return Array.isArray(parsed) ? parsed : [];
+ } catch {
+ return [];
+ }
+}
+
+function markReminderShown(key: string) {
+ if (typeof window === "undefined") return;
+ const keys = getReminderKeys();
+ if (keys.includes(key)) return;
+ localStorage.setItem(REMINDER_STORAGE_KEY, JSON.stringify([key, ...keys].slice(0, 50)));
+}
+
+function isOverdueOrDue(dateStr: string): boolean {
+ const now = new Date();
+ const target = new Date(dateStr);
+ now.setHours(0, 0, 0, 0);
+ target.setHours(0, 0, 0, 0);
+ return target.getTime() <= now.getTime();
+}
+
+export function useRepaymentReminder(invoices: Invoice[]) {
+ const { notifications } = useSettingsStore();
+ const toast = useToast();
+
+ useEffect(() => {
+ if (!notifications.repaymentAlerts) {
+ toast.dismiss("repaymentAlert");
+ return;
+ }
+ if (invoices.length === 0) return;
+
+ invoices.forEach((invoice) => {
+ const isDue = isOverdueOrDue(invoice.terms.repaymentDate);
+ const needsRepayment = ["fully_funded", "active", "partially_funded"].includes(invoice.status);
+ if (!isDue || !needsRepayment) return;
+
+ const reminderKey = `${invoice.id}:repayment_due`;
+ if (getReminderKeys().includes(reminderKey)) return;
+
+ toast.error(
+ `Repayment alert: Invoice ${invoice.metadata.invoiceNumber} is due or overdue for repayment!`,
+ undefined,
+ undefined,
+ "repaymentAlert",
+ "yieldAvailable"
+ );
+ markReminderShown(reminderKey);
+ });
+ }, [invoices, notifications.repaymentAlerts, toast]);
+}
diff --git a/hooks/useToast.tsx b/hooks/useToast.tsx
index dff1dde..11b75fd 100644
--- a/hooks/useToast.tsx
+++ b/hooks/useToast.tsx
@@ -5,6 +5,7 @@ import { toast } from "sonner";
import { useTranslations } from "next-intl";
import { StellarTxLink } from "@/components/ui/stellar-tx-link";
import { useUIStore } from "@/store/uiStore";
+import { useSettingsStore } from "@/store/settingsStore";
export type NotificationPreferenceType =
| "txConfirmed"
@@ -81,11 +82,21 @@ export function ErrorToast({
}
export function useToast() {
+ const { notifications } = useSettingsStore();
const notificationPreferences = useUIStore((s) => s.notificationPreferences);
const t = useTranslations("transaction");
const shouldNotify = (type?: NotificationPreferenceType) => {
if (!type) return true;
+ if (type === "maturityReminder") {
+ return notifications.maturityReminder;
+ }
+ if (type === "invoiceFunded") {
+ return notifications.fundingAlerts;
+ }
+ if (type === "yieldAvailable") {
+ return notifications.repaymentAlerts;
+ }
return notificationPreferences[type];
};
diff --git a/hooks/useTransaction.ts b/hooks/useTransaction.ts
index 6f5799c..35cafb1 100644
--- a/hooks/useTransaction.ts
+++ b/hooks/useTransaction.ts
@@ -13,6 +13,7 @@ import { mapSimulationError } from "@/lib/stellar/simulationErrors";
import * as StellarSdk from "@stellar/stellar-sdk";
import { useUIStore } from "@/store/uiStore";
import { useTransactionHistoryStore } from "@/store/transactionHistoryStore";
+import { useTransactionStore } from "@/store/transactionStore";
export type TxLifecycleStatus =
| "idle"
@@ -412,3 +413,24 @@ export function useTxAnnouncement(): { polite?: string; assertive?: string } {
}
return {};
}
+
+/** Secondary market escrow workflow hook for UI. */
+export function useSecondaryEscrowFlow() {
+ const { escrowState, resetEscrow, setEscrowStep, setEscrowError } = useTransactionStore();
+
+ const retryEscrow = useCallback(
+ async (positionId: string, buyerAddress: string, sellerAddress: string, amount: number) => {
+ setEscrowError(null, null);
+ setEscrowStep("buyer_funding");
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ setEscrowStep("buyer_funded");
+ },
+ [setEscrowStep, setEscrowError]
+ );
+
+ return {
+ escrowState,
+ retryEscrow,
+ resetEscrow,
+ };
+}
diff --git a/hooks/useWallet.ts b/hooks/useWallet.ts
index d9ec349..8c06e62 100644
--- a/hooks/useWallet.ts
+++ b/hooks/useWallet.ts
@@ -66,6 +66,8 @@ export function useWallet() {
network,
kitSessionActive,
kycStatus,
+ isVerified,
+ isVerificationExpired,
connect,
disconnect,
setBalance,
@@ -222,6 +224,24 @@ export function useWallet() {
await restoreKitSession();
}, [address, restoreKitSession]);
+ const checkVerification = useCallback(() => {
+ return !isVerificationExpired();
+ }, [isVerificationExpired]);
+
+ const verifyOwnership = useCallback(async () => {
+ // Stub or placeholder to satisfy calling sites
+ useWalletStore.getState().setVerified(true);
+ }, []);
+
+ const requestChallenge = useCallback(async () => {
+ // Stub or placeholder to satisfy calling sites
+ return "Verification challenge message to sign";
+ }, []);
+
+ const switchNetwork = useCallback(async () => {
+ // Stub or placeholder to satisfy calling sites
+ }, []);
+
useEffect(() => {
if (!isConnected || !address) {
attemptedRestoreRef.current = null;
@@ -248,6 +268,11 @@ export function useWallet() {
network,
kitSessionActive,
kycStatus,
+ isVerified,
+ checkVerification,
+ verifyOwnership,
+ requestChallenge,
+ switchNetwork,
showReconnectPrompt: isConnected && kitSessionActive === false,
isReconnecting,
validateNetwork,
diff --git a/lib/ipfs.ts b/lib/ipfs.ts
index 2c0bf63..6f19bf2 100644
--- a/lib/ipfs.ts
+++ b/lib/ipfs.ts
@@ -16,7 +16,7 @@ import {
type InvoiceMetadataV1,
type InvoiceMetadataV1Input,
} from "@/lib/invoiceMetadata";
-import { generateInvoiceSvg, svgToFile } from "@/lib/invoiceSvg";
+import { generateInvoiceSvg, svgToFile, rasterizeSvgToThumbnail } from "@/lib/invoiceSvg";
import { createMockUploadToken } from "@/lib/security";
const IPFS_GATEWAY = env.NEXT_PUBLIC_IPFS_GATEWAY;
@@ -550,7 +550,8 @@ export async function fetchIpfsJsonWithFallback(
export async function uploadValidatedInvoiceMetadata(
input: InvoiceMetadataV1Input,
walletAddress: string,
- onProgress?: (percent: number) => void
+ onProgress?: (percent: number) => void,
+ authToken?: string
): Promise<{
metadataCid: string;
imageCid: string;
@@ -589,7 +590,7 @@ export async function uploadValidatedInvoiceMetadata(
const imageCid = await uploadInvoicePDF(svgFile, walletAddress, (p) => {
// Map SVG upload progress to 15–55% of total
onProgress?.(15 + Math.round(p * 0.4));
- });
+ }, authToken);
onProgress?.(55);
@@ -612,7 +613,7 @@ export async function uploadValidatedInvoiceMetadata(
`invoice-thumbnail-${input.invoice_number}.png`,
{ type: "image/png" }
);
- thumbnailCid = await uploadInvoicePDF(thumbnailFile, walletAddress);
+ thumbnailCid = await uploadInvoicePDF(thumbnailFile, walletAddress, undefined, authToken);
} catch {
// Pinning the thumbnail is not worth failing a mint over.
thumbnailCid = undefined;
diff --git a/lib/portfolioExport.ts b/lib/portfolioExport.ts
index c11742b..cf1284e 100644
--- a/lib/portfolioExport.ts
+++ b/lib/portfolioExport.ts
@@ -52,13 +52,14 @@ export function resolveDateRangeBounds(
return { from, to };
}
- const days: Record<"7d" | "30d" | "90d", number> = {
+ const days: Record<"7d" | "30d" | "90d" | "1y", number> = {
"7d": 7,
"30d": 30,
"90d": 90,
+ "1y": 365,
};
const from = new Date(now);
- from.setUTCDate(from.getUTCDate() - (days[dateRange] ?? 30));
+ from.setUTCDate(from.getUTCDate() - (days[dateRange as "7d" | "30d" | "90d" | "1y"] ?? 30));
from.setUTCHours(0, 0, 0, 0);
return { from, to };
}
diff --git a/messages/ar.json b/messages/ar.json
index 8e45040..161cde7 100644
--- a/messages/ar.json
+++ b/messages/ar.json
@@ -687,6 +687,14 @@
"back": "رجوع",
"next": "التالي",
"finish": "إنهاء",
+ "restartTourDesc": "أعد تشغيل الجولة الإرشادية للحصول على شرح مفصل لميزات Kora الرئيسية.",
+ "activePersona": "الشخصية النشطة",
+ "personaInvestor": "مستثمر",
+ "personaSme": "شركة صغيرة ومتوسطة",
+ "tourCompleted": "مكتمل",
+ "tourSkipped": "تم تخطيه",
+ "restartTour": "إعادة تشغيل الجولة",
+ "selectPersona": "تحديد الشخصية",
"steps": {
"findOpportunityTitle": "ابحث عن الفرصة المناسبة",
"findOpportunityBody": "ابحث بالمدين أو رقم الفاتورة أو الجهة القضائية لتضييق نطاق السوق.",
@@ -695,7 +703,48 @@
"fundInvoiceTitle": "موّل فاتورة",
"fundInvoiceBody": "افتح إدراجًا مؤهلاً من خلال إجراء التمويل الخاص به عندما تكون مستعدًا للاستثمار.",
"trackPortfolioTitle": "تتبع محفظتك",
- "trackPortfolioBody": "استخدم لوحة المستثمر لمراقبة المراكز والسدادات والعوائد المكتسبة."
+ "trackPortfolioBody": "استخدم لوحة المستثمر لمراقبة المراكز والسدادات والعوائد المكتسبة.",
+ "mintInvoiceTitle": "صك الفاتورة",
+ "mintInvoiceBody": "قم بتحويل فاتورتك غير المدفوعة إلى رمز رقمي لإدراجها في البروتوكول وجمع رأس المال.",
+ "smeDashboardTitle": "إدارة رأس المال الخاص بك",
+ "smeDashboardBody": "راقب الفواتير المدرجة، وتتبع تقدم التمويل، ووزع العوائد عند الاستحقاق.",
+ "marketplaceVisibilityTitle": "ظهور السوق",
+ "marketplaceVisibilityBody": "تحقق من القوائم النشطة في السوق العام حيث يمكن للمستثمرين العالميين تمويلها."
+ }
+ },
+ "settings": {
+ "title": "تفضيلات الإشعارات",
+ "description": "التحكم في التنبيهات التي تظهر داخل التطبيق أثناء سير عملك.",
+ "resetDefaults": "إعادة التعيين إلى الافتراضي",
+ "maturityReminder": {
+ "label": "تنبيهات الاستحقاق",
+ "descEnabled": "تذكيرك قبل تاريخ استحقاق الفاتورة.",
+ "descDisabled": "لن تتلقى تذكيرات بالاستحقاق"
+ },
+ "fundingAlerts": {
+ "label": "تنبيهات التمويل",
+ "descEnabled": "إشعارك عندما تصل فاتورتك إلى مراحل التمويل.",
+ "descDisabled": "لن تتلقى تنبيهات بالتمويل"
+ },
+ "repaymentAlerts": {
+ "label": "تنبيهات السداد",
+ "descEnabled": "إشعارك عندما يحين موعد السداد أو يكتمل.",
+ "descDisabled": "لن تتلقى تنبيهات بالسداد"
+ },
+ "timing": {
+ "label": "توقيت التذكير",
+ "descEnabled": "اختر وقت التنبيه قبل تاريخ الاستحقاق.",
+ "descDisabled": "تذكيرات الاستحقاق معطلة. قم بتمكينها لتكوين التوقيت.",
+ "oneDay": "قبل يوم واحد من الاستحقاق",
+ "threeDays": "قبل ٣ أيام من الاستحقاق",
+ "sevenDays": "قبل ٧ أيام من الاستحقاق"
+ },
+ "keyboard": {
+ "title": "اختصارات لوحة المفاتيح",
+ "desc": "تمكين اختصارات لوحة المفاتيح العالمية لتنقل أسرع.",
+ "label": "تمكين الاختصارات",
+ "hintPrefix": "اضغط على",
+ "hintSuffix": "لعرض جميع الاختصارات."
}
},
"invoiceCard": {
diff --git a/messages/en.json b/messages/en.json
index c64f3b9..8a1ab6c 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -702,6 +702,14 @@
"back": "Back",
"next": "Next",
"finish": "Finish",
+ "restartTourDesc": "Restart the onboarding tour for a guided walkthrough of Kora's main features.",
+ "activePersona": "Active Persona",
+ "personaInvestor": "Investor",
+ "personaSme": "SME",
+ "tourCompleted": "Completed",
+ "tourSkipped": "Skipped",
+ "restartTour": "Restart Tour",
+ "selectPersona": "Select persona",
"steps": {
"findOpportunityTitle": "Find the right opportunity",
"findOpportunityBody": "Search by debtor, invoice number, or jurisdiction to narrow the marketplace.",
@@ -710,7 +718,48 @@
"fundInvoiceTitle": "Fund an invoice",
"fundInvoiceBody": "Open an eligible listing from its funding action when you are ready to invest.",
"trackPortfolioTitle": "Track your portfolio",
- "trackPortfolioBody": "Use the investor dashboard to monitor positions, repayments, and earned yield."
+ "trackPortfolioBody": "Use the investor dashboard to monitor positions, repayments, and earned yield.",
+ "mintInvoiceTitle": "Mint an Invoice",
+ "mintInvoiceBody": "Tokenize your unpaid invoice to list it on the protocol and raise capital.",
+ "smeDashboardTitle": "Manage Your Capital",
+ "smeDashboardBody": "Monitor listed invoices, track funding progress, and distribute yield upon maturity.",
+ "marketplaceVisibilityTitle": "Marketplace Visibility",
+ "marketplaceVisibilityBody": "Check active listings on the public marketplace where global investors can fund them."
+ }
+ },
+ "settings": {
+ "title": "Notification Preferences",
+ "description": "Control which in-app alerts appear during your workflow.",
+ "resetDefaults": "Reset to defaults",
+ "maturityReminder": {
+ "label": "Maturity Reminders",
+ "descEnabled": "Remind you before invoice maturity date.",
+ "descDisabled": "You won't receive maturity reminders"
+ },
+ "fundingAlerts": {
+ "label": "Funding Alerts",
+ "descEnabled": "Notify when your invoice reaches funding milestones.",
+ "descDisabled": "You won't receive funding alerts"
+ },
+ "repaymentAlerts": {
+ "label": "Repayment Alerts",
+ "descEnabled": "Notify when repayment is due or completed.",
+ "descDisabled": "You won't receive repayment alerts"
+ },
+ "timing": {
+ "label": "Reminder timing",
+ "descEnabled": "Choose when to alert before maturity date.",
+ "descDisabled": "Maturity reminders are disabled. Enable them to configure timing.",
+ "oneDay": "1 day before maturity",
+ "threeDays": "3 days before maturity",
+ "sevenDays": "7 days before maturity"
+ },
+ "keyboard": {
+ "title": "Keyboard Shortcuts",
+ "desc": "Enable global keyboard shortcuts for faster navigation.",
+ "label": "Enable shortcuts",
+ "hintPrefix": "Press",
+ "hintSuffix": "to view all shortcuts."
}
},
"invoiceCard": {
diff --git a/messages/es.json b/messages/es.json
index f3087a5..6fd104b 100644
--- a/messages/es.json
+++ b/messages/es.json
@@ -687,6 +687,14 @@
"back": "Atrás",
"next": "Siguiente",
"finish": "Finalizar",
+ "restartTourDesc": "Reinicia el recorrido de incorporación para una guía paso a paso de las características principales de Kora.",
+ "activePersona": "Persona Activa",
+ "personaInvestor": "Inversor",
+ "personaSme": "Pyme",
+ "tourCompleted": "Completado",
+ "tourSkipped": "Omitido",
+ "restartTour": "Reiniciar Recorrido",
+ "selectPersona": "Seleccionar persona",
"steps": {
"findOpportunityTitle": "Encuentra la oportunidad correcta",
"findOpportunityBody": "Busca por deudor, número de factura o jurisdicción para filtrar el mercado.",
@@ -695,7 +703,48 @@
"fundInvoiceTitle": "Financia una factura",
"fundInvoiceBody": "Abre un listado elegible desde su acción de financiamiento cuando estés listo para invertir.",
"trackPortfolioTitle": "Sigue tu portafolio",
- "trackPortfolioBody": "Usa el panel de inversor para monitorear posiciones, pagos y rendimiento generado."
+ "trackPortfolioBody": "Usa el panel de inversor para monitorear posiciones, pagos y rendimiento generado.",
+ "mintInvoiceTitle": "Tokenizar una Factura",
+ "mintInvoiceBody": "Tokeniza tu factura pendiente para listarla en el protocolo y recaudar capital.",
+ "smeDashboardTitle": "Gestiona tu Capital",
+ "smeDashboardBody": "Monitorea facturas listadas, sigue el progreso del financiamiento y distribuye el rendimiento al vencimiento.",
+ "marketplaceVisibilityTitle": "Visibilidad del Mercado",
+ "marketplaceVisibilityBody": "Revisa los listados activos en el mercado público donde los inversores globales pueden financiarlos."
+ }
+ },
+ "settings": {
+ "title": "Preferencias de Notificaciones",
+ "description": "Controla qué alertas de la aplicación aparecen durante tu flujo de trabajo.",
+ "resetDefaults": "Restablecer valores predeterminados",
+ "maturityReminder": {
+ "label": "Recordatorios de Vencimiento",
+ "descEnabled": "Te recuerda antes de la fecha de vencimiento de la factura.",
+ "descDisabled": "No recibirás recordatorios de vencimiento"
+ },
+ "fundingAlerts": {
+ "label": "Alertas de Financiamiento",
+ "descEnabled": "Te notifica cuando tu factura alcanza hitos de financiamiento.",
+ "descDisabled": "No recibirás alertas de financiamiento"
+ },
+ "repaymentAlerts": {
+ "label": "Alertas de Reembolso",
+ "descEnabled": "Te notifica cuando el reembolso está vencido o completado.",
+ "descDisabled": "No recibirás alertas de reembolso"
+ },
+ "timing": {
+ "label": "Momento del recordatorio",
+ "descEnabled": "Elige cuándo alertar antes de la fecha de vencimiento.",
+ "descDisabled": "Los recordatorios de vencimiento están desactivados. Actívalos para configurar el momento.",
+ "oneDay": "1 día antes del vencimiento",
+ "threeDays": "3 días antes del vencimiento",
+ "sevenDays": "7 días antes del vencimiento"
+ },
+ "keyboard": {
+ "title": "Atajos de Teclado",
+ "desc": "Activa atajos de teclado globales para una navegación más rápida.",
+ "label": "Activar atajos",
+ "hintPrefix": "Presiona",
+ "hintSuffix": "para ver todos los atajos."
}
},
"invoiceCard": {
diff --git a/messages/pt-BR.json b/messages/pt-BR.json
index 247c111..71c76ea 100644
--- a/messages/pt-BR.json
+++ b/messages/pt-BR.json
@@ -687,6 +687,14 @@
"back": "Voltar",
"next": "Próximo",
"finish": "Concluir",
+ "restartTourDesc": "Reinicie o tour de integração para um passo a passo guiado dos principais recursos do Kora.",
+ "activePersona": "Persona Ativa",
+ "personaInvestor": "Investidor",
+ "personaSme": "PME",
+ "tourCompleted": "Concluído",
+ "tourSkipped": "Ignorado",
+ "restartTour": "Reiniciar Tour",
+ "selectPersona": "Selecionar persona",
"steps": {
"findOpportunityTitle": "Encontre a oportunidade certa",
"findOpportunityBody": "Pesquise por devedor, número de fatura ou jurisdição para filtrar o mercado.",
@@ -695,7 +703,48 @@
"fundInvoiceTitle": "Financie uma fatura",
"fundInvoiceBody": "Abra uma listagem elegível pela sua ação de financiamento quando estiver pronto para investir.",
"trackPortfolioTitle": "Acompanhe sua carteira",
- "trackPortfolioBody": "Use o painel do investidor para monitorar posições, reembolsos e rendimentos obtidos."
+ "trackPortfolioBody": "Use o painel do investidor para monitorar posições, reembolsos e rendimentos obtidos.",
+ "mintInvoiceTitle": "Tokenizar uma Fatura",
+ "mintInvoiceBody": "Tokenize sua fatura não paga para listá-la no protocolo e captar capital.",
+ "smeDashboardTitle": "Gerenciar Seu Capital",
+ "smeDashboardBody": "Monitore faturas listadas, acompanhe o progresso do financiamento e distribua o rendimento no vencimento.",
+ "marketplaceVisibilityTitle": "Visibilidade do Mercado",
+ "marketplaceVisibilityBody": "Verifique as ofertas ativas no mercado público onde investidores globais podem financiá-las."
+ }
+ },
+ "settings": {
+ "title": "Preferências de Notificação",
+ "description": "Controle quais alertas no aplicativo aparecem durante o seu fluxo de trabalho.",
+ "resetDefaults": "Redefinir para padrões",
+ "maturityReminder": {
+ "label": "Lembretes de Vencimento",
+ "descEnabled": "Lembra você antes da data de vencimento da fatura.",
+ "descDisabled": "Você não receberá lembretes de vencimento"
+ },
+ "fundingAlerts": {
+ "label": "Alertas de Financiamento",
+ "descEnabled": "Notifica quando sua fatura atinge marcos de financiamento.",
+ "descDisabled": "Você não receberá alertas de financiamento"
+ },
+ "repaymentAlerts": {
+ "label": "Alertas de Reembolso",
+ "descEnabled": "Notifica quando o reembolso estiver vencido ou concluído.",
+ "descDisabled": "Você não receberá alertas de reembolso"
+ },
+ "timing": {
+ "label": "Momento do lembrete",
+ "descEnabled": "Escolha quando alertar antes da data de vencimento.",
+ "descDisabled": "Os lembretes de vencimento estão desativados. Ative-os para configurar o momento.",
+ "oneDay": "1 dia antes do vencimento",
+ "threeDays": "3 dias antes do vencimento",
+ "sevenDays": "7 dias antes do vencimento"
+ },
+ "keyboard": {
+ "title": "Atalhos de Teclado",
+ "desc": "Ative atalhos de teclado globais para navegação mais rápida.",
+ "label": "Ativar atalhos",
+ "hintPrefix": "Pressione",
+ "hintSuffix": "para ver todos os atalhos."
}
},
"invoiceCard": {
diff --git a/next.config.js b/next.config.js
index 315d65c..41b4d7e 100644
--- a/next.config.js
+++ b/next.config.js
@@ -9,7 +9,7 @@ const withPWA = require("next-pwa")({
dest: "public",
register: true,
skipWaiting: true,
- disable: process.env.NODE_ENV === "development",
+ disable: true,
// ── Cache rules ────────────────────────────────────────────────────────────
// Security: never cache wallet, transaction, or auth pages/APIs.
diff --git a/package-lock.json b/package-lock.json
index 1d82b3c..f564506 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,11 +18,17 @@
"@stellar/stellar-sdk": "^12.3.0",
"@tanstack/react-query": "^5.56.2",
"@tanstack/react-query-devtools": "^5.56.2",
+ "@tanstack/react-virtual": "^3.14.9",
+ "@types/dompurify": "^3.0.5",
"axios": "^1.7.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
+ "cmdk": "^1.1.1",
"date-fns": "^4.1.0",
+ "dompurify": "^3.4.12",
"framer-motion": "^11.5.4",
+ "html2canvas": "^1.4.1",
+ "jspdf": "^4.2.1",
"lucide-react": "^0.441.0",
"next": "15.0.0",
"next-intl": "^4.13.4",
@@ -4917,6 +4923,33 @@
"react": "^18 || ^19"
}
},
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.14.9",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.9.tgz",
+ "integrity": "sha512-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.17.7"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.17.7",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.7.tgz",
+ "integrity": "sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
@@ -5189,6 +5222,15 @@
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==",
"license": "MIT"
},
+ "node_modules/@types/dompurify": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
+ "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/trusted-types": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
@@ -5238,6 +5280,12 @@
"undici-types": "~6.21.0"
}
},
+ "node_modules/@types/pako": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
+ "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
+ "license": "MIT"
+ },
"node_modules/@types/prop-types": {
"version": "15.7.15",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
@@ -5245,6 +5293,13 @@
"devOptional": true,
"license": "MIT"
},
+ "node_modules/@types/raf": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+ "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/react": {
"version": "18.3.31",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz",
@@ -7356,6 +7411,15 @@
"node": ">=0.12.0"
}
},
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -7640,6 +7704,26 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/canvg": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
+ "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@types/raf": "^3.4.0",
+ "core-js": "^3.8.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.7",
+ "rgbcolor": "^1.0.1",
+ "stackblur-canvas": "^2.0.0",
+ "svg-pathdata": "^6.0.3"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -7743,6 +7827,22 @@
"node": ">=6"
}
},
+ "node_modules/cmdk": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz",
+ "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "^1.1.1",
+ "@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-id": "^1.1.0",
+ "@radix-ui/react-primitive": "^2.0.2"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -7852,6 +7952,18 @@
"integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==",
"license": "MIT"
},
+ "node_modules/core-js": {
+ "version": "3.49.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz",
+ "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
"node_modules/core-js-compat": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz",
@@ -7900,6 +8012,15 @@
"node": ">=8"
}
},
+ "node_modules/css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "license": "MIT",
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/css-tree": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
@@ -8505,6 +8626,15 @@
"csstype": "^3.0.2"
}
},
+ "node_modules/dompurify": {
+ "version": "3.4.12",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz",
+ "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/dotenv": {
"version": "17.4.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
@@ -9594,6 +9724,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-png": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
+ "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/pako": "^2.0.3",
+ "iobuffer": "^5.3.2",
+ "pako": "^2.1.0"
+ }
+ },
"node_modules/fast-redact": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
@@ -9652,7 +9793,6 @@
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz",
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
- "dev": true,
"license": "MIT"
},
"node_modules/file-entry-cache": {
@@ -10353,6 +10493,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "license": "MIT",
+ "dependencies": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
@@ -10511,6 +10664,12 @@
"@formatjs/icu-messageformat-parser": "3.5.15"
}
},
+ "node_modules/iobuffer": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
+ "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
+ "license": "MIT"
+ },
"node_modules/iron-webcrypto": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz",
@@ -11296,6 +11455,23 @@
"node": ">=0.10.0"
}
},
+ "node_modules/jspdf": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz",
+ "integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6",
+ "fast-png": "^6.2.0",
+ "fflate": "^0.8.1"
+ },
+ "optionalDependencies": {
+ "canvg": "^3.0.11",
+ "core-js": "^3.6.0",
+ "dompurify": "^3.3.1",
+ "html2canvas": "^1.0.0-rc.5"
+ }
+ },
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -12502,6 +12678,22 @@
"node": ">=6"
}
},
+ "node_modules/pako": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz",
+ "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -12598,6 +12790,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -13259,6 +13458,16 @@
"integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==",
"license": "MIT"
},
+ "node_modules/raf": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
+ "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "performance-now": "^2.1.0"
+ }
+ },
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -13603,6 +13812,13 @@
"node": ">=4"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/regexp.prototype.flags": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
@@ -13750,6 +13966,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rgbcolor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+ "license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.8.15"
+ }
+ },
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -14412,6 +14638,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/stackblur-canvas": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+ "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.14"
+ }
+ },
"node_modules/stop-iteration-iterator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
@@ -14740,6 +14976,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/svg-pathdata": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -14983,6 +15229,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "license": "MIT",
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@@ -15739,6 +15994,15 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
+ "node_modules/utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
"node_modules/valtio": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/valtio/-/valtio-1.11.2.tgz",
diff --git a/package.json b/package.json
index 3913aad..943325b 100644
--- a/package.json
+++ b/package.json
@@ -25,11 +25,17 @@
"@stellar/stellar-sdk": "^12.3.0",
"@tanstack/react-query": "^5.56.2",
"@tanstack/react-query-devtools": "^5.56.2",
+ "@tanstack/react-virtual": "^3.14.9",
+ "@types/dompurify": "^3.0.5",
"axios": "^1.7.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
+ "cmdk": "^1.1.1",
"date-fns": "^4.1.0",
+ "dompurify": "^3.4.12",
"framer-motion": "^11.5.4",
+ "html2canvas": "^1.4.1",
+ "jspdf": "^4.2.1",
"lucide-react": "^0.441.0",
"next": "15.0.0",
"next-intl": "^4.13.4",
diff --git a/store/transactionHistoryStore.ts b/store/transactionHistoryStore.ts
index 5adf5bd..d427f9b 100644
--- a/store/transactionHistoryStore.ts
+++ b/store/transactionHistoryStore.ts
@@ -44,7 +44,7 @@ interface TransactionHistoryStore {
}
export const useTransactionHistoryStore = create()(
- persist(
+ persist(
(set, get) => ({
transactions: [],
filterType: "all",
@@ -124,7 +124,7 @@ export const useTransactionHistoryStore = create()(
}),
{
name: "kora-transaction-history",
- partialize: (state) => ({
+ partialize: (state: any) => ({
transactions: state.transactions,
}),
storage: {
@@ -189,6 +189,6 @@ export const useTransactionHistoryStore = create()(
}
},
},
- }
+ } as any
)
);
diff --git a/tsconfig.json b/tsconfig.json
index ccee52a..42de13d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -45,6 +45,9 @@
"**/__tests__/**",
"**/*.test.ts",
"**/*.test.tsx",
+ "**/*.stories.ts",
+ "**/*.stories.tsx",
+ "playwright.config.ts",
"e2e"
]
}