@plasius/economy 0.3.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -5
- package/dist/index.cjs +708 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +204 -1
- package/dist/index.d.ts +204 -1
- package/dist/index.js +692 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
EARLY_BACKER_SETTLEMENT_POLICY_V2: () => EARLY_BACKER_SETTLEMENT_POLICY_V2,
|
|
29
29
|
ECONOMY_CONTRACT_VERSION: () => ECONOMY_CONTRACT_VERSION,
|
|
30
30
|
EconomyError: () => EconomyError,
|
|
31
|
+
MODULE_ALLOWANCE_PURPOSE: () => MODULE_ALLOWANCE_PURPOSE,
|
|
31
32
|
PRE_UTILITY_BACKER_COHORT: () => PRE_UTILITY_BACKER_COHORT,
|
|
32
33
|
TOKEN_SUBUNITS_MAX: () => TOKEN_SUBUNITS_MAX,
|
|
33
34
|
TOKEN_SUBUNITS_MIN: () => TOKEN_SUBUNITS_MIN,
|
|
@@ -52,6 +53,11 @@ __export(index_exports, {
|
|
|
52
53
|
assertFxSnapshot: () => assertFxSnapshot,
|
|
53
54
|
assertGameplayAllocation: () => assertGameplayAllocation,
|
|
54
55
|
assertJournalChainHead: () => assertJournalChainHead,
|
|
56
|
+
assertModuleAllowance: () => assertModuleAllowance,
|
|
57
|
+
assertModulePurchaseReceipt: () => assertModulePurchaseReceipt,
|
|
58
|
+
assertModulePurchaseReconciliationObservation: () => assertModulePurchaseReconciliationObservation,
|
|
59
|
+
assertModuleSpendHold: () => assertModuleSpendHold,
|
|
60
|
+
assertModuleSpendQuote: () => assertModuleSpendQuote,
|
|
55
61
|
assertNonNegativeAccounts: () => assertNonNegativeAccounts,
|
|
56
62
|
assertOpenPurchaseIntent: () => assertOpenPurchaseIntent,
|
|
57
63
|
assertPaidLotLifecycle: () => assertPaidLotLifecycle,
|
|
@@ -95,10 +101,15 @@ __export(index_exports, {
|
|
|
95
101
|
calculateRollingPurchaseCapUsage: () => calculateRollingPurchaseCapUsage,
|
|
96
102
|
canUseSourceLot: () => canUseSourceLot,
|
|
97
103
|
canonicalTransactionPayload: () => canonicalTransactionPayload,
|
|
104
|
+
closeModuleAllowance: () => closeModuleAllowance,
|
|
98
105
|
convertRewardPayout: () => convertRewardPayout,
|
|
99
106
|
createEarlyBackerRetentionFromPaidLot: () => createEarlyBackerRetentionFromPaidLot,
|
|
100
107
|
createGameplayAllocation: () => createGameplayAllocation,
|
|
101
108
|
createInitialSourceLotSnapshot: () => createInitialSourceLotSnapshot,
|
|
109
|
+
createModuleAllowance: () => createModuleAllowance,
|
|
110
|
+
createModulePurchaseReceipt: () => createModulePurchaseReceipt,
|
|
111
|
+
createModuleSpendHold: () => createModuleSpendHold,
|
|
112
|
+
createModuleSpendQuote: () => createModuleSpendQuote,
|
|
102
113
|
createPaidLotLifecycle: () => createPaidLotLifecycle,
|
|
103
114
|
createProjectionSnapshot: () => createProjectionSnapshot,
|
|
104
115
|
createPurchaseIntentLifecycle: () => createPurchaseIntentLifecycle,
|
|
@@ -112,6 +123,7 @@ __export(index_exports, {
|
|
|
112
123
|
economyAssert: () => economyAssert,
|
|
113
124
|
evaluateEarlyBacker: () => evaluateEarlyBacker,
|
|
114
125
|
evaluateEarlyBackerBySettlementV2: () => evaluateEarlyBackerBySettlementV2,
|
|
126
|
+
fundModuleAllowance: () => fundModuleAllowance,
|
|
115
127
|
isWholeTokenAmount: () => isWholeTokenAmount,
|
|
116
128
|
mapBitLabsConversionState: () => mapBitLabsConversionState,
|
|
117
129
|
parseGbpMinorUnits: () => parseGbpMinorUnits,
|
|
@@ -119,12 +131,16 @@ __export(index_exports, {
|
|
|
119
131
|
parseTokenSubunits: () => parseTokenSubunits,
|
|
120
132
|
rebuildBalanceProjection: () => rebuildBalanceProjection,
|
|
121
133
|
reclaimGameplayAllocation: () => reclaimGameplayAllocation,
|
|
134
|
+
reclaimModuleAllowance: () => reclaimModuleAllowance,
|
|
135
|
+
reconcileModulePurchase: () => reconcileModulePurchase,
|
|
122
136
|
reducePaidLotLifecycleEvents: () => reducePaidLotLifecycleEvents,
|
|
123
137
|
reducePurchaseIntentTransitions: () => reducePurchaseIntentTransitions,
|
|
138
|
+
releaseModuleSpendHold: () => releaseModuleSpendHold,
|
|
124
139
|
reserveRollingPurchaseCaps: () => reserveRollingPurchaseCaps,
|
|
125
140
|
selectSourceLots: () => selectSourceLots,
|
|
126
141
|
serializeGbpMinorUnits: () => serializeGbpMinorUnits,
|
|
127
142
|
serializeTokenSubunits: () => serializeTokenSubunits,
|
|
143
|
+
settleModuleSpendHold: () => settleModuleSpendHold,
|
|
128
144
|
sortStringRecord: () => sortStringRecord,
|
|
129
145
|
subunitsToWholeTokens: () => subunitsToWholeTokens,
|
|
130
146
|
sumTokenSubunits: () => sumTokenSubunits,
|
|
@@ -1682,6 +1698,682 @@ function selectSourceLots(lots, requestedAmount, context) {
|
|
|
1682
1698
|
);
|
|
1683
1699
|
}
|
|
1684
1700
|
|
|
1701
|
+
// src/module-allowances.ts
|
|
1702
|
+
var MODULE_ALLOWANCE_PURPOSE = "junior-coder-module-entitlement";
|
|
1703
|
+
function assertWholePositive2(amount, label) {
|
|
1704
|
+
const parsed = parseTokenSubunits(amount);
|
|
1705
|
+
economyAssert(parsed > 0n, "INVALID_AMOUNT", `${label} must be positive`);
|
|
1706
|
+
economyAssert(
|
|
1707
|
+
isWholeTokenAmount(parsed),
|
|
1708
|
+
"AMOUNT_NOT_WHOLE_TOKEN",
|
|
1709
|
+
`${label} must contain whole Tokens`
|
|
1710
|
+
);
|
|
1711
|
+
return parsed;
|
|
1712
|
+
}
|
|
1713
|
+
function assertPositiveSlices(slices, expectedAmount, label) {
|
|
1714
|
+
economyAssert(slices.length > 0, "INVALID_CONTRACT", `${label} are required`);
|
|
1715
|
+
const lotIds = /* @__PURE__ */ new Set();
|
|
1716
|
+
for (const slice of slices) {
|
|
1717
|
+
assertEconomyIdentifier(slice.lotId, "lotId");
|
|
1718
|
+
economyAssert(
|
|
1719
|
+
!lotIds.has(slice.lotId),
|
|
1720
|
+
"DUPLICATE_IDENTIFIER",
|
|
1721
|
+
`${label} must not repeat a lot`
|
|
1722
|
+
);
|
|
1723
|
+
lotIds.add(slice.lotId);
|
|
1724
|
+
economyAssert(
|
|
1725
|
+
parseTokenSubunits(slice.amount) > 0n,
|
|
1726
|
+
"INVALID_AMOUNT",
|
|
1727
|
+
`${label} amounts must be positive`
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
economyAssert(
|
|
1731
|
+
sumTokenSubunits(slices.map((slice) => slice.amount)) === expectedAmount,
|
|
1732
|
+
"INVALID_CONTRACT",
|
|
1733
|
+
`${label} must sum to the operation amount`
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
function sumSlicesByLot(slices) {
|
|
1737
|
+
const totals = /* @__PURE__ */ new Map();
|
|
1738
|
+
for (const slice of slices) {
|
|
1739
|
+
assertEconomyIdentifier(slice.lotId, "lotId");
|
|
1740
|
+
const amount = parseTokenSubunits(slice.amount);
|
|
1741
|
+
economyAssert(amount > 0n, "INVALID_AMOUNT", "Lot slices must be positive");
|
|
1742
|
+
totals.set(slice.lotId, (totals.get(slice.lotId) ?? 0n) + amount);
|
|
1743
|
+
}
|
|
1744
|
+
return totals;
|
|
1745
|
+
}
|
|
1746
|
+
function assertSha256Reference(value, label) {
|
|
1747
|
+
economyAssert(
|
|
1748
|
+
typeof value === "string" && /^sha256:[a-f0-9]{64}$/u.test(value),
|
|
1749
|
+
"INVALID_CONTRACT",
|
|
1750
|
+
`${label} must be a canonical SHA-256 reference`
|
|
1751
|
+
);
|
|
1752
|
+
}
|
|
1753
|
+
function assertUpdatedAt(current, occurredAt, label) {
|
|
1754
|
+
economyAssert(
|
|
1755
|
+
parseIsoTimestamp(occurredAt) >= parseIsoTimestamp(current),
|
|
1756
|
+
"INVALID_TIME_WINDOW",
|
|
1757
|
+
`${label} cannot precede the current state`
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
function assertModuleAllowance(allowance) {
|
|
1761
|
+
economyAssert(
|
|
1762
|
+
allowance.schemaVersion === ECONOMY_CONTRACT_VERSION && allowance.purpose === MODULE_ALLOWANCE_PURPOSE,
|
|
1763
|
+
"INVALID_CONTRACT",
|
|
1764
|
+
"Unsupported Module Allowance contract"
|
|
1765
|
+
);
|
|
1766
|
+
assertEconomyIdentifier(allowance.allowanceId, "allowanceId");
|
|
1767
|
+
assertEconomyIdentifier(allowance.householdId, "householdId");
|
|
1768
|
+
assertEconomyIdentifier(allowance.hostWalletId, "hostWalletId");
|
|
1769
|
+
assertEconomyIdentifier(allowance.allowanceWalletId, "allowanceWalletId");
|
|
1770
|
+
assertEconomyIdentifier(allowance.childAccountId, "childAccountId");
|
|
1771
|
+
economyAssert(
|
|
1772
|
+
allowance.hostWalletId !== allowance.allowanceWalletId,
|
|
1773
|
+
"INVALID_CONTRACT",
|
|
1774
|
+
"Host and Module Allowance wallets must be distinct"
|
|
1775
|
+
);
|
|
1776
|
+
economyAssert(
|
|
1777
|
+
allowance.status === "active" || allowance.status === "closed",
|
|
1778
|
+
"INVALID_CONTRACT",
|
|
1779
|
+
"Module Allowance status is unsupported"
|
|
1780
|
+
);
|
|
1781
|
+
const createdAt = parseIsoTimestamp(allowance.createdAt);
|
|
1782
|
+
const updatedAt = parseIsoTimestamp(allowance.updatedAt);
|
|
1783
|
+
economyAssert(
|
|
1784
|
+
updatedAt >= createdAt,
|
|
1785
|
+
"INVALID_TIME_WINDOW",
|
|
1786
|
+
"Module Allowance update cannot precede creation"
|
|
1787
|
+
);
|
|
1788
|
+
economyAssert(
|
|
1789
|
+
Number.isSafeInteger(allowance.version) && allowance.version >= 1,
|
|
1790
|
+
"INVALID_CONTRACT",
|
|
1791
|
+
"Module Allowance version must be a positive safe integer"
|
|
1792
|
+
);
|
|
1793
|
+
const allocated = parseTokenSubunits(allowance.allocatedAmount);
|
|
1794
|
+
const available = parseTokenSubunits(allowance.availableAmount);
|
|
1795
|
+
const held = parseTokenSubunits(allowance.heldAmount);
|
|
1796
|
+
const spent = parseTokenSubunits(allowance.spentAmount);
|
|
1797
|
+
const reclaimed = parseTokenSubunits(allowance.reclaimedAmount);
|
|
1798
|
+
economyAssert(
|
|
1799
|
+
allocated > 0n && [allocated, available, held, spent, reclaimed].every(
|
|
1800
|
+
(amount) => amount >= 0n && isWholeTokenAmount(amount)
|
|
1801
|
+
),
|
|
1802
|
+
"AMOUNT_NOT_WHOLE_TOKEN",
|
|
1803
|
+
"Module Allowance amounts must be non-negative whole Tokens"
|
|
1804
|
+
);
|
|
1805
|
+
economyAssert(
|
|
1806
|
+
allocated === available + held + spent + reclaimed,
|
|
1807
|
+
"INVALID_CONTRACT",
|
|
1808
|
+
"Module Allowance buckets must equal its lifetime allocation"
|
|
1809
|
+
);
|
|
1810
|
+
economyAssert(
|
|
1811
|
+
allowance.status !== "closed" || available === 0n && held === 0n,
|
|
1812
|
+
"INVALID_CONTRACT",
|
|
1813
|
+
"A closed Module Allowance cannot retain available or held value"
|
|
1814
|
+
);
|
|
1815
|
+
const fundedByLot = sumSlicesByLot(allowance.fundingSlices);
|
|
1816
|
+
const reclaimedByLot = sumSlicesByLot(allowance.reclaimedSlices);
|
|
1817
|
+
economyAssert(
|
|
1818
|
+
sumTokenSubunits(allowance.fundingSlices.map((slice) => slice.amount)) === allocated,
|
|
1819
|
+
"INVALID_CONTRACT",
|
|
1820
|
+
"Module Allowance funding provenance must equal allocated value"
|
|
1821
|
+
);
|
|
1822
|
+
economyAssert(
|
|
1823
|
+
sumTokenSubunits(allowance.reclaimedSlices.map((slice) => slice.amount)) === reclaimed,
|
|
1824
|
+
"INVALID_CONTRACT",
|
|
1825
|
+
"Module Allowance reclaim provenance must equal reclaimed value"
|
|
1826
|
+
);
|
|
1827
|
+
for (const [lotId, amount] of reclaimedByLot) {
|
|
1828
|
+
economyAssert(
|
|
1829
|
+
amount <= (fundedByLot.get(lotId) ?? 0n),
|
|
1830
|
+
"SOURCE_LOT_RESTRICTED",
|
|
1831
|
+
"Reclaimed provenance cannot exceed Module Allowance funding"
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
function createModuleAllowance(input) {
|
|
1836
|
+
const amount = assertWholePositive2(input.amount, "Module Allowance amount");
|
|
1837
|
+
assertPositiveSlices(input.fundingSlices, amount, "Funding slices");
|
|
1838
|
+
const allowance = {
|
|
1839
|
+
schemaVersion: ECONOMY_CONTRACT_VERSION,
|
|
1840
|
+
purpose: MODULE_ALLOWANCE_PURPOSE,
|
|
1841
|
+
allowanceId: input.allowanceId,
|
|
1842
|
+
householdId: input.householdId,
|
|
1843
|
+
hostWalletId: input.hostWalletId,
|
|
1844
|
+
allowanceWalletId: input.allowanceWalletId,
|
|
1845
|
+
childAccountId: input.childAccountId,
|
|
1846
|
+
status: "active",
|
|
1847
|
+
allocatedAmount: input.amount,
|
|
1848
|
+
availableAmount: input.amount,
|
|
1849
|
+
heldAmount: serializeTokenSubunits(0n),
|
|
1850
|
+
spentAmount: serializeTokenSubunits(0n),
|
|
1851
|
+
reclaimedAmount: serializeTokenSubunits(0n),
|
|
1852
|
+
fundingSlices: input.fundingSlices,
|
|
1853
|
+
reclaimedSlices: [],
|
|
1854
|
+
version: 1,
|
|
1855
|
+
createdAt: input.occurredAt,
|
|
1856
|
+
updatedAt: input.occurredAt
|
|
1857
|
+
};
|
|
1858
|
+
assertModuleAllowance(allowance);
|
|
1859
|
+
return allowance;
|
|
1860
|
+
}
|
|
1861
|
+
function fundModuleAllowance(allowance, input) {
|
|
1862
|
+
assertModuleAllowance(allowance);
|
|
1863
|
+
economyAssert(
|
|
1864
|
+
allowance.status === "active" && allowance.version === input.expectedVersion,
|
|
1865
|
+
"INVALID_CONTRACT",
|
|
1866
|
+
"Module Allowance is closed or its optimistic version is stale"
|
|
1867
|
+
);
|
|
1868
|
+
assertUpdatedAt(allowance.updatedAt, input.occurredAt, "Funding time");
|
|
1869
|
+
const amount = assertWholePositive2(input.amount, "Module Allowance funding");
|
|
1870
|
+
assertPositiveSlices(input.fundingSlices, amount, "Funding slices");
|
|
1871
|
+
const next = {
|
|
1872
|
+
...allowance,
|
|
1873
|
+
allocatedAmount: serializeTokenSubunits(
|
|
1874
|
+
parseTokenSubunits(allowance.allocatedAmount) + amount
|
|
1875
|
+
),
|
|
1876
|
+
availableAmount: serializeTokenSubunits(
|
|
1877
|
+
parseTokenSubunits(allowance.availableAmount) + amount
|
|
1878
|
+
),
|
|
1879
|
+
fundingSlices: [...allowance.fundingSlices, ...input.fundingSlices],
|
|
1880
|
+
version: allowance.version + 1,
|
|
1881
|
+
updatedAt: input.occurredAt
|
|
1882
|
+
};
|
|
1883
|
+
assertModuleAllowance(next);
|
|
1884
|
+
return next;
|
|
1885
|
+
}
|
|
1886
|
+
function reclaimModuleAllowance(allowance, input) {
|
|
1887
|
+
assertModuleAllowance(allowance);
|
|
1888
|
+
economyAssert(
|
|
1889
|
+
allowance.status === "active" && allowance.version === input.expectedVersion,
|
|
1890
|
+
"INVALID_CONTRACT",
|
|
1891
|
+
"Module Allowance is closed or its optimistic version is stale"
|
|
1892
|
+
);
|
|
1893
|
+
assertUpdatedAt(allowance.updatedAt, input.occurredAt, "Reclaim time");
|
|
1894
|
+
const amount = assertWholePositive2(input.amount, "Module Allowance reclaim");
|
|
1895
|
+
economyAssert(
|
|
1896
|
+
amount <= parseTokenSubunits(allowance.availableAmount),
|
|
1897
|
+
"INSUFFICIENT_BALANCE",
|
|
1898
|
+
"Cannot reclaim held, spent, or unavailable Module Allowance value"
|
|
1899
|
+
);
|
|
1900
|
+
assertPositiveSlices(input.sourceSlices, amount, "Reclaim slices");
|
|
1901
|
+
const fundedLotIds = new Set(
|
|
1902
|
+
allowance.fundingSlices.map((slice) => slice.lotId)
|
|
1903
|
+
);
|
|
1904
|
+
economyAssert(
|
|
1905
|
+
input.sourceSlices.every((slice) => fundedLotIds.has(slice.lotId)),
|
|
1906
|
+
"SOURCE_LOT_RESTRICTED",
|
|
1907
|
+
"Reclaim slices must originate from the Module Allowance"
|
|
1908
|
+
);
|
|
1909
|
+
const next = {
|
|
1910
|
+
...allowance,
|
|
1911
|
+
availableAmount: serializeTokenSubunits(
|
|
1912
|
+
parseTokenSubunits(allowance.availableAmount) - amount
|
|
1913
|
+
),
|
|
1914
|
+
reclaimedAmount: serializeTokenSubunits(
|
|
1915
|
+
parseTokenSubunits(allowance.reclaimedAmount) + amount
|
|
1916
|
+
),
|
|
1917
|
+
reclaimedSlices: [...allowance.reclaimedSlices, ...input.sourceSlices],
|
|
1918
|
+
version: allowance.version + 1,
|
|
1919
|
+
updatedAt: input.occurredAt
|
|
1920
|
+
};
|
|
1921
|
+
assertModuleAllowance(next);
|
|
1922
|
+
return next;
|
|
1923
|
+
}
|
|
1924
|
+
function closeModuleAllowance(allowance, input) {
|
|
1925
|
+
assertModuleAllowance(allowance);
|
|
1926
|
+
economyAssert(
|
|
1927
|
+
allowance.status === "active" && allowance.version === input.expectedVersion,
|
|
1928
|
+
"INVALID_CONTRACT",
|
|
1929
|
+
"Module Allowance is closed or its optimistic version is stale"
|
|
1930
|
+
);
|
|
1931
|
+
assertUpdatedAt(allowance.updatedAt, input.occurredAt, "Closure time");
|
|
1932
|
+
economyAssert(
|
|
1933
|
+
parseTokenSubunits(allowance.availableAmount) === 0n && parseTokenSubunits(allowance.heldAmount) === 0n,
|
|
1934
|
+
"INVALID_CONTRACT",
|
|
1935
|
+
"Available value must be reclaimed and holds resolved before closure"
|
|
1936
|
+
);
|
|
1937
|
+
const next = {
|
|
1938
|
+
...allowance,
|
|
1939
|
+
status: "closed",
|
|
1940
|
+
version: allowance.version + 1,
|
|
1941
|
+
updatedAt: input.occurredAt
|
|
1942
|
+
};
|
|
1943
|
+
assertModuleAllowance(next);
|
|
1944
|
+
return next;
|
|
1945
|
+
}
|
|
1946
|
+
function assertModuleSpendQuote(quote) {
|
|
1947
|
+
economyAssert(
|
|
1948
|
+
quote.schemaVersion === ECONOMY_CONTRACT_VERSION,
|
|
1949
|
+
"INVALID_CONTRACT",
|
|
1950
|
+
"Unsupported module-spend quote contract version"
|
|
1951
|
+
);
|
|
1952
|
+
assertEconomyIdentifier(quote.quoteId, "quoteId");
|
|
1953
|
+
assertEconomyIdentifier(quote.allowanceId, "allowanceId");
|
|
1954
|
+
assertEconomyIdentifier(quote.householdId, "householdId");
|
|
1955
|
+
assertEconomyIdentifier(quote.guardianAccountId, "guardianAccountId");
|
|
1956
|
+
assertEconomyIdentifier(quote.childAccountId, "childAccountId");
|
|
1957
|
+
assertEconomyIdentifier(quote.moduleVersionId, "moduleVersionId");
|
|
1958
|
+
assertEconomyIdentifier(quote.catalogVersion, "catalogVersion");
|
|
1959
|
+
assertEconomyIdentifier(
|
|
1960
|
+
quote.requirementsManifestVersion,
|
|
1961
|
+
"requirementsManifestVersion"
|
|
1962
|
+
);
|
|
1963
|
+
assertSha256Reference(
|
|
1964
|
+
quote.requirementsManifestHash,
|
|
1965
|
+
"Requirements manifest hash"
|
|
1966
|
+
);
|
|
1967
|
+
assertWholePositive2(quote.amount, "Module price");
|
|
1968
|
+
const acknowledgedAt = parseIsoTimestamp(quote.requirementsAcknowledgedAt);
|
|
1969
|
+
const issuedAt = parseIsoTimestamp(quote.issuedAt);
|
|
1970
|
+
const expiresAt = parseIsoTimestamp(quote.expiresAt);
|
|
1971
|
+
economyAssert(
|
|
1972
|
+
acknowledgedAt <= issuedAt && expiresAt > issuedAt,
|
|
1973
|
+
"INVALID_TIME_WINDOW",
|
|
1974
|
+
"Requirements must be acknowledged before a bounded quote is issued"
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1977
|
+
function createModuleSpendQuote(input) {
|
|
1978
|
+
const quote = {
|
|
1979
|
+
schemaVersion: ECONOMY_CONTRACT_VERSION,
|
|
1980
|
+
...input
|
|
1981
|
+
};
|
|
1982
|
+
assertModuleSpendQuote(quote);
|
|
1983
|
+
return quote;
|
|
1984
|
+
}
|
|
1985
|
+
function assertModuleSpendHold(hold) {
|
|
1986
|
+
economyAssert(
|
|
1987
|
+
hold.schemaVersion === ECONOMY_CONTRACT_VERSION,
|
|
1988
|
+
"INVALID_CONTRACT",
|
|
1989
|
+
"Unsupported module-spend hold contract version"
|
|
1990
|
+
);
|
|
1991
|
+
assertEconomyIdentifier(hold.holdId, "holdId");
|
|
1992
|
+
assertEconomyIdentifier(hold.quoteId, "quoteId");
|
|
1993
|
+
assertEconomyIdentifier(hold.allowanceId, "allowanceId");
|
|
1994
|
+
assertEconomyIdentifier(hold.householdId, "householdId");
|
|
1995
|
+
assertEconomyIdentifier(hold.guardianAccountId, "guardianAccountId");
|
|
1996
|
+
assertEconomyIdentifier(hold.childAccountId, "childAccountId");
|
|
1997
|
+
assertEconomyIdentifier(hold.moduleVersionId, "moduleVersionId");
|
|
1998
|
+
assertEconomyIdentifier(
|
|
1999
|
+
hold.requirementsManifestVersion,
|
|
2000
|
+
"requirementsManifestVersion"
|
|
2001
|
+
);
|
|
2002
|
+
assertSha256Reference(
|
|
2003
|
+
hold.requirementsManifestHash,
|
|
2004
|
+
"Requirements manifest hash"
|
|
2005
|
+
);
|
|
2006
|
+
assertEconomyIdentifier(hold.holdIdempotencyKey, "holdIdempotencyKey");
|
|
2007
|
+
const amount = assertWholePositive2(hold.amount, "Module hold amount");
|
|
2008
|
+
assertPositiveSlices(hold.sourceSlices, amount, "Hold source slices");
|
|
2009
|
+
economyAssert(
|
|
2010
|
+
["held", "settled", "released"].includes(hold.status),
|
|
2011
|
+
"INVALID_CONTRACT",
|
|
2012
|
+
"Module hold status is unsupported"
|
|
2013
|
+
);
|
|
2014
|
+
economyAssert(
|
|
2015
|
+
Number.isSafeInteger(hold.version) && hold.version >= 1,
|
|
2016
|
+
"INVALID_CONTRACT",
|
|
2017
|
+
"Module hold version must be a positive safe integer"
|
|
2018
|
+
);
|
|
2019
|
+
const createdAt = parseIsoTimestamp(hold.createdAt);
|
|
2020
|
+
const expiresAt = parseIsoTimestamp(hold.expiresAt);
|
|
2021
|
+
const updatedAt = parseIsoTimestamp(hold.updatedAt);
|
|
2022
|
+
economyAssert(
|
|
2023
|
+
expiresAt > createdAt && updatedAt >= createdAt,
|
|
2024
|
+
"INVALID_TIME_WINDOW",
|
|
2025
|
+
"Module hold requires a bounded lifetime and ordered updates"
|
|
2026
|
+
);
|
|
2027
|
+
if (hold.status === "held") {
|
|
2028
|
+
economyAssert(
|
|
2029
|
+
hold.entitlementId === void 0 && hold.settlementTransactionId === void 0 && hold.releaseTransactionId === void 0 && hold.settlementIdempotencyKey === void 0 && hold.releaseIdempotencyKey === void 0,
|
|
2030
|
+
"INVALID_CONTRACT",
|
|
2031
|
+
"An unsettled hold cannot claim an entitlement or final transaction"
|
|
2032
|
+
);
|
|
2033
|
+
} else if (hold.status === "settled") {
|
|
2034
|
+
economyAssert(
|
|
2035
|
+
hold.entitlementId !== void 0 && hold.settlementTransactionId !== void 0 && hold.settlementIdempotencyKey !== void 0 && hold.releaseTransactionId === void 0 && hold.releaseIdempotencyKey === void 0,
|
|
2036
|
+
"INVALID_CONTRACT",
|
|
2037
|
+
"A settled hold requires entitlement and transaction evidence"
|
|
2038
|
+
);
|
|
2039
|
+
assertEconomyIdentifier(hold.entitlementId, "entitlementId");
|
|
2040
|
+
assertEconomyIdentifier(
|
|
2041
|
+
hold.settlementTransactionId,
|
|
2042
|
+
"settlementTransactionId"
|
|
2043
|
+
);
|
|
2044
|
+
assertEconomyIdentifier(
|
|
2045
|
+
hold.settlementIdempotencyKey,
|
|
2046
|
+
"settlementIdempotencyKey"
|
|
2047
|
+
);
|
|
2048
|
+
} else {
|
|
2049
|
+
economyAssert(
|
|
2050
|
+
hold.entitlementId === void 0 && hold.settlementTransactionId === void 0 && hold.settlementIdempotencyKey === void 0 && hold.releaseTransactionId !== void 0 && hold.releaseIdempotencyKey !== void 0,
|
|
2051
|
+
"INVALID_CONTRACT",
|
|
2052
|
+
"A released hold requires release evidence only"
|
|
2053
|
+
);
|
|
2054
|
+
assertEconomyIdentifier(hold.releaseTransactionId, "releaseTransactionId");
|
|
2055
|
+
assertEconomyIdentifier(hold.releaseIdempotencyKey, "releaseIdempotencyKey");
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
function assertAllowanceMatchesQuote(allowance, quote) {
|
|
2059
|
+
economyAssert(
|
|
2060
|
+
allowance.status === "active" && allowance.allowanceId === quote.allowanceId && allowance.householdId === quote.householdId && allowance.childAccountId === quote.childAccountId,
|
|
2061
|
+
"INVALID_CONTRACT",
|
|
2062
|
+
"Module quote does not belong to this active child allowance"
|
|
2063
|
+
);
|
|
2064
|
+
}
|
|
2065
|
+
function assertAllowanceMatchesHold(allowance, hold) {
|
|
2066
|
+
economyAssert(
|
|
2067
|
+
allowance.status === "active" && allowance.allowanceId === hold.allowanceId && allowance.householdId === hold.householdId && allowance.childAccountId === hold.childAccountId,
|
|
2068
|
+
"INVALID_CONTRACT",
|
|
2069
|
+
"Module hold does not belong to this active child allowance"
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
function createModuleSpendHold(allowance, quote, input) {
|
|
2073
|
+
assertModuleAllowance(allowance);
|
|
2074
|
+
assertModuleSpendQuote(quote);
|
|
2075
|
+
assertAllowanceMatchesQuote(allowance, quote);
|
|
2076
|
+
economyAssert(
|
|
2077
|
+
allowance.version === input.expectedAllowanceVersion,
|
|
2078
|
+
"INVALID_CONTRACT",
|
|
2079
|
+
"Module Allowance optimistic version is stale"
|
|
2080
|
+
);
|
|
2081
|
+
const occurredAt = parseIsoTimestamp(input.occurredAt);
|
|
2082
|
+
economyAssert(
|
|
2083
|
+
occurredAt >= parseIsoTimestamp(allowance.updatedAt) && occurredAt >= parseIsoTimestamp(quote.issuedAt) && occurredAt < parseIsoTimestamp(quote.expiresAt),
|
|
2084
|
+
"INVALID_TIME_WINDOW",
|
|
2085
|
+
"Module quote is not valid at hold time"
|
|
2086
|
+
);
|
|
2087
|
+
const amount = parseTokenSubunits(quote.amount);
|
|
2088
|
+
economyAssert(
|
|
2089
|
+
amount <= parseTokenSubunits(allowance.availableAmount),
|
|
2090
|
+
"INSUFFICIENT_BALANCE",
|
|
2091
|
+
"Module Allowance has insufficient available value"
|
|
2092
|
+
);
|
|
2093
|
+
assertEconomyIdentifier(input.holdId, "holdId");
|
|
2094
|
+
assertEconomyIdentifier(input.idempotencyKey, "idempotencyKey");
|
|
2095
|
+
assertPositiveSlices(input.sourceSlices, amount, "Hold source slices");
|
|
2096
|
+
const fundedLotIds = new Set(
|
|
2097
|
+
allowance.fundingSlices.map((slice) => slice.lotId)
|
|
2098
|
+
);
|
|
2099
|
+
economyAssert(
|
|
2100
|
+
input.sourceSlices.every((slice) => fundedLotIds.has(slice.lotId)),
|
|
2101
|
+
"SOURCE_LOT_RESTRICTED",
|
|
2102
|
+
"Hold slices must originate from the Module Allowance"
|
|
2103
|
+
);
|
|
2104
|
+
const nextAllowance = {
|
|
2105
|
+
...allowance,
|
|
2106
|
+
availableAmount: serializeTokenSubunits(
|
|
2107
|
+
parseTokenSubunits(allowance.availableAmount) - amount
|
|
2108
|
+
),
|
|
2109
|
+
heldAmount: serializeTokenSubunits(
|
|
2110
|
+
parseTokenSubunits(allowance.heldAmount) + amount
|
|
2111
|
+
),
|
|
2112
|
+
version: allowance.version + 1,
|
|
2113
|
+
updatedAt: input.occurredAt
|
|
2114
|
+
};
|
|
2115
|
+
const hold = {
|
|
2116
|
+
schemaVersion: ECONOMY_CONTRACT_VERSION,
|
|
2117
|
+
holdId: input.holdId,
|
|
2118
|
+
quoteId: quote.quoteId,
|
|
2119
|
+
allowanceId: quote.allowanceId,
|
|
2120
|
+
householdId: quote.householdId,
|
|
2121
|
+
guardianAccountId: quote.guardianAccountId,
|
|
2122
|
+
childAccountId: quote.childAccountId,
|
|
2123
|
+
moduleVersionId: quote.moduleVersionId,
|
|
2124
|
+
amount: quote.amount,
|
|
2125
|
+
requirementsManifestVersion: quote.requirementsManifestVersion,
|
|
2126
|
+
requirementsManifestHash: quote.requirementsManifestHash,
|
|
2127
|
+
sourceSlices: input.sourceSlices,
|
|
2128
|
+
status: "held",
|
|
2129
|
+
holdIdempotencyKey: input.idempotencyKey,
|
|
2130
|
+
version: 1,
|
|
2131
|
+
createdAt: input.occurredAt,
|
|
2132
|
+
expiresAt: quote.expiresAt,
|
|
2133
|
+
updatedAt: input.occurredAt
|
|
2134
|
+
};
|
|
2135
|
+
assertModuleAllowance(nextAllowance);
|
|
2136
|
+
assertModuleSpendHold(hold);
|
|
2137
|
+
return { allowance: nextAllowance, hold };
|
|
2138
|
+
}
|
|
2139
|
+
function settleModuleSpendHold(allowance, hold, input) {
|
|
2140
|
+
assertModuleAllowance(allowance);
|
|
2141
|
+
assertModuleSpendHold(hold);
|
|
2142
|
+
assertAllowanceMatchesHold(allowance, hold);
|
|
2143
|
+
economyAssert(
|
|
2144
|
+
hold.status === "held" && allowance.version === input.expectedAllowanceVersion && hold.version === input.expectedHoldVersion,
|
|
2145
|
+
"INVALID_CONTRACT",
|
|
2146
|
+
"Module hold is final or an optimistic version is stale"
|
|
2147
|
+
);
|
|
2148
|
+
assertEconomyIdentifier(input.entitlementId, "entitlementId");
|
|
2149
|
+
assertEconomyIdentifier(
|
|
2150
|
+
input.settlementTransactionId,
|
|
2151
|
+
"settlementTransactionId"
|
|
2152
|
+
);
|
|
2153
|
+
assertEconomyIdentifier(input.idempotencyKey, "idempotencyKey");
|
|
2154
|
+
const occurredAt = parseIsoTimestamp(input.occurredAt);
|
|
2155
|
+
economyAssert(
|
|
2156
|
+
occurredAt >= parseIsoTimestamp(allowance.updatedAt) && occurredAt >= parseIsoTimestamp(hold.updatedAt) && occurredAt < parseIsoTimestamp(hold.expiresAt),
|
|
2157
|
+
"INVALID_TIME_WINDOW",
|
|
2158
|
+
"Module hold cannot settle outside its valid lifetime"
|
|
2159
|
+
);
|
|
2160
|
+
const amount = parseTokenSubunits(hold.amount);
|
|
2161
|
+
economyAssert(
|
|
2162
|
+
amount <= parseTokenSubunits(allowance.heldAmount),
|
|
2163
|
+
"INSUFFICIENT_BALANCE",
|
|
2164
|
+
"Module Allowance does not contain the quoted held value"
|
|
2165
|
+
);
|
|
2166
|
+
const nextAllowance = {
|
|
2167
|
+
...allowance,
|
|
2168
|
+
heldAmount: serializeTokenSubunits(
|
|
2169
|
+
parseTokenSubunits(allowance.heldAmount) - amount
|
|
2170
|
+
),
|
|
2171
|
+
spentAmount: serializeTokenSubunits(
|
|
2172
|
+
parseTokenSubunits(allowance.spentAmount) + amount
|
|
2173
|
+
),
|
|
2174
|
+
version: allowance.version + 1,
|
|
2175
|
+
updatedAt: input.occurredAt
|
|
2176
|
+
};
|
|
2177
|
+
const nextHold = {
|
|
2178
|
+
...hold,
|
|
2179
|
+
status: "settled",
|
|
2180
|
+
settlementIdempotencyKey: input.idempotencyKey,
|
|
2181
|
+
entitlementId: input.entitlementId,
|
|
2182
|
+
settlementTransactionId: input.settlementTransactionId,
|
|
2183
|
+
version: hold.version + 1,
|
|
2184
|
+
updatedAt: input.occurredAt
|
|
2185
|
+
};
|
|
2186
|
+
assertModuleAllowance(nextAllowance);
|
|
2187
|
+
assertModuleSpendHold(nextHold);
|
|
2188
|
+
return { allowance: nextAllowance, hold: nextHold };
|
|
2189
|
+
}
|
|
2190
|
+
function releaseModuleSpendHold(allowance, hold, input) {
|
|
2191
|
+
assertModuleAllowance(allowance);
|
|
2192
|
+
assertModuleSpendHold(hold);
|
|
2193
|
+
assertAllowanceMatchesHold(allowance, hold);
|
|
2194
|
+
economyAssert(
|
|
2195
|
+
hold.status === "held" && allowance.version === input.expectedAllowanceVersion && hold.version === input.expectedHoldVersion,
|
|
2196
|
+
"INVALID_CONTRACT",
|
|
2197
|
+
"Module hold is final or an optimistic version is stale"
|
|
2198
|
+
);
|
|
2199
|
+
assertEconomyIdentifier(input.releaseTransactionId, "releaseTransactionId");
|
|
2200
|
+
assertEconomyIdentifier(input.idempotencyKey, "idempotencyKey");
|
|
2201
|
+
const occurredAt = parseIsoTimestamp(input.occurredAt);
|
|
2202
|
+
economyAssert(
|
|
2203
|
+
occurredAt >= parseIsoTimestamp(allowance.updatedAt) && occurredAt >= parseIsoTimestamp(hold.updatedAt),
|
|
2204
|
+
"INVALID_TIME_WINDOW",
|
|
2205
|
+
"Module hold release cannot precede its current state"
|
|
2206
|
+
);
|
|
2207
|
+
const amount = parseTokenSubunits(hold.amount);
|
|
2208
|
+
economyAssert(
|
|
2209
|
+
amount <= parseTokenSubunits(allowance.heldAmount),
|
|
2210
|
+
"INSUFFICIENT_BALANCE",
|
|
2211
|
+
"Module Allowance does not contain the quoted held value"
|
|
2212
|
+
);
|
|
2213
|
+
const nextAllowance = {
|
|
2214
|
+
...allowance,
|
|
2215
|
+
availableAmount: serializeTokenSubunits(
|
|
2216
|
+
parseTokenSubunits(allowance.availableAmount) + amount
|
|
2217
|
+
),
|
|
2218
|
+
heldAmount: serializeTokenSubunits(
|
|
2219
|
+
parseTokenSubunits(allowance.heldAmount) - amount
|
|
2220
|
+
),
|
|
2221
|
+
version: allowance.version + 1,
|
|
2222
|
+
updatedAt: input.occurredAt
|
|
2223
|
+
};
|
|
2224
|
+
const nextHold = {
|
|
2225
|
+
...hold,
|
|
2226
|
+
status: "released",
|
|
2227
|
+
releaseIdempotencyKey: input.idempotencyKey,
|
|
2228
|
+
releaseTransactionId: input.releaseTransactionId,
|
|
2229
|
+
version: hold.version + 1,
|
|
2230
|
+
updatedAt: input.occurredAt
|
|
2231
|
+
};
|
|
2232
|
+
assertModuleAllowance(nextAllowance);
|
|
2233
|
+
assertModuleSpendHold(nextHold);
|
|
2234
|
+
return { allowance: nextAllowance, hold: nextHold };
|
|
2235
|
+
}
|
|
2236
|
+
function assertModulePurchaseReceipt(receipt) {
|
|
2237
|
+
economyAssert(
|
|
2238
|
+
receipt.schemaVersion === ECONOMY_CONTRACT_VERSION,
|
|
2239
|
+
"INVALID_CONTRACT",
|
|
2240
|
+
"Unsupported module-purchase receipt contract version"
|
|
2241
|
+
);
|
|
2242
|
+
assertEconomyIdentifier(receipt.receiptId, "receiptId");
|
|
2243
|
+
assertEconomyIdentifier(receipt.quoteId, "quoteId");
|
|
2244
|
+
assertEconomyIdentifier(receipt.holdId, "holdId");
|
|
2245
|
+
assertEconomyIdentifier(receipt.allowanceId, "allowanceId");
|
|
2246
|
+
assertEconomyIdentifier(receipt.householdId, "householdId");
|
|
2247
|
+
assertEconomyIdentifier(receipt.guardianAccountId, "guardianAccountId");
|
|
2248
|
+
assertEconomyIdentifier(receipt.childAccountId, "childAccountId");
|
|
2249
|
+
assertEconomyIdentifier(receipt.moduleVersionId, "moduleVersionId");
|
|
2250
|
+
assertEconomyIdentifier(receipt.entitlementId, "entitlementId");
|
|
2251
|
+
assertEconomyIdentifier(
|
|
2252
|
+
receipt.requirementsManifestVersion,
|
|
2253
|
+
"requirementsManifestVersion"
|
|
2254
|
+
);
|
|
2255
|
+
assertSha256Reference(
|
|
2256
|
+
receipt.requirementsManifestHash,
|
|
2257
|
+
"Requirements manifest hash"
|
|
2258
|
+
);
|
|
2259
|
+
assertEconomyIdentifier(
|
|
2260
|
+
receipt.settlementTransactionId,
|
|
2261
|
+
"settlementTransactionId"
|
|
2262
|
+
);
|
|
2263
|
+
assertWholePositive2(receipt.amount, "Receipt amount");
|
|
2264
|
+
parseIsoTimestamp(receipt.issuedAt);
|
|
2265
|
+
}
|
|
2266
|
+
function createModulePurchaseReceipt(quote, hold, input) {
|
|
2267
|
+
assertModuleSpendQuote(quote);
|
|
2268
|
+
assertModuleSpendHold(hold);
|
|
2269
|
+
economyAssert(
|
|
2270
|
+
hold.status === "settled" && hold.quoteId === quote.quoteId && hold.allowanceId === quote.allowanceId && hold.householdId === quote.householdId && hold.guardianAccountId === quote.guardianAccountId && hold.childAccountId === quote.childAccountId && hold.moduleVersionId === quote.moduleVersionId && hold.amount === quote.amount && hold.requirementsManifestVersion === quote.requirementsManifestVersion && hold.requirementsManifestHash === quote.requirementsManifestHash,
|
|
2271
|
+
"INVALID_CONTRACT",
|
|
2272
|
+
"Receipt quote and settled hold evidence do not match"
|
|
2273
|
+
);
|
|
2274
|
+
economyAssert(
|
|
2275
|
+
parseIsoTimestamp(input.issuedAt) >= parseIsoTimestamp(hold.updatedAt),
|
|
2276
|
+
"INVALID_TIME_WINDOW",
|
|
2277
|
+
"Receipt cannot precede settlement"
|
|
2278
|
+
);
|
|
2279
|
+
const receipt = {
|
|
2280
|
+
schemaVersion: ECONOMY_CONTRACT_VERSION,
|
|
2281
|
+
receiptId: input.receiptId,
|
|
2282
|
+
quoteId: quote.quoteId,
|
|
2283
|
+
holdId: hold.holdId,
|
|
2284
|
+
allowanceId: quote.allowanceId,
|
|
2285
|
+
householdId: quote.householdId,
|
|
2286
|
+
guardianAccountId: quote.guardianAccountId,
|
|
2287
|
+
childAccountId: quote.childAccountId,
|
|
2288
|
+
moduleVersionId: quote.moduleVersionId,
|
|
2289
|
+
entitlementId: hold.entitlementId,
|
|
2290
|
+
amount: quote.amount,
|
|
2291
|
+
requirementsManifestVersion: quote.requirementsManifestVersion,
|
|
2292
|
+
requirementsManifestHash: quote.requirementsManifestHash,
|
|
2293
|
+
settlementTransactionId: hold.settlementTransactionId,
|
|
2294
|
+
issuedAt: input.issuedAt
|
|
2295
|
+
};
|
|
2296
|
+
assertModulePurchaseReceipt(receipt);
|
|
2297
|
+
return receipt;
|
|
2298
|
+
}
|
|
2299
|
+
function assertModulePurchaseReconciliationObservation(observation) {
|
|
2300
|
+
economyAssert(
|
|
2301
|
+
observation.schemaVersion === ECONOMY_CONTRACT_VERSION,
|
|
2302
|
+
"INVALID_CONTRACT",
|
|
2303
|
+
"Unsupported module-purchase reconciliation contract version"
|
|
2304
|
+
);
|
|
2305
|
+
assertEconomyIdentifier(observation.quoteId, "quoteId");
|
|
2306
|
+
assertEconomyIdentifier(observation.holdId, "holdId");
|
|
2307
|
+
assertEconomyIdentifier(observation.childAccountId, "childAccountId");
|
|
2308
|
+
assertEconomyIdentifier(observation.moduleVersionId, "moduleVersionId");
|
|
2309
|
+
economyAssert(
|
|
2310
|
+
["missing", "held", "settled", "released"].includes(
|
|
2311
|
+
observation.financialState
|
|
2312
|
+
),
|
|
2313
|
+
"INVALID_CONTRACT",
|
|
2314
|
+
"Reconciliation financial state is unsupported"
|
|
2315
|
+
);
|
|
2316
|
+
economyAssert(
|
|
2317
|
+
["missing", "pending", "active", "cancelled"].includes(
|
|
2318
|
+
observation.entitlementState
|
|
2319
|
+
),
|
|
2320
|
+
"INVALID_CONTRACT",
|
|
2321
|
+
"Reconciliation entitlement state is unsupported"
|
|
2322
|
+
);
|
|
2323
|
+
economyAssert(
|
|
2324
|
+
typeof observation.receiptPresent === "boolean",
|
|
2325
|
+
"INVALID_CONTRACT",
|
|
2326
|
+
"Receipt presence must be Boolean"
|
|
2327
|
+
);
|
|
2328
|
+
parseIsoTimestamp(observation.observedAt);
|
|
2329
|
+
}
|
|
2330
|
+
function reconciliationResult(action, reasonCode, blocking = false, consistent = false) {
|
|
2331
|
+
return {
|
|
2332
|
+
schemaVersion: ECONOMY_CONTRACT_VERSION,
|
|
2333
|
+
action,
|
|
2334
|
+
blocking,
|
|
2335
|
+
consistent,
|
|
2336
|
+
reasonCode
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
function reconcileModulePurchase(observation) {
|
|
2340
|
+
assertModulePurchaseReconciliationObservation(observation);
|
|
2341
|
+
const { financialState, entitlementState, receiptPresent } = observation;
|
|
2342
|
+
if (receiptPresent && !(financialState === "settled" && entitlementState === "active")) {
|
|
2343
|
+
return reconciliationResult(
|
|
2344
|
+
"manual-review",
|
|
2345
|
+
"receipt-conflicts-with-authoritative-state",
|
|
2346
|
+
true
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
if (financialState === "settled" && entitlementState === "active") {
|
|
2350
|
+
return receiptPresent ? reconciliationResult("none", "purchase-consistent", false, true) : reconciliationResult("issue-receipt", "settled-active-receipt-missing");
|
|
2351
|
+
}
|
|
2352
|
+
if (financialState === "held" && entitlementState === "pending") {
|
|
2353
|
+
return reconciliationResult("resume-settlement", "held-pending");
|
|
2354
|
+
}
|
|
2355
|
+
if (financialState === "held" && (entitlementState === "missing" || entitlementState === "cancelled")) {
|
|
2356
|
+
return reconciliationResult("release-hold", "held-without-usable-entitlement");
|
|
2357
|
+
}
|
|
2358
|
+
if (financialState === "settled" && entitlementState === "pending") {
|
|
2359
|
+
return reconciliationResult("activate-entitlement", "settled-pending");
|
|
2360
|
+
}
|
|
2361
|
+
if ((financialState === "missing" || financialState === "released") && entitlementState === "pending") {
|
|
2362
|
+
return reconciliationResult(
|
|
2363
|
+
"cancel-pending-entitlement",
|
|
2364
|
+
"pending-without-settlement-path"
|
|
2365
|
+
);
|
|
2366
|
+
}
|
|
2367
|
+
if ((financialState === "missing" || financialState === "released") && (entitlementState === "missing" || entitlementState === "cancelled")) {
|
|
2368
|
+
return reconciliationResult("none", "no-active-purchase", false, true);
|
|
2369
|
+
}
|
|
2370
|
+
return reconciliationResult(
|
|
2371
|
+
"manual-review",
|
|
2372
|
+
financialState === "settled" ? "settled-debit-without-entitlement" : "active-entitlement-without-settled-debit",
|
|
2373
|
+
true
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
|
|
1685
2377
|
// src/operations.ts
|
|
1686
2378
|
function assertDistinctAdjustmentApproval(request) {
|
|
1687
2379
|
economyAssert(
|
|
@@ -4235,6 +4927,7 @@ function assertSpendRequest(request) {
|
|
|
4235
4927
|
EARLY_BACKER_SETTLEMENT_POLICY_V2,
|
|
4236
4928
|
ECONOMY_CONTRACT_VERSION,
|
|
4237
4929
|
EconomyError,
|
|
4930
|
+
MODULE_ALLOWANCE_PURPOSE,
|
|
4238
4931
|
PRE_UTILITY_BACKER_COHORT,
|
|
4239
4932
|
TOKEN_SUBUNITS_MAX,
|
|
4240
4933
|
TOKEN_SUBUNITS_MIN,
|
|
@@ -4259,6 +4952,11 @@ function assertSpendRequest(request) {
|
|
|
4259
4952
|
assertFxSnapshot,
|
|
4260
4953
|
assertGameplayAllocation,
|
|
4261
4954
|
assertJournalChainHead,
|
|
4955
|
+
assertModuleAllowance,
|
|
4956
|
+
assertModulePurchaseReceipt,
|
|
4957
|
+
assertModulePurchaseReconciliationObservation,
|
|
4958
|
+
assertModuleSpendHold,
|
|
4959
|
+
assertModuleSpendQuote,
|
|
4262
4960
|
assertNonNegativeAccounts,
|
|
4263
4961
|
assertOpenPurchaseIntent,
|
|
4264
4962
|
assertPaidLotLifecycle,
|
|
@@ -4302,10 +5000,15 @@ function assertSpendRequest(request) {
|
|
|
4302
5000
|
calculateRollingPurchaseCapUsage,
|
|
4303
5001
|
canUseSourceLot,
|
|
4304
5002
|
canonicalTransactionPayload,
|
|
5003
|
+
closeModuleAllowance,
|
|
4305
5004
|
convertRewardPayout,
|
|
4306
5005
|
createEarlyBackerRetentionFromPaidLot,
|
|
4307
5006
|
createGameplayAllocation,
|
|
4308
5007
|
createInitialSourceLotSnapshot,
|
|
5008
|
+
createModuleAllowance,
|
|
5009
|
+
createModulePurchaseReceipt,
|
|
5010
|
+
createModuleSpendHold,
|
|
5011
|
+
createModuleSpendQuote,
|
|
4309
5012
|
createPaidLotLifecycle,
|
|
4310
5013
|
createProjectionSnapshot,
|
|
4311
5014
|
createPurchaseIntentLifecycle,
|
|
@@ -4319,6 +5022,7 @@ function assertSpendRequest(request) {
|
|
|
4319
5022
|
economyAssert,
|
|
4320
5023
|
evaluateEarlyBacker,
|
|
4321
5024
|
evaluateEarlyBackerBySettlementV2,
|
|
5025
|
+
fundModuleAllowance,
|
|
4322
5026
|
isWholeTokenAmount,
|
|
4323
5027
|
mapBitLabsConversionState,
|
|
4324
5028
|
parseGbpMinorUnits,
|
|
@@ -4326,12 +5030,16 @@ function assertSpendRequest(request) {
|
|
|
4326
5030
|
parseTokenSubunits,
|
|
4327
5031
|
rebuildBalanceProjection,
|
|
4328
5032
|
reclaimGameplayAllocation,
|
|
5033
|
+
reclaimModuleAllowance,
|
|
5034
|
+
reconcileModulePurchase,
|
|
4329
5035
|
reducePaidLotLifecycleEvents,
|
|
4330
5036
|
reducePurchaseIntentTransitions,
|
|
5037
|
+
releaseModuleSpendHold,
|
|
4331
5038
|
reserveRollingPurchaseCaps,
|
|
4332
5039
|
selectSourceLots,
|
|
4333
5040
|
serializeGbpMinorUnits,
|
|
4334
5041
|
serializeTokenSubunits,
|
|
5042
|
+
settleModuleSpendHold,
|
|
4335
5043
|
sortStringRecord,
|
|
4336
5044
|
subunitsToWholeTokens,
|
|
4337
5045
|
sumTokenSubunits,
|