@playmos/sdk 0.3.10 → 0.3.12
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/CHANGELOG.md +197 -0
- package/README.md +6 -2
- package/dist/chunk-2CW4U5YB.js +445 -0
- package/dist/{errors-Chizbb96.d.cts → errors-CYKgt2VU.d.cts} +112 -6
- package/dist/{errors-Chizbb96.d.ts → errors-CYKgt2VU.d.ts} +112 -6
- package/dist/index.cjs +1412 -24
- package/dist/index.d.cts +688 -21
- package/dist/index.d.ts +688 -21
- package/dist/index.js +1202 -189
- package/dist/server.cjs +117 -0
- package/dist/server.d.cts +26 -2
- package/dist/server.d.ts +26 -2
- package/dist/server.js +110 -2
- package/package.json +7 -11
- package/dist/chunk-UZECDT6F.js +0 -83
package/dist/index.cjs
CHANGED
|
@@ -267,10 +267,10 @@ function validateMetadata(metadata) {
|
|
|
267
267
|
|
|
268
268
|
// src/payout.ts
|
|
269
269
|
var PayoutError = class extends Error {
|
|
270
|
-
constructor(message) {
|
|
270
|
+
constructor(message, code = "payout_invalid") {
|
|
271
271
|
super(message);
|
|
272
|
-
this.code = "payout_invalid";
|
|
273
272
|
this.name = "PayoutError";
|
|
273
|
+
this.code = code;
|
|
274
274
|
}
|
|
275
275
|
};
|
|
276
276
|
var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
@@ -315,7 +315,8 @@ function computePayout(pool, ranking, rule) {
|
|
|
315
315
|
}
|
|
316
316
|
if (splits.length > wallets.length) {
|
|
317
317
|
throw new PayoutError(
|
|
318
|
-
`top-n
|
|
318
|
+
`ranking_too_short: a top-n rule cannot be settled from a ranking shorter than splitsBps.length (need ${splits.length}, got ${wallets.length}) \u2014 open with winner-take-all, or settle with explicit winners[{wallet,amount}]`,
|
|
319
|
+
"ranking_too_short"
|
|
319
320
|
);
|
|
320
321
|
}
|
|
321
322
|
let sumBps = 0;
|
|
@@ -628,7 +629,7 @@ function resolveRawProvider(wallet) {
|
|
|
628
629
|
}
|
|
629
630
|
if (injected) return injected;
|
|
630
631
|
throw new WalletConnectionError(
|
|
631
|
-
|
|
632
|
+
'base-account connector needs a provider \u2014 this is not a built-in Base Account/passkey flow in @playmos/sdk. In the Base App a provider is often injected; elsewhere install @base-org/account, construct a provider, and pass it as wallet.provider (or use connector: "injected" in a wallet browser).'
|
|
632
633
|
);
|
|
633
634
|
}
|
|
634
635
|
function resolveProvider(wallet, opts) {
|
|
@@ -801,6 +802,182 @@ async function assertEnoughGas(provider, from, minWei) {
|
|
|
801
802
|
});
|
|
802
803
|
}
|
|
803
804
|
}
|
|
805
|
+
function seriesToBytes32(series) {
|
|
806
|
+
const s = series.trim();
|
|
807
|
+
if (/^0x[0-9a-fA-F]{64}$/.test(s)) return s.toLowerCase();
|
|
808
|
+
return viem.keccak256(viem.toHex(s));
|
|
809
|
+
}
|
|
810
|
+
var identityToBytes32 = seriesToBytes32;
|
|
811
|
+
|
|
812
|
+
// src/chain/epochPrizePoolAbi.ts
|
|
813
|
+
var epochPrizePoolViewAbi = [
|
|
814
|
+
{
|
|
815
|
+
type: "function",
|
|
816
|
+
name: "currentEpochId",
|
|
817
|
+
stateMutability: "view",
|
|
818
|
+
inputs: [{ name: "series", type: "bytes32" }],
|
|
819
|
+
outputs: [{ type: "uint256" }]
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
type: "function",
|
|
823
|
+
name: "epochIdAt",
|
|
824
|
+
stateMutability: "view",
|
|
825
|
+
inputs: [
|
|
826
|
+
{ name: "series", type: "bytes32" },
|
|
827
|
+
{ name: "timestamp", type: "uint256" }
|
|
828
|
+
],
|
|
829
|
+
outputs: [{ type: "uint256" }]
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
type: "function",
|
|
833
|
+
name: "epochPool",
|
|
834
|
+
stateMutability: "view",
|
|
835
|
+
inputs: [
|
|
836
|
+
{ name: "series", type: "bytes32" },
|
|
837
|
+
{ name: "epochId", type: "uint256" }
|
|
838
|
+
],
|
|
839
|
+
outputs: [{ type: "uint256" }]
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
type: "function",
|
|
843
|
+
name: "epochOutgoingSeed",
|
|
844
|
+
stateMutability: "view",
|
|
845
|
+
inputs: [
|
|
846
|
+
{ name: "series", type: "bytes32" },
|
|
847
|
+
{ name: "epochId", type: "uint256" }
|
|
848
|
+
],
|
|
849
|
+
outputs: [{ type: "uint256" }]
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
type: "function",
|
|
853
|
+
name: "epochIncomingSeed",
|
|
854
|
+
stateMutability: "view",
|
|
855
|
+
inputs: [
|
|
856
|
+
{ name: "series", type: "bytes32" },
|
|
857
|
+
{ name: "epochId", type: "uint256" }
|
|
858
|
+
],
|
|
859
|
+
outputs: [{ type: "uint256" }]
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
type: "function",
|
|
863
|
+
name: "epochEntryCount",
|
|
864
|
+
stateMutability: "view",
|
|
865
|
+
inputs: [
|
|
866
|
+
{ name: "series", type: "bytes32" },
|
|
867
|
+
{ name: "epochId", type: "uint256" }
|
|
868
|
+
],
|
|
869
|
+
outputs: [{ type: "uint256" }]
|
|
870
|
+
},
|
|
871
|
+
{
|
|
872
|
+
type: "function",
|
|
873
|
+
name: "getSeries",
|
|
874
|
+
stateMutability: "view",
|
|
875
|
+
inputs: [{ name: "series", type: "bytes32" }],
|
|
876
|
+
outputs: [
|
|
877
|
+
{ name: "created", type: "bool" },
|
|
878
|
+
{ name: "genesis", type: "uint256" },
|
|
879
|
+
{ name: "epochDuration", type: "uint256" },
|
|
880
|
+
{ name: "entry", type: "uint256" },
|
|
881
|
+
{ name: "feeBps", type: "uint16" },
|
|
882
|
+
{ name: "poolBps", type: "uint16" },
|
|
883
|
+
{ name: "seedBps", type: "uint16" }
|
|
884
|
+
]
|
|
885
|
+
},
|
|
886
|
+
{
|
|
887
|
+
type: "function",
|
|
888
|
+
name: "getEpoch",
|
|
889
|
+
stateMutability: "view",
|
|
890
|
+
inputs: [
|
|
891
|
+
{ name: "series", type: "bytes32" },
|
|
892
|
+
{ name: "epochId", type: "uint256" }
|
|
893
|
+
],
|
|
894
|
+
outputs: [
|
|
895
|
+
{ name: "pool", type: "uint256" },
|
|
896
|
+
{ name: "outgoingSeed", type: "uint256" },
|
|
897
|
+
{ name: "incomingSeed", type: "uint256" },
|
|
898
|
+
{ name: "entryCount_", type: "uint256" },
|
|
899
|
+
{ name: "terminal", type: "uint8" }
|
|
900
|
+
]
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
type: "function",
|
|
904
|
+
name: "entryCount",
|
|
905
|
+
stateMutability: "view",
|
|
906
|
+
inputs: [
|
|
907
|
+
{ name: "series", type: "bytes32" },
|
|
908
|
+
{ name: "epochId", type: "uint256" },
|
|
909
|
+
{ name: "identity", type: "bytes32" }
|
|
910
|
+
],
|
|
911
|
+
outputs: [{ type: "uint256" }]
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
type: "function",
|
|
915
|
+
name: "claimableRefund",
|
|
916
|
+
stateMutability: "view",
|
|
917
|
+
inputs: [
|
|
918
|
+
{ name: "series", type: "bytes32" },
|
|
919
|
+
{ name: "epochId", type: "uint256" },
|
|
920
|
+
{ name: "payer", type: "address" }
|
|
921
|
+
],
|
|
922
|
+
outputs: [{ type: "uint256" }]
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
type: "function",
|
|
926
|
+
name: "withdrawable",
|
|
927
|
+
stateMutability: "view",
|
|
928
|
+
inputs: [{ name: "account", type: "address" }],
|
|
929
|
+
outputs: [{ type: "uint256" }]
|
|
930
|
+
}
|
|
931
|
+
];
|
|
932
|
+
var epochPrizePoolEnterAbi = [
|
|
933
|
+
{
|
|
934
|
+
type: "function",
|
|
935
|
+
name: "enter",
|
|
936
|
+
stateMutability: "nonpayable",
|
|
937
|
+
inputs: [
|
|
938
|
+
{ name: "series", type: "bytes32" },
|
|
939
|
+
{ name: "identity", type: "bytes32" }
|
|
940
|
+
],
|
|
941
|
+
outputs: []
|
|
942
|
+
}
|
|
943
|
+
];
|
|
944
|
+
var epochPrizePoolRefundAbi = [
|
|
945
|
+
{
|
|
946
|
+
type: "function",
|
|
947
|
+
name: "claimRefund",
|
|
948
|
+
stateMutability: "nonpayable",
|
|
949
|
+
inputs: [
|
|
950
|
+
{ name: "series", type: "bytes32" },
|
|
951
|
+
{ name: "epochId", type: "uint256" },
|
|
952
|
+
{ name: "payer", type: "address" }
|
|
953
|
+
],
|
|
954
|
+
outputs: [{ name: "amount", type: "uint256" }]
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
type: "function",
|
|
958
|
+
name: "withdraw",
|
|
959
|
+
stateMutability: "nonpayable",
|
|
960
|
+
inputs: [],
|
|
961
|
+
outputs: []
|
|
962
|
+
}
|
|
963
|
+
];
|
|
964
|
+
var epochPrizePoolExecuteSettlementAbi = [
|
|
965
|
+
{
|
|
966
|
+
type: "function",
|
|
967
|
+
name: "executeSignedSettlement",
|
|
968
|
+
stateMutability: "nonpayable",
|
|
969
|
+
inputs: [
|
|
970
|
+
{ name: "series", type: "bytes32" },
|
|
971
|
+
{ name: "epochId", type: "uint256" },
|
|
972
|
+
{ name: "winners", type: "address[]" },
|
|
973
|
+
{ name: "amounts", type: "uint256[]" },
|
|
974
|
+
{ name: "signature", type: "bytes" }
|
|
975
|
+
],
|
|
976
|
+
outputs: []
|
|
977
|
+
}
|
|
978
|
+
];
|
|
979
|
+
|
|
980
|
+
// src/chain/calls.ts
|
|
804
981
|
var toBytes32 = (s) => viem.keccak256(viem.toBytes(s));
|
|
805
982
|
function buildIapCalls(args) {
|
|
806
983
|
const payData = viem.encodeFunctionData({
|
|
@@ -832,6 +1009,53 @@ function buildWithdrawCall(prizePool) {
|
|
|
832
1009
|
});
|
|
833
1010
|
return { to: prizePool, data };
|
|
834
1011
|
}
|
|
1012
|
+
function buildEpochExecuteSettlementCall(args) {
|
|
1013
|
+
return {
|
|
1014
|
+
to: args.epochPrizePool,
|
|
1015
|
+
data: viem.encodeFunctionData({
|
|
1016
|
+
abi: epochPrizePoolExecuteSettlementAbi,
|
|
1017
|
+
functionName: "executeSignedSettlement",
|
|
1018
|
+
args: [
|
|
1019
|
+
seriesToBytes32(args.series),
|
|
1020
|
+
args.epochId,
|
|
1021
|
+
args.winners.map((w) => viem.getAddress(w.toLowerCase())),
|
|
1022
|
+
args.amounts,
|
|
1023
|
+
args.signature
|
|
1024
|
+
]
|
|
1025
|
+
})
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
function buildEpochEntryCalls(args) {
|
|
1029
|
+
const enterData = viem.encodeFunctionData({
|
|
1030
|
+
abi: epochPrizePoolEnterAbi,
|
|
1031
|
+
functionName: "enter",
|
|
1032
|
+
args: [seriesToBytes32(args.series), identityToBytes32(args.identity)]
|
|
1033
|
+
});
|
|
1034
|
+
return [
|
|
1035
|
+
{ to: args.usdc, data: encodeApprove(args.epochPrizePool, args.entryMicro) },
|
|
1036
|
+
{ to: args.epochPrizePool, data: enterData }
|
|
1037
|
+
];
|
|
1038
|
+
}
|
|
1039
|
+
function buildEpochClaimRefundCall(args) {
|
|
1040
|
+
return {
|
|
1041
|
+
to: args.epochPrizePool,
|
|
1042
|
+
data: viem.encodeFunctionData({
|
|
1043
|
+
abi: epochPrizePoolRefundAbi,
|
|
1044
|
+
functionName: "claimRefund",
|
|
1045
|
+
args: [seriesToBytes32(args.series), args.epochId, args.payer]
|
|
1046
|
+
})
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
function buildEpochWithdrawCall(epochPrizePool) {
|
|
1050
|
+
return {
|
|
1051
|
+
to: epochPrizePool,
|
|
1052
|
+
data: viem.encodeFunctionData({
|
|
1053
|
+
abi: epochPrizePoolRefundAbi,
|
|
1054
|
+
functionName: "withdraw",
|
|
1055
|
+
args: []
|
|
1056
|
+
})
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
835
1059
|
|
|
836
1060
|
// src/mock.ts
|
|
837
1061
|
var MOCK_TX = `0x${"0".repeat(56)}deadbeef`;
|
|
@@ -893,13 +1117,893 @@ function mockVerifyResult(payment) {
|
|
|
893
1117
|
return { ...payment, mock: true };
|
|
894
1118
|
}
|
|
895
1119
|
|
|
896
|
-
// src/
|
|
1120
|
+
// src/epochs.ts
|
|
1121
|
+
var PLAYMOS_FEE_SINK_DEFAULT = "0x1b8031e20ed96131a849a52290b4d640f286998d";
|
|
1122
|
+
var EPOCH_POOL_CONSTRUCTOR_TYPES = [
|
|
1123
|
+
{ type: "address" },
|
|
1124
|
+
{ type: "address" },
|
|
1125
|
+
{ type: "address" },
|
|
1126
|
+
{ type: "address" },
|
|
1127
|
+
{ type: "uint256" }
|
|
1128
|
+
];
|
|
1129
|
+
function encodeEpochPoolConstructorArgs(args) {
|
|
1130
|
+
return viem.encodeAbiParameters(EPOCH_POOL_CONSTRUCTOR_TYPES, [
|
|
1131
|
+
args.token,
|
|
1132
|
+
args.admin,
|
|
1133
|
+
args.operator,
|
|
1134
|
+
args.feeSink,
|
|
1135
|
+
args.refundTimeout
|
|
1136
|
+
]);
|
|
1137
|
+
}
|
|
1138
|
+
function derivedEpochId(genesis, epochDuration, at) {
|
|
1139
|
+
if (epochDuration === 0n) {
|
|
1140
|
+
throw new ConfigError("epochDuration must be > 0");
|
|
1141
|
+
}
|
|
1142
|
+
if (at < genesis) {
|
|
1143
|
+
throw new ConfigError("epoch has not started (at < genesis)");
|
|
1144
|
+
}
|
|
1145
|
+
return (at - genesis) / epochDuration;
|
|
1146
|
+
}
|
|
1147
|
+
function epochPayableMicro(incomingSeed, pool) {
|
|
1148
|
+
return incomingSeed + pool;
|
|
1149
|
+
}
|
|
1150
|
+
function mockPodiumPayable(winners) {
|
|
1151
|
+
if (winners.length === 0) return void 0;
|
|
1152
|
+
let total = 0n;
|
|
1153
|
+
for (const w of winners) {
|
|
1154
|
+
if (typeof w === "string") return void 0;
|
|
1155
|
+
if (w.amountMicro != null && w.amountMicro !== "") {
|
|
1156
|
+
try {
|
|
1157
|
+
total += BigInt(w.amountMicro);
|
|
1158
|
+
} catch {
|
|
1159
|
+
return void 0;
|
|
1160
|
+
}
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (w.amount) {
|
|
1164
|
+
try {
|
|
1165
|
+
total += parseUsdToMicro(w.amount);
|
|
1166
|
+
} catch {
|
|
1167
|
+
return void 0;
|
|
1168
|
+
}
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1171
|
+
return void 0;
|
|
1172
|
+
}
|
|
1173
|
+
return {
|
|
1174
|
+
payable: formatMicroToUsd(total),
|
|
1175
|
+
payableMicro: total.toString()
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
function mockWinnerMicro(w) {
|
|
1179
|
+
if (typeof w === "string") return void 0;
|
|
1180
|
+
if (w.amountMicro != null && w.amountMicro !== "" && /^\d+$/.test(w.amountMicro)) {
|
|
1181
|
+
return BigInt(w.amountMicro);
|
|
1182
|
+
}
|
|
1183
|
+
if (w.amount) {
|
|
1184
|
+
try {
|
|
1185
|
+
return parseUsdToMicro(w.amount);
|
|
1186
|
+
} catch {
|
|
1187
|
+
return void 0;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return void 0;
|
|
1191
|
+
}
|
|
1192
|
+
function mockWinnerAmount(w) {
|
|
1193
|
+
const micro = mockWinnerMicro(w);
|
|
1194
|
+
if (micro === void 0) return {};
|
|
1195
|
+
return { amount: formatMicroToUsd(micro), amountMicro: micro.toString() };
|
|
1196
|
+
}
|
|
1197
|
+
var TERMINALS = ["none", "settled", "refunded", "rolled"];
|
|
1198
|
+
function terminalFromChain(raw) {
|
|
1199
|
+
return TERMINALS[raw] ?? "none";
|
|
1200
|
+
}
|
|
1201
|
+
function requireSeries(series) {
|
|
1202
|
+
if (typeof series !== "string" || series.trim() === "") {
|
|
1203
|
+
throw new MissingFieldError("series");
|
|
1204
|
+
}
|
|
1205
|
+
return series.trim();
|
|
1206
|
+
}
|
|
1207
|
+
function requireIdentity(identity) {
|
|
1208
|
+
if (typeof identity !== "string" || identity.trim() === "") {
|
|
1209
|
+
throw new MissingFieldError("identity");
|
|
1210
|
+
}
|
|
1211
|
+
return identity.trim();
|
|
1212
|
+
}
|
|
1213
|
+
function assertPinParity(label, local, fromService) {
|
|
1214
|
+
if (!fromService) {
|
|
1215
|
+
throw new ConfigError(
|
|
1216
|
+
`epochs.enter: the service did not return ${label}Bytes32 \u2014 cannot prove the on-chain id matches before paying.`,
|
|
1217
|
+
{ field: `${label}Bytes32` }
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
if (local.toLowerCase() !== fromService.toLowerCase()) {
|
|
1221
|
+
throw new ConfigError(
|
|
1222
|
+
`epochs.enter: ${label} derives differently in the SDK and the service (sdk=${local}, service=${fromService}). Refusing to enter \u2014 a paid entry under a mismatched id is never scored. See sdk#633.`,
|
|
1223
|
+
{ field: `${label}Bytes32` }
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
function epochPathId(epochId) {
|
|
1228
|
+
if (epochId === void 0 || epochId === null || epochId === "") return "current";
|
|
1229
|
+
const s = String(epochId).trim();
|
|
1230
|
+
if (s === "current") return "current";
|
|
1231
|
+
if (!/^\d+$/.test(s)) {
|
|
1232
|
+
throw new ConfigError(`epochId must be a non-negative integer, got: ${JSON.stringify(epochId)}`);
|
|
1233
|
+
}
|
|
1234
|
+
return s;
|
|
1235
|
+
}
|
|
1236
|
+
function qs(params) {
|
|
1237
|
+
const u = new URLSearchParams();
|
|
1238
|
+
for (const [k, v] of Object.entries(params)) {
|
|
1239
|
+
if (v) u.set(k, v);
|
|
1240
|
+
}
|
|
1241
|
+
const s = u.toString();
|
|
1242
|
+
return s ? `?${s}` : "";
|
|
1243
|
+
}
|
|
1244
|
+
function pictureFromMicros(args) {
|
|
1245
|
+
const payableMicro = epochPayableMicro(args.incomingSeedMicro, args.poolMicro);
|
|
1246
|
+
return {
|
|
1247
|
+
series: args.series,
|
|
1248
|
+
epochId: args.epochId,
|
|
1249
|
+
pool: formatMicroToUsd(args.poolMicro),
|
|
1250
|
+
incomingSeed: formatMicroToUsd(args.incomingSeedMicro),
|
|
1251
|
+
outgoingSeed: formatMicroToUsd(args.outgoingSeedMicro),
|
|
1252
|
+
payable: formatMicroToUsd(payableMicro),
|
|
1253
|
+
poolMicro: args.poolMicro.toString(),
|
|
1254
|
+
incomingSeedMicro: args.incomingSeedMicro.toString(),
|
|
1255
|
+
outgoingSeedMicro: args.outgoingSeedMicro.toString(),
|
|
1256
|
+
payableMicro: payableMicro.toString(),
|
|
1257
|
+
via: args.via,
|
|
1258
|
+
epochPrizePool: args.epochPrizePool,
|
|
1259
|
+
...args.mock ? { mock: true } : {}
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
function toBig(value, fallback) {
|
|
1263
|
+
if (value === void 0) return fallback;
|
|
1264
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return BigInt(Math.trunc(value));
|
|
1265
|
+
if (typeof value === "string" && /^\d+$/.test(value.trim())) return BigInt(value.trim());
|
|
1266
|
+
throw new ConfigError(`expected a non-negative integer, got: ${JSON.stringify(value)}`);
|
|
1267
|
+
}
|
|
897
1268
|
var ADDRESS_RE2 = /^0x[0-9a-fA-F]{40}$/;
|
|
1269
|
+
function requireAddr(raw, field) {
|
|
1270
|
+
if (typeof raw !== "string" || !ADDRESS_RE2.test(raw.trim())) {
|
|
1271
|
+
throw new MissingFieldError(field);
|
|
1272
|
+
}
|
|
1273
|
+
return raw.trim().toLowerCase();
|
|
1274
|
+
}
|
|
1275
|
+
function requireAddrKeepCase(raw, field) {
|
|
1276
|
+
if (typeof raw !== "string" || !ADDRESS_RE2.test(raw.trim())) {
|
|
1277
|
+
throw new MissingFieldError(field);
|
|
1278
|
+
}
|
|
1279
|
+
return raw.trim();
|
|
1280
|
+
}
|
|
1281
|
+
function requireEpochPool(input, cfg) {
|
|
1282
|
+
const raw = input?.epochPrizePool ?? cfg.contracts?.epochPrizePool;
|
|
1283
|
+
if (!raw) {
|
|
1284
|
+
throw new ConfigError(
|
|
1285
|
+
"epochs refund rail needs contracts.epochPrizePool (or pass epochPrizePool) \u2014 this is EpochPrizePool, not PrizePool",
|
|
1286
|
+
{ field: "epochPrizePool" }
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1289
|
+
return requireAddr(raw, "epochPrizePool");
|
|
1290
|
+
}
|
|
1291
|
+
function requirePastEpochId(epochId) {
|
|
1292
|
+
const id = epochPathId(epochId);
|
|
1293
|
+
if (id === "current") {
|
|
1294
|
+
throw new ConfigError("epochs refund rail requires a past epochId \u2014 Current is still taking entries");
|
|
1295
|
+
}
|
|
1296
|
+
return id;
|
|
1297
|
+
}
|
|
1298
|
+
function walletStatus(status) {
|
|
1299
|
+
if (status === "CONFIRMED") return "confirmed";
|
|
1300
|
+
if (status === "FAILED") return "failed";
|
|
1301
|
+
return "pending";
|
|
1302
|
+
}
|
|
1303
|
+
function sameAddr(a, b) {
|
|
1304
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
1305
|
+
}
|
|
1306
|
+
function prepareStudioPoolLocal(input, playmosFeeSink, token, playmosAddresses = []) {
|
|
1307
|
+
const studio = requireAddr(input.studioWallet, "studioWallet");
|
|
1308
|
+
const sink = playmosFeeSink.toLowerCase();
|
|
1309
|
+
const forbidden = [sink, ...playmosAddresses.map((a) => a.toLowerCase())];
|
|
1310
|
+
if (forbidden.some((a) => sameAddr(a, studio))) {
|
|
1311
|
+
throw new ConfigError("studioWallet must not be a Playmos address (Playmos never holds studio keys)");
|
|
1312
|
+
}
|
|
1313
|
+
if (input.feeSink && !sameAddr(input.feeSink, sink)) {
|
|
1314
|
+
throw new ConfigError("feeSink must equal the Playmos sink; refused before broadcast");
|
|
1315
|
+
}
|
|
1316
|
+
if (input.admin && !sameAddr(input.admin, studio)) {
|
|
1317
|
+
throw new ConfigError("admin must be the studio wallet from block one");
|
|
1318
|
+
}
|
|
1319
|
+
if (input.operator && !sameAddr(input.operator, studio)) {
|
|
1320
|
+
throw new ConfigError("operator must be the studio wallet from block one");
|
|
1321
|
+
}
|
|
1322
|
+
if (input.admin && forbidden.some((a) => sameAddr(a, input.admin))) {
|
|
1323
|
+
throw new ConfigError("Playmos must not be ADMIN_ROLE");
|
|
1324
|
+
}
|
|
1325
|
+
if (input.operator && forbidden.some((a) => sameAddr(a, input.operator))) {
|
|
1326
|
+
throw new ConfigError("Playmos must not be OPERATOR_ROLE");
|
|
1327
|
+
}
|
|
1328
|
+
const refundTimeout = toBig(input.refundTimeout, 604800n);
|
|
1329
|
+
if (refundTimeout < 3600n || refundTimeout > 7776000n) {
|
|
1330
|
+
throw new ConfigError("refundTimeout must be between 3600 (1h) and 7776000 (90d) seconds");
|
|
1331
|
+
}
|
|
1332
|
+
return {
|
|
1333
|
+
token: token.toLowerCase(),
|
|
1334
|
+
admin: studio,
|
|
1335
|
+
operator: studio,
|
|
1336
|
+
feeSink: sink,
|
|
1337
|
+
refundTimeout: refundTimeout.toString(),
|
|
1338
|
+
constructorArgs: encodeEpochPoolConstructorArgs({
|
|
1339
|
+
token: token.toLowerCase(),
|
|
1340
|
+
admin: studio,
|
|
1341
|
+
operator: studio,
|
|
1342
|
+
feeSink: sink,
|
|
1343
|
+
refundTimeout
|
|
1344
|
+
}),
|
|
1345
|
+
constructor: "EpochPrizePool",
|
|
1346
|
+
studioHoldsAdmin: true,
|
|
1347
|
+
studioHoldsOperator: true,
|
|
1348
|
+
playmosIsAdmin: false,
|
|
1349
|
+
playmosIsOperator: false,
|
|
1350
|
+
via: "mock",
|
|
1351
|
+
mock: true
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
function epochPoolProofMessage(terms) {
|
|
1355
|
+
return [
|
|
1356
|
+
"Playmos EpochPrizePool registration",
|
|
1357
|
+
`studio: ${terms.studioId}`,
|
|
1358
|
+
`pool: ${terms.poolAddress.toLowerCase()}`,
|
|
1359
|
+
`wallet: ${terms.studioWallet.toLowerCase()}`,
|
|
1360
|
+
`chainId: ${terms.chainId}`
|
|
1361
|
+
].join("\n");
|
|
1362
|
+
}
|
|
1363
|
+
async function readWithdrawableMicro(deps, pool) {
|
|
1364
|
+
const read = deps.readView;
|
|
1365
|
+
const account = deps.walletAddress;
|
|
1366
|
+
if (!read || !account) return null;
|
|
1367
|
+
try {
|
|
1368
|
+
const raw = await read(
|
|
1369
|
+
pool,
|
|
1370
|
+
viem.encodeFunctionData({
|
|
1371
|
+
abi: epochPrizePoolViewAbi,
|
|
1372
|
+
functionName: "withdrawable",
|
|
1373
|
+
args: [await account()]
|
|
1374
|
+
})
|
|
1375
|
+
);
|
|
1376
|
+
return viem.decodeFunctionResult({
|
|
1377
|
+
abi: epochPrizePoolViewAbi,
|
|
1378
|
+
functionName: "withdrawable",
|
|
1379
|
+
data: raw
|
|
1380
|
+
});
|
|
1381
|
+
} catch {
|
|
1382
|
+
return null;
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
function settlementTypedData(input) {
|
|
1386
|
+
const pool = requireAddr(input.pool, "pool");
|
|
1387
|
+
const series = requireSeries(input.series);
|
|
1388
|
+
const epochId = requirePastEpochId(input.epochId);
|
|
1389
|
+
if (!Array.isArray(input.winners) || !Array.isArray(input.amounts)) {
|
|
1390
|
+
throw new MissingFieldError("winners");
|
|
1391
|
+
}
|
|
1392
|
+
if (input.winners.length !== input.amounts.length) {
|
|
1393
|
+
throw new ConfigError("winners and amounts must be the same length");
|
|
1394
|
+
}
|
|
1395
|
+
return {
|
|
1396
|
+
domain: {
|
|
1397
|
+
name: "EpochPrizePool",
|
|
1398
|
+
version: "1",
|
|
1399
|
+
chainId: input.chainId,
|
|
1400
|
+
verifyingContract: pool
|
|
1401
|
+
},
|
|
1402
|
+
types: {
|
|
1403
|
+
Settlement: [
|
|
1404
|
+
{ name: "series", type: "bytes32" },
|
|
1405
|
+
{ name: "epochId", type: "uint256" },
|
|
1406
|
+
{ name: "winners", type: "address[]" },
|
|
1407
|
+
{ name: "amounts", type: "uint256[]" }
|
|
1408
|
+
]
|
|
1409
|
+
},
|
|
1410
|
+
primaryType: "Settlement",
|
|
1411
|
+
message: {
|
|
1412
|
+
series: seriesToBytes32(series),
|
|
1413
|
+
epochId: BigInt(epochId),
|
|
1414
|
+
winners: input.winners.map((w, i) => requireAddrKeepCase(w, `winners[${i}]`)),
|
|
1415
|
+
amounts: input.amounts.map((a, i) => parseMicroString(a, `amounts[${i}]`))
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
function parseMicroString(raw, field) {
|
|
1420
|
+
if (typeof raw !== "string" || !/^(0|[1-9]\d*)$/.test(raw)) {
|
|
1421
|
+
throw new ConfigError(`${field} must be an integer micro-USDC string`, { field });
|
|
1422
|
+
}
|
|
1423
|
+
return BigInt(raw);
|
|
1424
|
+
}
|
|
1425
|
+
async function readEpochTerminal(deps, pool, series, epochId) {
|
|
1426
|
+
const read = deps.readView;
|
|
1427
|
+
if (!read) return null;
|
|
1428
|
+
try {
|
|
1429
|
+
const raw = await read(
|
|
1430
|
+
pool,
|
|
1431
|
+
viem.encodeFunctionData({
|
|
1432
|
+
abi: epochPrizePoolViewAbi,
|
|
1433
|
+
functionName: "getEpoch",
|
|
1434
|
+
args: [seriesToBytes32(series), BigInt(epochId)]
|
|
1435
|
+
})
|
|
1436
|
+
);
|
|
1437
|
+
const decoded = viem.decodeFunctionResult({
|
|
1438
|
+
abi: epochPrizePoolViewAbi,
|
|
1439
|
+
functionName: "getEpoch",
|
|
1440
|
+
data: raw
|
|
1441
|
+
});
|
|
1442
|
+
const termRaw = decoded && typeof decoded === "object" && "terminal" in decoded ? Number(decoded.terminal) : Number(decoded[4]);
|
|
1443
|
+
return terminalFromChain(termRaw);
|
|
1444
|
+
} catch {
|
|
1445
|
+
return null;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
function createEpochsApi(deps) {
|
|
1449
|
+
return {
|
|
1450
|
+
async currentId(input) {
|
|
1451
|
+
const series = requireSeries(input?.series);
|
|
1452
|
+
const cfg = deps.config();
|
|
1453
|
+
if (cfg.mock) {
|
|
1454
|
+
const genesis = toBig(input.genesis, 0n);
|
|
1455
|
+
const duration = toBig(input.epochDuration, 1n);
|
|
1456
|
+
const at = toBig(input.at, BigInt(Math.floor(Date.now() / 1e3)));
|
|
1457
|
+
const epochId = derivedEpochId(genesis, duration, at);
|
|
1458
|
+
return {
|
|
1459
|
+
series,
|
|
1460
|
+
epochId: epochId.toString(),
|
|
1461
|
+
via: "mock",
|
|
1462
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool ?? null,
|
|
1463
|
+
mock: true
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
const path = `/epochs/current${qs({
|
|
1467
|
+
series,
|
|
1468
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool
|
|
1469
|
+
})}`;
|
|
1470
|
+
return deps.http().get(path);
|
|
1471
|
+
},
|
|
1472
|
+
async prize(input) {
|
|
1473
|
+
const series = requireSeries(input?.series);
|
|
1474
|
+
const cfg = deps.config();
|
|
1475
|
+
const epochId = epochPathId(input?.epochId);
|
|
1476
|
+
if (cfg.mock) {
|
|
1477
|
+
return pictureFromMicros({
|
|
1478
|
+
series,
|
|
1479
|
+
epochId: epochId === "current" ? "0" : epochId,
|
|
1480
|
+
poolMicro: 0n,
|
|
1481
|
+
incomingSeedMicro: 0n,
|
|
1482
|
+
outgoingSeedMicro: 0n,
|
|
1483
|
+
via: "mock",
|
|
1484
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool ?? null,
|
|
1485
|
+
mock: true
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
const path = `/epochs/${encodeURIComponent(epochId)}/prize${qs({
|
|
1489
|
+
series,
|
|
1490
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool
|
|
1491
|
+
})}`;
|
|
1492
|
+
const res = await deps.http().get(path);
|
|
1493
|
+
return res.prize;
|
|
1494
|
+
},
|
|
1495
|
+
async get(input) {
|
|
1496
|
+
const series = requireSeries(input?.series);
|
|
1497
|
+
const cfg = deps.config();
|
|
1498
|
+
const epochId = epochPathId(input?.epochId);
|
|
1499
|
+
if (cfg.mock) {
|
|
1500
|
+
const prize = pictureFromMicros({
|
|
1501
|
+
series,
|
|
1502
|
+
epochId: epochId === "current" ? "0" : epochId,
|
|
1503
|
+
poolMicro: 0n,
|
|
1504
|
+
incomingSeedMicro: 0n,
|
|
1505
|
+
outgoingSeedMicro: 0n,
|
|
1506
|
+
via: "mock",
|
|
1507
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool ?? null,
|
|
1508
|
+
mock: true
|
|
1509
|
+
});
|
|
1510
|
+
return {
|
|
1511
|
+
...prize,
|
|
1512
|
+
entryCount: "0",
|
|
1513
|
+
terminal: "none"
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
const path = `/epochs/${encodeURIComponent(epochId)}${qs({
|
|
1517
|
+
series,
|
|
1518
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool
|
|
1519
|
+
})}`;
|
|
1520
|
+
return deps.http().get(path);
|
|
1521
|
+
},
|
|
1522
|
+
async getSeries(input) {
|
|
1523
|
+
const series = requireSeries(input?.series);
|
|
1524
|
+
const cfg = deps.config();
|
|
1525
|
+
if (cfg.mock) {
|
|
1526
|
+
return {
|
|
1527
|
+
series,
|
|
1528
|
+
created: true,
|
|
1529
|
+
genesis: "0",
|
|
1530
|
+
epochDuration: "1",
|
|
1531
|
+
entry: "0",
|
|
1532
|
+
feeBps: 1e3,
|
|
1533
|
+
poolBps: 6e3,
|
|
1534
|
+
seedBps: 3e3,
|
|
1535
|
+
via: "mock",
|
|
1536
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool ?? null,
|
|
1537
|
+
mock: true
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
const path = `/epochs/series${qs({
|
|
1541
|
+
series,
|
|
1542
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool
|
|
1543
|
+
})}`;
|
|
1544
|
+
return deps.http().get(path);
|
|
1545
|
+
},
|
|
1546
|
+
async preparePool(input) {
|
|
1547
|
+
const studioWallet = requireAddr(input?.studioWallet, "studioWallet");
|
|
1548
|
+
const cfg = deps.config();
|
|
1549
|
+
const sink = (input.playmosFeeSink ?? PLAYMOS_FEE_SINK_DEFAULT).toLowerCase();
|
|
1550
|
+
const token = (input.token ?? cfg.contracts?.usdc ?? "0x036cbd53842c5426634e7929541ec2318f3dcf7e").toLowerCase();
|
|
1551
|
+
if (cfg.mock) {
|
|
1552
|
+
return prepareStudioPoolLocal({ ...input, studioWallet }, sink, token);
|
|
1553
|
+
}
|
|
1554
|
+
return deps.http().post("/epochs/pools/prepare", {
|
|
1555
|
+
studioWallet,
|
|
1556
|
+
feeSink: input.feeSink,
|
|
1557
|
+
admin: input.admin,
|
|
1558
|
+
operator: input.operator,
|
|
1559
|
+
refundTimeout: input.refundTimeout
|
|
1560
|
+
});
|
|
1561
|
+
},
|
|
1562
|
+
async registerPool(input) {
|
|
1563
|
+
const studioWallet = requireAddr(input?.studioWallet, "studioWallet");
|
|
1564
|
+
const poolAddress = requireAddr(input?.poolAddress, "poolAddress");
|
|
1565
|
+
const cfg = deps.config();
|
|
1566
|
+
if (cfg.mock) {
|
|
1567
|
+
const sink = (input.feeSink ?? PLAYMOS_FEE_SINK_DEFAULT).toLowerCase();
|
|
1568
|
+
const token = (cfg.contracts?.usdc ?? "0x036cbd53842c5426634e7929541ec2318f3dcf7e").toLowerCase();
|
|
1569
|
+
const prepared = prepareStudioPoolLocal({ studioWallet, feeSink: input.feeSink, admin: input.admin, operator: input.operator }, sink, token);
|
|
1570
|
+
return {
|
|
1571
|
+
poolAddress,
|
|
1572
|
+
studioWallet: prepared.admin,
|
|
1573
|
+
feeSink: prepared.feeSink,
|
|
1574
|
+
admin: prepared.admin,
|
|
1575
|
+
operator: prepared.operator,
|
|
1576
|
+
studioHoldsAdmin: true,
|
|
1577
|
+
studioHoldsOperator: true,
|
|
1578
|
+
playmosIsAdmin: false,
|
|
1579
|
+
playmosIsOperator: false,
|
|
1580
|
+
ownership: input.walletProof ? "confirmed" : "pending",
|
|
1581
|
+
owner: null,
|
|
1582
|
+
via: "mock",
|
|
1583
|
+
mock: true
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
return deps.http().post("/epochs/pools", {
|
|
1587
|
+
studioWallet,
|
|
1588
|
+
poolAddress,
|
|
1589
|
+
feeSink: input.feeSink,
|
|
1590
|
+
admin: input.admin,
|
|
1591
|
+
operator: input.operator,
|
|
1592
|
+
txHash: input.txHash,
|
|
1593
|
+
walletProof: input.walletProof
|
|
1594
|
+
});
|
|
1595
|
+
},
|
|
1596
|
+
async createPool(input) {
|
|
1597
|
+
const prepared = await this.preparePool(input);
|
|
1598
|
+
const cfg = deps.config();
|
|
1599
|
+
if (cfg.mock) {
|
|
1600
|
+
const poolAddress = input.poolAddress ? requireAddr(input.poolAddress, "poolAddress") : `0x${prepared.constructorArgs.slice(2, 42).padEnd(40, "0")}`;
|
|
1601
|
+
return this.registerPool({
|
|
1602
|
+
studioWallet: prepared.admin,
|
|
1603
|
+
poolAddress,
|
|
1604
|
+
feeSink: prepared.feeSink,
|
|
1605
|
+
walletProof: input.walletProof
|
|
1606
|
+
});
|
|
1607
|
+
}
|
|
1608
|
+
if (!input.poolAddress) {
|
|
1609
|
+
throw new ConfigError(
|
|
1610
|
+
"createPool on the live path needs poolAddress after the studio wallet submits the constructor \u2014 Playmos does not broadcast"
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
return this.registerPool({
|
|
1614
|
+
studioWallet: prepared.admin,
|
|
1615
|
+
poolAddress: input.poolAddress,
|
|
1616
|
+
feeSink: prepared.feeSink,
|
|
1617
|
+
txHash: input.txHash,
|
|
1618
|
+
walletProof: input.walletProof
|
|
1619
|
+
});
|
|
1620
|
+
},
|
|
1621
|
+
async settle(input) {
|
|
1622
|
+
const series = requireSeries(input?.series);
|
|
1623
|
+
if (!Array.isArray(input?.winners)) {
|
|
1624
|
+
throw new MissingFieldError("winners");
|
|
1625
|
+
}
|
|
1626
|
+
const epochId = epochPathId(input?.epochId);
|
|
1627
|
+
if (epochId === "current") {
|
|
1628
|
+
throw new ConfigError("epochs.settle requires a past epochId \u2014 Current is still taking entries");
|
|
1629
|
+
}
|
|
1630
|
+
const cfg = deps.config();
|
|
1631
|
+
if (!cfg.mock) {
|
|
1632
|
+
if (deps.assertSecretKey) deps.assertSecretKey("playmos.epochs.settle");
|
|
1633
|
+
else if (!String(cfg.apiKey ?? "").startsWith("sk_")) {
|
|
1634
|
+
throw new AuthError(
|
|
1635
|
+
"playmos.epochs.settle requires a secret (sk_) key \u2014 use sk_test_\u2026 / sk_live_\u2026 on the server. Publishable (pk_) keys cannot settle.",
|
|
1636
|
+
{ surface: "playmos.epochs.settle", isSecret: false }
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
if (cfg.mock) {
|
|
1641
|
+
const empty = input.winners.length === 0;
|
|
1642
|
+
const podiumPayable = mockPodiumPayable(input.winners);
|
|
1643
|
+
return {
|
|
1644
|
+
series,
|
|
1645
|
+
epochId,
|
|
1646
|
+
status: empty ? "rolled" : "settled",
|
|
1647
|
+
txHash: "0x0000000000000000000000000000000000000000000000000000000000000001",
|
|
1648
|
+
winners: input.winners.map((w) => {
|
|
1649
|
+
const wallet = (typeof w === "string" ? w : w.wallet).toLowerCase();
|
|
1650
|
+
return { wallet, ...mockWinnerAmount(w) };
|
|
1651
|
+
}),
|
|
1652
|
+
...podiumPayable ?? {},
|
|
1653
|
+
terminal: empty ? "rolled" : "settled",
|
|
1654
|
+
via: "mock",
|
|
1655
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool ?? null,
|
|
1656
|
+
mock: true
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
const body = await deps.http().post(
|
|
1660
|
+
`/epochs/${encodeURIComponent(epochId)}/settle`,
|
|
1661
|
+
{
|
|
1662
|
+
series,
|
|
1663
|
+
winners: input.winners,
|
|
1664
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool
|
|
1665
|
+
},
|
|
1666
|
+
{ acceptStatuses: [200, 202] }
|
|
1667
|
+
);
|
|
1668
|
+
return body.settle;
|
|
1669
|
+
},
|
|
1670
|
+
async enter(input) {
|
|
1671
|
+
const series = requireSeries(input?.series);
|
|
1672
|
+
const identity = requireIdentity(input?.identity);
|
|
1673
|
+
const cfg = deps.config();
|
|
1674
|
+
const seriesBytes32 = seriesToBytes32(series);
|
|
1675
|
+
const identityBytes32 = identityToBytes32(identity);
|
|
1676
|
+
if (cfg.mock) {
|
|
1677
|
+
const genesis = toBig(input.genesis, 0n);
|
|
1678
|
+
const duration = toBig(input.epochDuration, 1n);
|
|
1679
|
+
const at = toBig(input.at, BigInt(Math.floor(Date.now() / 1e3)));
|
|
1680
|
+
return {
|
|
1681
|
+
series,
|
|
1682
|
+
identity,
|
|
1683
|
+
epochId: derivedEpochId(genesis, duration, at).toString(),
|
|
1684
|
+
epochIdSource: "mock",
|
|
1685
|
+
seriesBytes32,
|
|
1686
|
+
identityBytes32,
|
|
1687
|
+
entry: formatMicroToUsd(C1_ENTER_SPLIT_MICRO.entry),
|
|
1688
|
+
entryMicro: C1_ENTER_SPLIT_MICRO.entry.toString(),
|
|
1689
|
+
status: "confirmed",
|
|
1690
|
+
txHash: MOCK_TX,
|
|
1691
|
+
identityEntryCount: "1",
|
|
1692
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool ?? null,
|
|
1693
|
+
usdc: cfg.contracts?.usdc ?? null,
|
|
1694
|
+
via: "mock",
|
|
1695
|
+
mock: true
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
const epochPrizePool = input.epochPrizePool ?? cfg.contracts?.epochPrizePool;
|
|
1699
|
+
const plan = await deps.http().post("/epochs/enter", {
|
|
1700
|
+
series,
|
|
1701
|
+
identity,
|
|
1702
|
+
...epochPrizePool ? { epochPrizePool } : {}
|
|
1703
|
+
});
|
|
1704
|
+
assertPinParity("series", seriesBytes32, plan.clientParams?.seriesBytes32);
|
|
1705
|
+
assertPinParity("identity", identityBytes32, plan.clientParams?.identityBytes32);
|
|
1706
|
+
const send = deps.sendEntry;
|
|
1707
|
+
if (!send) {
|
|
1708
|
+
throw new ConfigError(
|
|
1709
|
+
"epochs.enter needs a player wallet \u2014 pass `wallet` to the Playmos client. Entry money goes straight to the contract; Playmos never holds it. Building the calls yourself? Import `buildEpochEntryCalls`.",
|
|
1710
|
+
{ field: "wallet" }
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
const entryMicro = BigInt(plan.clientParams.entryMicro);
|
|
1714
|
+
const calls = buildEpochEntryCalls({
|
|
1715
|
+
usdc: plan.clientParams.usdc,
|
|
1716
|
+
epochPrizePool: plan.clientParams.epochPrizePool,
|
|
1717
|
+
series,
|
|
1718
|
+
identity,
|
|
1719
|
+
entryMicro
|
|
1720
|
+
});
|
|
1721
|
+
const sent = await send(calls, { paymasterUrl: plan.clientParams.paymasterUrl });
|
|
1722
|
+
const base = {
|
|
1723
|
+
series,
|
|
1724
|
+
identity,
|
|
1725
|
+
epochId: plan.entry.epochId,
|
|
1726
|
+
epochIdSource: "chain-clock",
|
|
1727
|
+
seriesBytes32,
|
|
1728
|
+
identityBytes32,
|
|
1729
|
+
entry: plan.entry.entry,
|
|
1730
|
+
entryMicro: plan.entry.entryMicro,
|
|
1731
|
+
status: sent.status === "FAILED" ? "failed" : "pending",
|
|
1732
|
+
txHash: sent.txHash,
|
|
1733
|
+
epochPrizePool: plan.clientParams.epochPrizePool,
|
|
1734
|
+
usdc: plan.clientParams.usdc,
|
|
1735
|
+
via: "service"
|
|
1736
|
+
};
|
|
1737
|
+
if (!sent.txHash) return base;
|
|
1738
|
+
try {
|
|
1739
|
+
const confirmed = await deps.http().post("/epochs/enter/confirm", {
|
|
1740
|
+
series,
|
|
1741
|
+
identity,
|
|
1742
|
+
txHash: sent.txHash,
|
|
1743
|
+
...epochPrizePool ? { epochPrizePool } : {}
|
|
1744
|
+
});
|
|
1745
|
+
return {
|
|
1746
|
+
...base,
|
|
1747
|
+
epochId: confirmed.entry.epochId,
|
|
1748
|
+
epochIdSource: confirmed.entry.epochIdSource,
|
|
1749
|
+
status: confirmed.entry.status,
|
|
1750
|
+
identityEntryCount: confirmed.entry.identityEntryCount
|
|
1751
|
+
};
|
|
1752
|
+
} catch {
|
|
1753
|
+
return base;
|
|
1754
|
+
}
|
|
1755
|
+
},
|
|
1756
|
+
async claimableRefund(input) {
|
|
1757
|
+
const series = requireSeries(input?.series);
|
|
1758
|
+
const payer = requireAddr(input?.payer, "payer");
|
|
1759
|
+
const epochId = requirePastEpochId(input?.epochId);
|
|
1760
|
+
const cfg = deps.config();
|
|
1761
|
+
const seriesBytes32 = seriesToBytes32(series);
|
|
1762
|
+
const epochPrizePool = input.epochPrizePool ?? cfg.contracts?.epochPrizePool ?? null;
|
|
1763
|
+
if (cfg.mock) {
|
|
1764
|
+
return {
|
|
1765
|
+
series,
|
|
1766
|
+
epochId,
|
|
1767
|
+
payer,
|
|
1768
|
+
claimable: "0.00",
|
|
1769
|
+
claimableMicro: "0",
|
|
1770
|
+
seriesBytes32,
|
|
1771
|
+
epochPrizePool,
|
|
1772
|
+
via: "mock",
|
|
1773
|
+
mock: true
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
const pool = requireEpochPool(input, cfg);
|
|
1777
|
+
const data = viem.encodeFunctionData({
|
|
1778
|
+
abi: epochPrizePoolViewAbi,
|
|
1779
|
+
functionName: "claimableRefund",
|
|
1780
|
+
args: [seriesBytes32, BigInt(epochId), payer]
|
|
1781
|
+
});
|
|
1782
|
+
const read = deps.readView;
|
|
1783
|
+
if (!read) {
|
|
1784
|
+
throw new ConfigError(
|
|
1785
|
+
"epochs.claimableRefund needs a wallet or RPC to read EpochPrizePool \u2014 pass `wallet` to the Playmos client",
|
|
1786
|
+
{ field: "wallet" }
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
const raw = await read(pool, data);
|
|
1790
|
+
const amount = viem.decodeFunctionResult({
|
|
1791
|
+
abi: epochPrizePoolViewAbi,
|
|
1792
|
+
functionName: "claimableRefund",
|
|
1793
|
+
data: raw
|
|
1794
|
+
});
|
|
1795
|
+
return {
|
|
1796
|
+
series,
|
|
1797
|
+
epochId,
|
|
1798
|
+
payer,
|
|
1799
|
+
claimable: formatMicroToUsd(amount),
|
|
1800
|
+
claimableMicro: amount.toString(),
|
|
1801
|
+
seriesBytes32,
|
|
1802
|
+
epochPrizePool: pool,
|
|
1803
|
+
via: "chain"
|
|
1804
|
+
};
|
|
1805
|
+
},
|
|
1806
|
+
async claimRefund(input) {
|
|
1807
|
+
const series = requireSeries(input?.series);
|
|
1808
|
+
const payer = requireAddr(input?.payer, "payer");
|
|
1809
|
+
const epochId = requirePastEpochId(input?.epochId);
|
|
1810
|
+
const cfg = deps.config();
|
|
1811
|
+
const pool = requireEpochPool(input, cfg);
|
|
1812
|
+
if (cfg.mock) {
|
|
1813
|
+
return {
|
|
1814
|
+
series,
|
|
1815
|
+
epochId,
|
|
1816
|
+
payer,
|
|
1817
|
+
txHash: MOCK_TX,
|
|
1818
|
+
status: "confirmed",
|
|
1819
|
+
epochPrizePool: pool,
|
|
1820
|
+
via: "mock",
|
|
1821
|
+
mock: true
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
const send = deps.sendEntry;
|
|
1825
|
+
if (!send) {
|
|
1826
|
+
throw new ConfigError(
|
|
1827
|
+
"epochs.claimRefund needs a player wallet \u2014 pass `wallet` to the Playmos client. The credit lands on `payer`; the connected wallet only signs.",
|
|
1828
|
+
{ field: "wallet" }
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
const call = buildEpochClaimRefundCall({
|
|
1832
|
+
epochPrizePool: pool,
|
|
1833
|
+
series,
|
|
1834
|
+
epochId: BigInt(epochId),
|
|
1835
|
+
payer
|
|
1836
|
+
});
|
|
1837
|
+
const sent = await send([call], {});
|
|
1838
|
+
return {
|
|
1839
|
+
series,
|
|
1840
|
+
epochId,
|
|
1841
|
+
payer,
|
|
1842
|
+
txHash: sent.txHash,
|
|
1843
|
+
status: walletStatus(sent.status),
|
|
1844
|
+
epochPrizePool: pool,
|
|
1845
|
+
via: "chain"
|
|
1846
|
+
};
|
|
1847
|
+
},
|
|
1848
|
+
async withdraw(input = {}) {
|
|
1849
|
+
const cfg = deps.config();
|
|
1850
|
+
const pool = requireEpochPool(input, cfg);
|
|
1851
|
+
if (cfg.mock) {
|
|
1852
|
+
return {
|
|
1853
|
+
epochPrizePool: pool,
|
|
1854
|
+
txHash: MOCK_TX,
|
|
1855
|
+
status: "confirmed",
|
|
1856
|
+
via: "mock",
|
|
1857
|
+
mock: true
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
const send = deps.sendEntry;
|
|
1861
|
+
if (!send) {
|
|
1862
|
+
throw new ConfigError(
|
|
1863
|
+
"epochs.withdraw needs the payer wallet \u2014 pass `wallet` to the Playmos client. USDC is pulled to msg.sender on EpochPrizePool, not PrizePool.",
|
|
1864
|
+
{ field: "wallet" }
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
const creditedMicro = await readWithdrawableMicro(deps, pool);
|
|
1868
|
+
const call = buildEpochWithdrawCall(pool);
|
|
1869
|
+
const sent = await send([call], {});
|
|
1870
|
+
return {
|
|
1871
|
+
epochPrizePool: pool,
|
|
1872
|
+
...creditedMicro === null ? {} : {
|
|
1873
|
+
amount: formatMicroToUsd(creditedMicro),
|
|
1874
|
+
amountMicro: creditedMicro.toString()
|
|
1875
|
+
},
|
|
1876
|
+
txHash: sent.txHash,
|
|
1877
|
+
status: walletStatus(sent.status),
|
|
1878
|
+
via: "chain"
|
|
1879
|
+
};
|
|
1880
|
+
},
|
|
1881
|
+
async getAttestation(input) {
|
|
1882
|
+
const series = requireSeries(input?.series);
|
|
1883
|
+
const epochId = requirePastEpochId(input?.epochId);
|
|
1884
|
+
const cfg = deps.config();
|
|
1885
|
+
if (cfg.mock) {
|
|
1886
|
+
const pool = input.epochPrizePool ?? cfg.contracts?.epochPrizePool;
|
|
1887
|
+
if (!pool) {
|
|
1888
|
+
throw new ConfigError("epochs.getAttestation needs contracts.epochPrizePool (or pass epochPrizePool)", {
|
|
1889
|
+
field: "epochPrizePool"
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
return {
|
|
1893
|
+
series,
|
|
1894
|
+
epochId,
|
|
1895
|
+
winners: input.winners ?? [],
|
|
1896
|
+
amountsMicro: input.amountsMicro ?? [],
|
|
1897
|
+
signature: input.signature ?? `0x${"00".repeat(65)}`,
|
|
1898
|
+
epochPrizePool: requireAddr(pool, "epochPrizePool"),
|
|
1899
|
+
via: "mock",
|
|
1900
|
+
mock: true
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
const path = `/epochs/${encodeURIComponent(epochId)}/attestation${qs({
|
|
1904
|
+
series,
|
|
1905
|
+
epochPrizePool: input.epochPrizePool ?? cfg.contracts?.epochPrizePool
|
|
1906
|
+
})}`;
|
|
1907
|
+
const res = await deps.http().get(path);
|
|
1908
|
+
const inner = res.attestation ?? res;
|
|
1909
|
+
if (!inner.series || !inner.winners || !inner.amountsMicro || !inner.signature || !inner.epochPrizePool) {
|
|
1910
|
+
throw new ConfigError("attestation response missing posted fields \u2014 refusing to invent a podium");
|
|
1911
|
+
}
|
|
1912
|
+
return {
|
|
1913
|
+
series: inner.series,
|
|
1914
|
+
epochId: String(inner.epochId ?? epochId),
|
|
1915
|
+
winners: inner.winners,
|
|
1916
|
+
amountsMicro: inner.amountsMicro,
|
|
1917
|
+
signature: inner.signature,
|
|
1918
|
+
epochPrizePool: inner.epochPrizePool,
|
|
1919
|
+
...res.terminal !== void 0 ? { terminal: res.terminal } : {},
|
|
1920
|
+
...res.dueAt !== void 0 ? { dueAt: res.dueAt } : {},
|
|
1921
|
+
via: res.via ?? "service"
|
|
1922
|
+
};
|
|
1923
|
+
},
|
|
1924
|
+
async executeSettlement(input) {
|
|
1925
|
+
const series = requireSeries(input?.series);
|
|
1926
|
+
const epochId = requirePastEpochId(input?.epochId);
|
|
1927
|
+
if (!Array.isArray(input?.winners)) throw new MissingFieldError("winners");
|
|
1928
|
+
if (!Array.isArray(input?.amounts)) throw new MissingFieldError("amounts");
|
|
1929
|
+
if (typeof input?.signature !== "string" || !/^0x[0-9a-fA-F]{130}$/.test(input.signature)) {
|
|
1930
|
+
throw new MissingFieldError("signature");
|
|
1931
|
+
}
|
|
1932
|
+
const winners = input.winners.map((w, i) => requireAddrKeepCase(w, `winners[${i}]`));
|
|
1933
|
+
const amountsMicro = input.amounts.map((a, i) => {
|
|
1934
|
+
parseMicroString(a, `amounts[${i}]`);
|
|
1935
|
+
return a;
|
|
1936
|
+
});
|
|
1937
|
+
const amounts = amountsMicro.map((a, i) => parseMicroString(a, `amounts[${i}]`));
|
|
1938
|
+
const cfg = deps.config();
|
|
1939
|
+
const pool = requireEpochPool(input, cfg);
|
|
1940
|
+
if (cfg.mock) {
|
|
1941
|
+
return {
|
|
1942
|
+
series,
|
|
1943
|
+
epochId,
|
|
1944
|
+
winners,
|
|
1945
|
+
amountsMicro,
|
|
1946
|
+
signature: input.signature,
|
|
1947
|
+
txHash: MOCK_TX,
|
|
1948
|
+
status: "confirmed",
|
|
1949
|
+
epochPrizePool: pool,
|
|
1950
|
+
via: "mock",
|
|
1951
|
+
mock: true
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
const send = deps.sendEntry;
|
|
1955
|
+
if (!send) {
|
|
1956
|
+
throw new ConfigError(
|
|
1957
|
+
"epochs.executeSettlement needs a wallet \u2014 pass `wallet` to the Playmos client. The SDK relays the signed podium; it does not choose winners.",
|
|
1958
|
+
{ field: "wallet" }
|
|
1959
|
+
);
|
|
1960
|
+
}
|
|
1961
|
+
const call = buildEpochExecuteSettlementCall({
|
|
1962
|
+
epochPrizePool: pool,
|
|
1963
|
+
series,
|
|
1964
|
+
epochId: BigInt(epochId),
|
|
1965
|
+
winners,
|
|
1966
|
+
amounts,
|
|
1967
|
+
signature: input.signature
|
|
1968
|
+
});
|
|
1969
|
+
let sent;
|
|
1970
|
+
try {
|
|
1971
|
+
sent = await send([call], {});
|
|
1972
|
+
} catch (e) {
|
|
1973
|
+
const msg = e?.message ?? String(e);
|
|
1974
|
+
throw new PaymentFailedError("EpochPrizePool.executeSignedSettlement failed.", { cause: msg });
|
|
1975
|
+
}
|
|
1976
|
+
const terminal = await readEpochTerminal(deps, pool, series, epochId);
|
|
1977
|
+
return {
|
|
1978
|
+
series,
|
|
1979
|
+
epochId,
|
|
1980
|
+
winners,
|
|
1981
|
+
amountsMicro,
|
|
1982
|
+
signature: input.signature,
|
|
1983
|
+
txHash: sent.txHash,
|
|
1984
|
+
status: walletStatus(sent.status),
|
|
1985
|
+
...terminal === null ? {} : { terminal, settled: terminal === "settled" },
|
|
1986
|
+
epochPrizePool: pool,
|
|
1987
|
+
via: "chain"
|
|
1988
|
+
};
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
var C1_ENTER_SPLIT_MICRO = {
|
|
1993
|
+
entry: 1000000n,
|
|
1994
|
+
fee: 100000n,
|
|
1995
|
+
pool: 600000n,
|
|
1996
|
+
outgoingSeed: 300000n,
|
|
1997
|
+
incomingSeed: 0n
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2000
|
+
// src/x402.ts
|
|
2001
|
+
var ADDRESS_RE3 = /^0x[0-9a-fA-F]{40}$/;
|
|
898
2002
|
function requireAddress2(value, field) {
|
|
899
2003
|
if (typeof value !== "string" || value.trim() === "") {
|
|
900
2004
|
throw new MissingFieldError(field);
|
|
901
2005
|
}
|
|
902
|
-
if (!
|
|
2006
|
+
if (!ADDRESS_RE3.test(value)) {
|
|
903
2007
|
throw new ConfigError(`${field} must be a 0x-prefixed 20-byte address, got: ${JSON.stringify(value)}`, {
|
|
904
2008
|
field,
|
|
905
2009
|
value
|
|
@@ -1037,7 +2141,7 @@ function validateX402ChallengeInput(input) {
|
|
|
1037
2141
|
}
|
|
1038
2142
|
|
|
1039
2143
|
// src/client.ts
|
|
1040
|
-
var
|
|
2144
|
+
var ADDRESS_RE4 = /^0x[0-9a-fA-F]{40}$/;
|
|
1041
2145
|
function mapAlreadyEntered(e) {
|
|
1042
2146
|
const msg = e instanceof Error ? e.message : typeof e === "object" && e && "message" in e ? String(e.message) : String(e);
|
|
1043
2147
|
const detail = e instanceof PaymentFailedError || e instanceof ApiError ? e.detail : void 0;
|
|
@@ -1085,7 +2189,7 @@ function requireAddressField(value, field) {
|
|
|
1085
2189
|
if (typeof value !== "string" || value.trim() === "") {
|
|
1086
2190
|
throw new MissingFieldError(field);
|
|
1087
2191
|
}
|
|
1088
|
-
if (!
|
|
2192
|
+
if (!ADDRESS_RE4.test(value)) {
|
|
1089
2193
|
throw new ConfigError(
|
|
1090
2194
|
`${field} must be a 0x-prefixed 20-byte wallet address (Phase 1a transfers settle server-held wallets), got: ${JSON.stringify(value)}`,
|
|
1091
2195
|
{ field, value }
|
|
@@ -1124,6 +2228,56 @@ var Playmos = class {
|
|
|
1124
2228
|
);
|
|
1125
2229
|
}
|
|
1126
2230
|
};
|
|
2231
|
+
/**
|
|
2232
|
+
* Rolling epochs — `currentId` / `prize` / `get` / `getSeries` (sdk#629),
|
|
2233
|
+
* `enter` (sdk#635 / S2), operator `settle` (sdk#639 / S3), the C3
|
|
2234
|
+
* refund pull (sdk#634), and E1b `getAttestation` / `executeSettlement`
|
|
2235
|
+
* (wallet-direct via the same `walletProvider()` funnel as `rounds.withdraw`).
|
|
2236
|
+
* Reads/settle go through the service. Refund claim, withdraw, and signed
|
|
2237
|
+
* execute are wallet-direct on EpochPrizePool — not PrizePool.
|
|
2238
|
+
* Nothing here opens a round.
|
|
2239
|
+
*/
|
|
2240
|
+
this.epochs = createEpochsApi({
|
|
2241
|
+
http: () => this.http,
|
|
2242
|
+
config: () => this.config,
|
|
2243
|
+
assertSecretKey: (surface) => this.assertSecretKey(surface),
|
|
2244
|
+
sendEntry: async (calls, opts) => {
|
|
2245
|
+
const provider = this.walletProvider();
|
|
2246
|
+
if (this.config.gas?.mode === "player") {
|
|
2247
|
+
await assertEnoughGas(provider, await getAccount(provider));
|
|
2248
|
+
}
|
|
2249
|
+
const from = await getAccount(provider);
|
|
2250
|
+
const paymasterUrl = this.config.gas?.mode === "player" ? void 0 : this.config.gas?.paymasterUrl ?? opts.paymasterUrl;
|
|
2251
|
+
const { id } = await sendCalls(provider, from, this.env.chainId, calls, paymasterUrl);
|
|
2252
|
+
return waitForCalls(provider, id);
|
|
2253
|
+
},
|
|
2254
|
+
walletAddress: async () => getAccount(this.walletProvider()),
|
|
2255
|
+
readView: async (to, data) => {
|
|
2256
|
+
if (await walletAvailable(this.config.wallet, this.walletTimeoutPolicy())) {
|
|
2257
|
+
const provider = this.walletProvider();
|
|
2258
|
+
return await provider.request({
|
|
2259
|
+
method: "eth_call",
|
|
2260
|
+
params: [{ to, data }, "latest"]
|
|
2261
|
+
});
|
|
2262
|
+
}
|
|
2263
|
+
const rpc = this.env.network === "base" ? "https://mainnet.base.org" : "https://sepolia.base.org";
|
|
2264
|
+
const res = await fetch(rpc, {
|
|
2265
|
+
method: "POST",
|
|
2266
|
+
headers: { "content-type": "application/json" },
|
|
2267
|
+
body: JSON.stringify({
|
|
2268
|
+
jsonrpc: "2.0",
|
|
2269
|
+
id: 1,
|
|
2270
|
+
method: "eth_call",
|
|
2271
|
+
params: [{ to, data }, "latest"]
|
|
2272
|
+
})
|
|
2273
|
+
});
|
|
2274
|
+
const json = await res.json();
|
|
2275
|
+
if (!json.result) {
|
|
2276
|
+
throw new ApiError(`eth_call failed: ${json.error?.message ?? "no result"}`, { to });
|
|
2277
|
+
}
|
|
2278
|
+
return json.result;
|
|
2279
|
+
}
|
|
2280
|
+
});
|
|
1127
2281
|
this.payouts = {
|
|
1128
2282
|
/** Choose how the studio is paid: "usdc" (default) or "fiat" (Bridge). Secret key only (#264). */
|
|
1129
2283
|
setMode: async (mode) => {
|
|
@@ -1289,6 +2443,8 @@ var Playmos = class {
|
|
|
1289
2443
|
this.mockPotMicro = /* @__PURE__ */ new Map();
|
|
1290
2444
|
/** Persisted settle winners for re-settle replay (#490 C2) — never re-invent pool-to-everyone. */
|
|
1291
2445
|
this.mockSettleWinners = /* @__PURE__ */ new Map();
|
|
2446
|
+
/** L34 notices after mock settle — funded only with a push txHash. */
|
|
2447
|
+
this.mockPayoutNotices = /* @__PURE__ */ new Map();
|
|
1292
2448
|
this.rounds = {
|
|
1293
2449
|
open: async (input) => {
|
|
1294
2450
|
if (!this.config.mock) this.assertSecretKey("playmos.rounds.open");
|
|
@@ -1454,6 +2610,16 @@ var Playmos = class {
|
|
|
1454
2610
|
};
|
|
1455
2611
|
this.mockRounds.set(input.roundId, settled);
|
|
1456
2612
|
this.mockSettleWinners.set(input.roundId, winners);
|
|
2613
|
+
this.mockPayoutNotices.set(
|
|
2614
|
+
input.roundId,
|
|
2615
|
+
rows.filter((w) => w.micro > 0n).map((w) => ({
|
|
2616
|
+
roundId: input.roundId,
|
|
2617
|
+
wallet: w.wallet.toLowerCase(),
|
|
2618
|
+
amountMicro: w.micro.toString(),
|
|
2619
|
+
status: "claimable",
|
|
2620
|
+
reason: "prizepool_pull_credit"
|
|
2621
|
+
}))
|
|
2622
|
+
);
|
|
1457
2623
|
for (const w of rows) {
|
|
1458
2624
|
if (w.micro <= 0n) continue;
|
|
1459
2625
|
const k = `${input.roundId}:${w.wallet.toLowerCase()}`;
|
|
@@ -1695,6 +2861,93 @@ var Playmos = class {
|
|
|
1695
2861
|
}
|
|
1696
2862
|
return body.active;
|
|
1697
2863
|
},
|
|
2864
|
+
/**
|
|
2865
|
+
* L34 / sdk#610 — notifications for a settled round.
|
|
2866
|
+
* Each win is `funded` (push txHash) or `claimable` (withdraw fallback).
|
|
2867
|
+
* Never returns `funded` without a transfer txHash.
|
|
2868
|
+
*/
|
|
2869
|
+
payoutNotices: async (input) => {
|
|
2870
|
+
requireField(input?.roundId, "roundId");
|
|
2871
|
+
if (this.config.mock) {
|
|
2872
|
+
const existing = this.mockRounds.get(input.roundId);
|
|
2873
|
+
if (!existing) {
|
|
2874
|
+
throw new ApiError(`unknown round: ${input.roundId}`, { status: 404, code: "not_found" });
|
|
2875
|
+
}
|
|
2876
|
+
let notices = this.mockPayoutNotices.get(input.roundId) ?? [];
|
|
2877
|
+
if (input.wallet) {
|
|
2878
|
+
if (!ADDRESS_RE4.test(input.wallet)) {
|
|
2879
|
+
throw new ConfigError("wallet must be a 0x-prefixed 20-byte address");
|
|
2880
|
+
}
|
|
2881
|
+
const want = input.wallet.toLowerCase();
|
|
2882
|
+
notices = notices.filter((n) => n.wallet === want);
|
|
2883
|
+
}
|
|
2884
|
+
for (const n of notices) {
|
|
2885
|
+
if (n.status === "funded" && !n.txHash) {
|
|
2886
|
+
throw new ApiError("cannot label funded without a transfer txHash", {
|
|
2887
|
+
status: 500,
|
|
2888
|
+
code: "payout_notice_invalid"
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
return { roundId: input.roundId, status: existing.status, notices };
|
|
2893
|
+
}
|
|
2894
|
+
const q = input.wallet ? `?wallet=${encodeURIComponent(input.wallet)}` : "";
|
|
2895
|
+
return this.http.get(
|
|
2896
|
+
`/rounds/${encodeURIComponent(input.roundId)}/payout-notices${q}`
|
|
2897
|
+
);
|
|
2898
|
+
},
|
|
2899
|
+
/**
|
|
2900
|
+
* Operator push-attempt after settle (L34). Mock: mark a winner funded only
|
|
2901
|
+
* when a real-shaped txHash is supplied; otherwise that row stays claimable.
|
|
2902
|
+
*/
|
|
2903
|
+
pushWinnings: async (input) => {
|
|
2904
|
+
requireField(input?.roundId, "roundId");
|
|
2905
|
+
if (!this.config.mock) this.assertSecretKey("playmos.rounds.pushWinnings");
|
|
2906
|
+
if (this.config.mock) {
|
|
2907
|
+
const existing = this.mockRounds.get(input.roundId);
|
|
2908
|
+
if (!existing) {
|
|
2909
|
+
throw new ApiError(`unknown round: ${input.roundId}`, { status: 404, code: "not_found" });
|
|
2910
|
+
}
|
|
2911
|
+
if (existing.status !== "settled") {
|
|
2912
|
+
throw new ApiError(`round ${input.roundId} is not settled \u2014 push after the clock`, {
|
|
2913
|
+
status: 409,
|
|
2914
|
+
code: "conflict"
|
|
2915
|
+
});
|
|
2916
|
+
}
|
|
2917
|
+
const prior = this.mockPayoutNotices.get(input.roundId) ?? [];
|
|
2918
|
+
const byWallet = new Map(
|
|
2919
|
+
(input.pushes ?? []).map((p) => [p.wallet.toLowerCase(), p])
|
|
2920
|
+
);
|
|
2921
|
+
const TX_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
2922
|
+
const ZERO = "0x" + "00".repeat(32);
|
|
2923
|
+
const notices = prior.map((n) => {
|
|
2924
|
+
const p = byWallet.get(n.wallet);
|
|
2925
|
+
if (p && !p.failed && p.txHash && TX_RE.test(p.txHash) && p.txHash.toLowerCase() !== ZERO) {
|
|
2926
|
+
return {
|
|
2927
|
+
...n,
|
|
2928
|
+
status: "funded",
|
|
2929
|
+
txHash: p.txHash.toLowerCase(),
|
|
2930
|
+
reason: void 0
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2933
|
+
if (p && (p.failed || !p.txHash || !TX_RE.test(p.txHash) || p.txHash.toLowerCase() === ZERO)) {
|
|
2934
|
+
return {
|
|
2935
|
+
...n,
|
|
2936
|
+
status: "claimable",
|
|
2937
|
+
txHash: void 0,
|
|
2938
|
+
reason: p.failed ? "push_failed" : "push_missing_txHash"
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
return n;
|
|
2942
|
+
});
|
|
2943
|
+
this.mockPayoutNotices.set(input.roundId, notices);
|
|
2944
|
+
return { roundId: input.roundId, status: existing.status, notices };
|
|
2945
|
+
}
|
|
2946
|
+
return this.http.post(
|
|
2947
|
+
`/rounds/${encodeURIComponent(input.roundId)}/push-winnings`,
|
|
2948
|
+
{}
|
|
2949
|
+
);
|
|
2950
|
+
},
|
|
1698
2951
|
/**
|
|
1699
2952
|
* Read a wallet's **claimable** prize balance for a round (issue #41).
|
|
1700
2953
|
* Prefers the service chain read (`GET /v1/rounds/:id/prize?wallet=`); falls
|
|
@@ -1751,7 +3004,7 @@ var Playmos = class {
|
|
|
1751
3004
|
const existing = this.mockRounds.get(input.roundId);
|
|
1752
3005
|
if (existing?.prizePoolAddress) prizePool2 = existing.prizePoolAddress;
|
|
1753
3006
|
}
|
|
1754
|
-
if (!input.wallet || !
|
|
3007
|
+
if (!input.wallet || !ADDRESS_RE4.test(input.wallet)) {
|
|
1755
3008
|
throw new ConfigError(
|
|
1756
3009
|
"mock rounds.withdraw requires wallet (0x\u2026) \u2014 live uses the connected provider; without wallet mock would pay the first non-zero credit to the wrong player (#495)"
|
|
1757
3010
|
);
|
|
@@ -2037,8 +3290,16 @@ var Playmos = class {
|
|
|
2037
3290
|
/**
|
|
2038
3291
|
* Offline mock payments for this client instance — so `verify(id)` after
|
|
2039
3292
|
* `mock: true` pay/enterRound does not hit the live API (#204).
|
|
3293
|
+
* Keyed by payment id for verify.
|
|
2040
3294
|
*/
|
|
2041
3295
|
this.mockPayments = /* @__PURE__ */ new Map();
|
|
3296
|
+
/**
|
|
3297
|
+
* sdk#513 / residual B1–B2 — held idempotencyKey offline (live contract rehearsal).
|
|
3298
|
+
* Keyed like service: `${gameId}:${key}` (IAP without gameId uses empty gameId).
|
|
3299
|
+
* Stores terms so same key + different money throws (live IdempotencyConflict), not silent replay.
|
|
3300
|
+
* Auto-minted keys still unique per call (not stored for cross-call replay).
|
|
3301
|
+
*/
|
|
3302
|
+
this.mockByIdempotencyKey = /* @__PURE__ */ new Map();
|
|
2042
3303
|
/**
|
|
2043
3304
|
* `transfers` — read-back / confirmation for a prior `transfer()` (issues #27, #47).
|
|
2044
3305
|
* `get` reconciles once against chain truth; `wait` polls it to a terminal state
|
|
@@ -2223,18 +3484,62 @@ var Playmos = class {
|
|
|
2223
3484
|
}
|
|
2224
3485
|
};
|
|
2225
3486
|
}
|
|
2226
|
-
|
|
2227
|
-
|
|
3487
|
+
mockIdemScopeKey(gameId, heldKey) {
|
|
3488
|
+
return `${gameId ?? ""}:${heldKey}`;
|
|
3489
|
+
}
|
|
3490
|
+
rememberMock(payment, held) {
|
|
3491
|
+
if (payment.mock) {
|
|
3492
|
+
this.mockPayments.set(payment.id, payment);
|
|
3493
|
+
if (held) {
|
|
3494
|
+
this.mockByIdempotencyKey.set(this.mockIdemScopeKey(held.terms.gameId, held.key), {
|
|
3495
|
+
payment,
|
|
3496
|
+
terms: held.terms
|
|
3497
|
+
});
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
2228
3500
|
return payment;
|
|
2229
3501
|
}
|
|
3502
|
+
/** Live-shaped conflict when held mock key is reused with different money terms (sdk#513 residual B1). */
|
|
3503
|
+
mockIdemReplayOrThrow(heldKey, terms) {
|
|
3504
|
+
const prior = this.mockByIdempotencyKey.get(this.mockIdemScopeKey(terms.gameId, heldKey));
|
|
3505
|
+
if (!prior) return null;
|
|
3506
|
+
const t = prior.terms;
|
|
3507
|
+
const same = t.kind === terms.kind && t.amount === terms.amount && t.gameId === terms.gameId && t.playerId === terms.playerId && t.roundId === terms.roundId && t.identity === terms.identity && t.sku === terms.sku;
|
|
3508
|
+
if (!same) {
|
|
3509
|
+
throw new ApiError(
|
|
3510
|
+
`idempotencyKey was already used for a payment with DIFFERENT terms (kind/amount/gameId/playerId/roundId/identity/sku); use a fresh key (mock:true mirrors live conflict \u2014 sdk#513).`,
|
|
3511
|
+
{
|
|
3512
|
+
code: "idempotency_conflict",
|
|
3513
|
+
idempotencyKey: heldKey,
|
|
3514
|
+
priorKind: t.kind,
|
|
3515
|
+
priorAmount: t.amount
|
|
3516
|
+
}
|
|
3517
|
+
);
|
|
3518
|
+
}
|
|
3519
|
+
return prior.payment;
|
|
3520
|
+
}
|
|
2230
3521
|
/** External-studio IAP (1%). Returns a real confirmed Payment with a txHash. */
|
|
2231
3522
|
async pay(input) {
|
|
2232
3523
|
const amountMicro = validateAmount(input.amount);
|
|
2233
3524
|
requireField(input.sku, "sku");
|
|
2234
3525
|
requireField(input.playerId, "playerId");
|
|
2235
3526
|
const metadata = validateMetadata(input.metadata);
|
|
2236
|
-
const
|
|
3527
|
+
const heldIdem = typeof input.idempotencyKey === "string" && input.idempotencyKey.trim() ? input.idempotencyKey.trim() : void 0;
|
|
3528
|
+
const idempotencyKey = heldIdem ?? prefixedId("idem");
|
|
2237
3529
|
if (this.config.mock) {
|
|
3530
|
+
const terms = {
|
|
3531
|
+
kind: "iap",
|
|
3532
|
+
amount: formatMicroToUsd(amountMicro),
|
|
3533
|
+
gameId: input.gameId ?? "",
|
|
3534
|
+
playerId: input.playerId,
|
|
3535
|
+
roundId: "",
|
|
3536
|
+
identity: "",
|
|
3537
|
+
sku: input.sku.trim()
|
|
3538
|
+
};
|
|
3539
|
+
if (heldIdem) {
|
|
3540
|
+
const replay = this.mockIdemReplayOrThrow(heldIdem, terms);
|
|
3541
|
+
if (replay) return replay;
|
|
3542
|
+
}
|
|
2238
3543
|
return this.rememberMock(
|
|
2239
3544
|
mockIapPayment({
|
|
2240
3545
|
amountMicro,
|
|
@@ -2243,7 +3548,8 @@ var Playmos = class {
|
|
|
2243
3548
|
playerId: input.playerId,
|
|
2244
3549
|
chain: this.env.network,
|
|
2245
3550
|
metadata
|
|
2246
|
-
})
|
|
3551
|
+
}),
|
|
3552
|
+
heldIdem ? { key: heldIdem, terms } : void 0
|
|
2247
3553
|
);
|
|
2248
3554
|
}
|
|
2249
3555
|
if (this.env.isTest && !await walletAvailable(this.config.wallet, this.walletTimeoutPolicy())) {
|
|
@@ -2265,7 +3571,13 @@ var Playmos = class {
|
|
|
2265
3571
|
);
|
|
2266
3572
|
let payment = this.mapServerPayment(res.payment, "iap", amountMicro);
|
|
2267
3573
|
if (payment.status === "pending" || payment.status === "created") {
|
|
2268
|
-
payment = await this.waitForServerSettle(
|
|
3574
|
+
payment = await this.waitForServerSettle(
|
|
3575
|
+
payment.id,
|
|
3576
|
+
"iap",
|
|
3577
|
+
amountMicro,
|
|
3578
|
+
input.settleTimeoutMs ?? 6e4,
|
|
3579
|
+
input.signal
|
|
3580
|
+
);
|
|
2269
3581
|
}
|
|
2270
3582
|
return payment;
|
|
2271
3583
|
}
|
|
@@ -2303,15 +3615,29 @@ var Playmos = class {
|
|
|
2303
3615
|
const { txHash } = await waitForCalls(provider, callsId);
|
|
2304
3616
|
return this.settle(intent.payment.id, txHash);
|
|
2305
3617
|
}
|
|
2306
|
-
/**
|
|
3618
|
+
/** Skill-game prize-pool entry. Live sandbox / Playmos Lab uses 60/30/10. Studio contest take is 1% (not a live separate pool yet). Closes #343. */
|
|
2307
3619
|
async enterRound(input) {
|
|
2308
3620
|
const amountMicro = validateAmount(input.amount);
|
|
2309
3621
|
requireField(input.gameId, "gameId");
|
|
2310
3622
|
requireField(input.roundId, "roundId");
|
|
2311
3623
|
requireField(input.playerId, "playerId");
|
|
2312
3624
|
const metadata = validateMetadata(input.metadata);
|
|
2313
|
-
const
|
|
3625
|
+
const heldIdem = typeof input.idempotencyKey === "string" && input.idempotencyKey.trim() ? input.idempotencyKey.trim() : void 0;
|
|
3626
|
+
const idempotencyKey = heldIdem ?? prefixedId("idem");
|
|
2314
3627
|
if (this.config.mock) {
|
|
3628
|
+
const terms = {
|
|
3629
|
+
kind: "entry",
|
|
3630
|
+
amount: formatMicroToUsd(amountMicro),
|
|
3631
|
+
gameId: input.gameId,
|
|
3632
|
+
playerId: input.playerId,
|
|
3633
|
+
roundId: input.roundId,
|
|
3634
|
+
identity: typeof input.identity === "string" ? input.identity.trim() : "",
|
|
3635
|
+
sku: ""
|
|
3636
|
+
};
|
|
3637
|
+
if (heldIdem) {
|
|
3638
|
+
const replay = this.mockIdemReplayOrThrow(heldIdem, terms);
|
|
3639
|
+
if (replay) return replay;
|
|
3640
|
+
}
|
|
2315
3641
|
this.mockAccumulateEntry(input.roundId, amountMicro);
|
|
2316
3642
|
return this.rememberMock(
|
|
2317
3643
|
mockEntryPayment({
|
|
@@ -2327,7 +3653,8 @@ var Playmos = class {
|
|
|
2327
3653
|
// B2 pins — must survive mock path for hub hasEntered parity (#204).
|
|
2328
3654
|
roundKey: input.roundKey,
|
|
2329
3655
|
identity: input.identity
|
|
2330
|
-
})
|
|
3656
|
+
}),
|
|
3657
|
+
heldIdem ? { key: heldIdem, terms } : void 0
|
|
2331
3658
|
);
|
|
2332
3659
|
}
|
|
2333
3660
|
const serverSettleNoWallet = this.env.isTest && !await walletAvailable(this.config.wallet, this.walletTimeoutPolicy());
|
|
@@ -2360,7 +3687,13 @@ var Playmos = class {
|
|
|
2360
3687
|
);
|
|
2361
3688
|
let payment2 = this.mapServerPayment(res.payment, "entry", amountMicro);
|
|
2362
3689
|
if (payment2.status === "pending" || payment2.status === "created") {
|
|
2363
|
-
payment2 = await this.waitForServerSettle(
|
|
3690
|
+
payment2 = await this.waitForServerSettle(
|
|
3691
|
+
payment2.id,
|
|
3692
|
+
"entry",
|
|
3693
|
+
amountMicro,
|
|
3694
|
+
input.settleTimeoutMs ?? 6e4,
|
|
3695
|
+
input.signal
|
|
3696
|
+
);
|
|
2364
3697
|
}
|
|
2365
3698
|
if (input.identity) payment2.identity = input.identity;
|
|
2366
3699
|
const pool = res.clientParams?.contractAddress ?? this.config.contracts?.prizePool;
|
|
@@ -2543,10 +3876,16 @@ var Playmos = class {
|
|
|
2543
3876
|
* until chain verify catches PaymentSettled (RPC lag) — poll so pay() matches
|
|
2544
3877
|
* the documented "confirmed" sandbox promise (TTFSC-T0 / cold-run).
|
|
2545
3878
|
*/
|
|
2546
|
-
async waitForServerSettle(paymentId, kind, amountMicro, timeoutMs = 6e4) {
|
|
3879
|
+
async waitForServerSettle(paymentId, kind, amountMicro, timeoutMs = 6e4, signal) {
|
|
2547
3880
|
const start = Date.now();
|
|
2548
3881
|
let delayMs = 400;
|
|
2549
3882
|
while (Date.now() - start < timeoutMs) {
|
|
3883
|
+
if (signal?.aborted) {
|
|
3884
|
+
throw new ApiError(
|
|
3885
|
+
`Aborted waiting for server-settle of ${paymentId}. Re-check with playmos.verify("${paymentId}").`,
|
|
3886
|
+
{ paymentId, aborted: true, asyncSettle: true }
|
|
3887
|
+
);
|
|
3888
|
+
}
|
|
2550
3889
|
const raw = await this.http.get(
|
|
2551
3890
|
`/payments/${encodeURIComponent(paymentId)}`
|
|
2552
3891
|
);
|
|
@@ -2561,12 +3900,46 @@ var Playmos = class {
|
|
|
2561
3900
|
{ paymentId, timeout: true, asyncSettle: true }
|
|
2562
3901
|
);
|
|
2563
3902
|
}
|
|
3903
|
+
/**
|
|
3904
|
+
* sdk#509 — recover a payment after a lost response by the idempotency key you held.
|
|
3905
|
+
* Requires secret sk_test_ (same as list). Returns null if not found.
|
|
3906
|
+
*/
|
|
3907
|
+
async findPaymentByIdempotencyKey(idempotencyKey, opts) {
|
|
3908
|
+
requireField(idempotencyKey, "idempotencyKey");
|
|
3909
|
+
if (this.config.mock) {
|
|
3910
|
+
return null;
|
|
3911
|
+
}
|
|
3912
|
+
const q = new URLSearchParams({ idempotencyKey });
|
|
3913
|
+
if (opts?.gameId) q.set("gameId", opts.gameId);
|
|
3914
|
+
try {
|
|
3915
|
+
const res = await this.http.get(
|
|
3916
|
+
`/payments?${q.toString()}`
|
|
3917
|
+
);
|
|
3918
|
+
const row = res.data?.[0];
|
|
3919
|
+
if (!row || typeof row.id !== "string") return null;
|
|
3920
|
+
const kind = row.kind === "entry" ? "entry" : "iap";
|
|
3921
|
+
const amountMicro = validateAmount(String(row.amount ?? "0.01"));
|
|
3922
|
+
return this.mapServerPayment(
|
|
3923
|
+
row,
|
|
3924
|
+
kind,
|
|
3925
|
+
amountMicro
|
|
3926
|
+
);
|
|
3927
|
+
} catch (e) {
|
|
3928
|
+
if (e instanceof ApiError && (e.detail?.status === 404 || e.detail?.code === "not_found" || /not found/i.test(e.message))) {
|
|
3929
|
+
return null;
|
|
3930
|
+
}
|
|
3931
|
+
throw e;
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
2564
3934
|
/**
|
|
2565
3935
|
* Map a server-settled payment (from the `settle: "server"` response) into the
|
|
2566
3936
|
* SDK's `Payment` shape. The service already signed + confirmed on-chain, so we
|
|
2567
|
-
* pass its authoritative `status`/`txHash`/amounts straight through
|
|
2568
|
-
*
|
|
2569
|
-
*
|
|
3937
|
+
* pass its authoritative `status`/`txHash`/amounts straight through.
|
|
3938
|
+
*
|
|
3939
|
+
* For entry, `Payment.split` is a **first-party 60/30/10 projection** from the
|
|
3940
|
+
* request amount using SDK BPS constants (same integer math as mock) — not a
|
|
3941
|
+
* chain read of pool constructor bps or post-lock payable pot (sdk#514 residual N1).
|
|
3942
|
+
* Prefer `chainAmount` / on-chain `getRound` for money truth.
|
|
2570
3943
|
*/
|
|
2571
3944
|
mapServerPayment(p, kind, amountMicro) {
|
|
2572
3945
|
const payment = {
|
|
@@ -2734,13 +4107,13 @@ function previewPoolSplit(amount) {
|
|
|
2734
4107
|
}
|
|
2735
4108
|
|
|
2736
4109
|
// src/settlement.ts
|
|
2737
|
-
var
|
|
4110
|
+
var ADDRESS_RE5 = /^0x[0-9a-fA-F]{40}$/;
|
|
2738
4111
|
var DEFAULT_TTL_MS = 15 * 60 * 1e3;
|
|
2739
4112
|
function requireAddress3(value, field) {
|
|
2740
4113
|
if (typeof value !== "string" || value.trim() === "") {
|
|
2741
4114
|
throw new MissingFieldError(field);
|
|
2742
4115
|
}
|
|
2743
|
-
if (!
|
|
4116
|
+
if (!ADDRESS_RE5.test(value)) {
|
|
2744
4117
|
throw new ConfigError(`${field} must be a 0x-prefixed 20-byte address, got: ${JSON.stringify(value)}`, {
|
|
2745
4118
|
field,
|
|
2746
4119
|
value
|
|
@@ -2829,6 +4202,7 @@ function isX402PayloadAuthorization(auth) {
|
|
|
2829
4202
|
exports.AlreadyEnteredError = AlreadyEnteredError;
|
|
2830
4203
|
exports.ApiError = ApiError;
|
|
2831
4204
|
exports.AuthError = AuthError;
|
|
4205
|
+
exports.C1_ENTER_SPLIT_MICRO = C1_ENTER_SPLIT_MICRO;
|
|
2832
4206
|
exports.CHAIN_ID = CHAIN_ID;
|
|
2833
4207
|
exports.ConfigError = ConfigError;
|
|
2834
4208
|
exports.DEFAULT_API_BASE_URL = DEFAULT_API_BASE_URL;
|
|
@@ -2837,6 +4211,7 @@ exports.InvalidAmountError = InvalidAmountError;
|
|
|
2837
4211
|
exports.MICRO_PER_USDC = MICRO_PER_USDC;
|
|
2838
4212
|
exports.MissingFieldError = MissingFieldError;
|
|
2839
4213
|
exports.NothingToWithdrawError = NothingToWithdrawError;
|
|
4214
|
+
exports.PLAYMOS_FEE_SINK_DEFAULT = PLAYMOS_FEE_SINK_DEFAULT;
|
|
2840
4215
|
exports.PaymentFailedError = PaymentFailedError;
|
|
2841
4216
|
exports.PayoutError = PayoutError;
|
|
2842
4217
|
exports.Playmos = Playmos;
|
|
@@ -2845,15 +4220,25 @@ exports.USDC_ADDRESS = USDC_ADDRESS;
|
|
|
2845
4220
|
exports.USDC_DECIMALS = USDC_DECIMALS;
|
|
2846
4221
|
exports.WalletConnectionError = WalletConnectionError;
|
|
2847
4222
|
exports.WalletTimeoutError = WalletTimeoutError;
|
|
4223
|
+
exports.buildEpochClaimRefundCall = buildEpochClaimRefundCall;
|
|
4224
|
+
exports.buildEpochEntryCalls = buildEpochEntryCalls;
|
|
4225
|
+
exports.buildEpochExecuteSettlementCall = buildEpochExecuteSettlementCall;
|
|
4226
|
+
exports.buildEpochWithdrawCall = buildEpochWithdrawCall;
|
|
2848
4227
|
exports.clientRuntimeSignals = clientRuntimeSignals;
|
|
2849
4228
|
exports.computeIapSplit = computeIapSplit;
|
|
2850
4229
|
exports.computePayout = computePayout;
|
|
2851
4230
|
exports.computePoolSplit = computePoolSplit;
|
|
4231
|
+
exports.createEpochsApi = createEpochsApi;
|
|
2852
4232
|
exports.createPaymentRequirement = createPaymentRequirement;
|
|
2853
4233
|
exports.createX402Challenge = createX402Challenge;
|
|
2854
4234
|
exports.decodePaymentHeader = decodePaymentHeader;
|
|
4235
|
+
exports.derivedEpochId = derivedEpochId;
|
|
4236
|
+
exports.encodeEpochPoolConstructorArgs = encodeEpochPoolConstructorArgs;
|
|
2855
4237
|
exports.encodePaymentHeader = encodePaymentHeader;
|
|
4238
|
+
exports.epochPayableMicro = epochPayableMicro;
|
|
4239
|
+
exports.epochPoolProofMessage = epochPoolProofMessage;
|
|
2856
4240
|
exports.formatMicroToUsd = formatMicroToUsd;
|
|
4241
|
+
exports.identityToBytes32 = identityToBytes32;
|
|
2857
4242
|
exports.isClientRuntime = isClientRuntime;
|
|
2858
4243
|
exports.isWalletSignatureAuthorization = isWalletSignatureAuthorization;
|
|
2859
4244
|
exports.isX402PayloadAuthorization = isX402PayloadAuthorization;
|
|
@@ -2861,6 +4246,7 @@ exports.networkToCaip2 = networkToCaip2;
|
|
|
2861
4246
|
exports.parsePaymentRequirement = parsePaymentRequirement;
|
|
2862
4247
|
exports.parseUsdToMicro = parseUsdToMicro;
|
|
2863
4248
|
exports.prefixedId = prefixedId;
|
|
4249
|
+
exports.prepareStudioPoolLocal = prepareStudioPoolLocal;
|
|
2864
4250
|
exports.previewEscrowFee = previewEscrowFee;
|
|
2865
4251
|
exports.previewIapSplit = previewIapSplit;
|
|
2866
4252
|
exports.previewMarketplaceSplit = previewMarketplaceSplit;
|
|
@@ -2868,6 +4254,8 @@ exports.previewPoolSplit = previewPoolSplit;
|
|
|
2868
4254
|
exports.previewTransferSplit = previewTransferSplit;
|
|
2869
4255
|
exports.resolveEnv = resolveEnv;
|
|
2870
4256
|
exports.serializePaymentRequirement = serializePaymentRequirement;
|
|
4257
|
+
exports.seriesToBytes32 = seriesToBytes32;
|
|
4258
|
+
exports.settlementTypedData = settlementTypedData;
|
|
2871
4259
|
exports.toX402PaymentRequired = toX402PaymentRequired;
|
|
2872
4260
|
exports.ulid = ulid;
|
|
2873
4261
|
exports.validateX402ChallengeInput = validateX402ChallengeInput;
|