(* WolframInstitute`Puzzles` :: Chain :: History

   The transactions a block explorer associates with an address, normalized to one shape
   across the Esplora, Blockscout and Wolfram-Cloud backends: transaction id, block
   height, time, confirmation state, and the backend that said so.

   Scope. This is the address's transaction LIST, not transaction contents: no inputs,
   outputs, scripts, fees or amounts. Reading a spending input is how the public key
   behind a spent P2PKH address is recovered, and that is a Derivation and Oracles
   subject, not a chain-lookup one. The list is also a page, not a full history - every
   backend paginates, and only the first page is read, capped by MaxItems.

   Delegated: the request, cache and normalisation layer is ChainExplorerQuery.

   Tests: Tests/Chain/History.wlt, against explorer responses captured verbatim, pinned
   on transaction ids that are immutable facts of the chain.

   See docs/en/Guides/Chain.md. *)


(* ::Section:: *)
(*PackageExported*)


PackageExported[ChainAddressTransactions]


(* ::Section:: *)
(*PackageScoped*)


(* none: the shared internals live in Explorer.wl *)


(* ::Section:: *)
(*UsageMessages*)


ChainAddressTransactions::usage =
	"ChainAddressTransactions[address] gives a list of Associations for the most recent transactions of address, each with its \"TransactionID\", \"BlockHeight\", \"Time\", \"Confirmed\" state and the \"Backend\" that reported it, most recent first.\nChainAddressTransactions[address, MaxItems -> n] gives at most n of them.\nChainAddressTransactions[address, \"Backends\" -> backends] asks only the named backends, in the order given.\nSupported cases: a Bitcoin base58 or bech32 address, or an Ethereum hex address; anything else is left unevaluated. Only the first page a backend serves is read, so the list is the recent history and not the complete one. If no backend answers, the result is a Failure, never an empty list."


(* ::Section:: *)
(*Function Definitions*)


(* ::Subsection:: *)
(*ChainAddressTransactions*)


Options[ChainAddressTransactions] =
	{"Backends" -> Automatic, "Cache" -> True, MaxItems -> 25, TimeConstraint -> 30}


(* An empty list is a real answer here - an address with no history - so it must be
   distinguishable from "nobody answered", which is a Failure. *)
ChainAddressTransactions[address_String?ChainAddressQ, opts : OptionsPattern[]] :=
	Module[
		{
			backends = ChainResolveBackends[
				OptionValue["Backends"], ChainAddressNetwork[address], "AddressTransactions"
			],
			state
		},
		state = ChainFirstSuccess[
			backends,
			ChainExplorerQuery[
				#, "AddressTransactions", address,
				"Cache" -> OptionValue["Cache"], TimeConstraint -> OptionValue[TimeConstraint]
			] &
		];
		If[ MissingQ[state["Result"]],
			ChainFailure[
				"ChainNoBackendResponded",
				"No backend answered for `Address`; `Tried` were tried.",
				<|"Address" -> address, "Tried" -> backends, "Failures" -> state["Failures"]|>
			],
			Take[state["Result"], UpTo[OptionValue[MaxItems]]]
		]
	]
