{ "p": "time-lapse", "op": "deploy", "tick": "TIMELAPSE", "name": "Timelapse", "icon": "⏳", "max": "(deploy_time - 1231006505)", "lim": "600", "self_mint": "600", "epoch": "31556952", "influx": "(block.time - last_block.time)", "activity_scope": "wallet", "user_weight": "((supply == 0) ? 0 : (((last_block.user_participation == 0) && (minted == max)) ? 1 : (user.balance / supply)))", "user_activity_weight": "(((block.time - user.last_active) <= epoch) ? 1 : 0)", "user_participation": "sum(users[i].weight * users[i].activity_weight)", "user_yield": "((block.user_participation == 0) ? 0 : (((block.influx + (time_capsule * ((block.time - last_block.time) / epoch))) * (user.weight * user.activity_weight)) / block.user_participation))", "user_distribution": "user.balance += user.yield", "time_capsule_distribution": "((block.user_participation == 0) ? (time_capsule += block.influx) : (time_capsule = max(0, (time_capsule - (time_capsule * ((block.time - last_block.time) / epoch))))))", "timekeeper_weight": "user_weight", "timekeeper_activity_weight": "user_activity_weight", "proposal_balance": "1%", "proposal_quorum": "50%", "proposal_duration": "1008", "deploy_time": "1767464105", "pk": "36eb070a82bf60e7d578b670a8de8587715c25d7b0192aca0ab86a242ff0d8fb", "hash_id": "29c628e809a7fef298b718d2224e05c9df74861aa436f309279b1c41491cb169i0", "nonce": "0", "sig": "505a41adb0bd683fb5ba02bae61aa39f253bef18179941938f89d08cd03404be1d06468fac881a5f5d7de0d8951cda84296301e8919a5ae71a16123ce17266b6" } # Timelapse protocol The Timelapse protocol is a meta-protocol for deploying new tokens or extending existing Ordinal protocols with dynamic token generation capabilities. The native token for this protocol is $TIMELAPSE, which is deployed natively using the JSON shown above. The Timelapse protocol operates by indexing Ordinal inscriptions on Bitcoin. This protocol is not an investment. This paper is not financial advice. This is simply Computer Science. ## Token Deployment To extend a token using the Timelapse protocol, the user must own the original deployment inscription of that token. They must then use it as the parent inscription for the Timelapse deployment inscription. Example of an Extension Deploy Inscription: { "p": "time-lapse", "op": "deploy", "tick": "BTCS", "name": "BitSeconds", "icon": "💎", "max": "0", "lim": "0", "self_mint": "0", "genesis": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", "epoch": "31556952", "influx": "(block.time - last_block.time)", "activity_scope": "time-lapse", "user_weight": "((tokens['BTC'].supply == 0) ? 0 : (((last_block.user_participation == 0) && (tokens['BTC'].minted == tokens['BTC'].max)) ? 1 : ((user['BTC'].balance + user.balance) / (tokens['BTC'].supply + supply))))", "user_activity_weight": "(((block.time - user.last_active) <= epoch) ? 1 : 0)", "user_participation": "sum(users[i].weight * users[i].activity_weight)", "user_yield": "((block.user_participation == 0) ? 0 : (((block.influx + (time_capsule * ((block.time - last_block.time) / epoch))) * (user.weight * user.activity_weight)) / block.user_participation))", "user_distribution": "user.balance += user.yield", "time_capsule_distribution": "((block.user_participation == 0) ? (time_capsule += block.influx) : (time_capsule = max(0, (time_capsule - (time_capsule * ((block.time - last_block.time) / epoch))))))", "deploy_time": "1231006505", "tip_tick": "TIMELAPSE", "tip_amt": "60", "pk": "satoshi_public_key", "nonce": "0", "sig": "satoshi_sig" } Example of a Native Deploy Inscription: { "p": "time-lapse", "op": "deploy", "tick": "LUCKY", "name": "LuckyDay", "icon": "🔮", "max": "86400", "lim": "1", "self_mint": "1", "epoch": "21037968", "influx": "((block.time - last_block.time) * (1 - 0.0018047370354399295))", "transfer_function": "[(burn_amount = (transfer_amount * 0.00001)), (transfer_amount -= burn_amount), (time_capsule += burn_amount)]", "user_weight": "((supply == 0) ? 0 : (((last_block.user_participation == 0) && (minted == max)) ? (0.5 + rand(block.hash + user.pk)) : ((user.balance / supply) * (0.5 + rand(block.hash + user.pk)))))", "user_activity_weight": "(((block.time - user.last_active) <= epoch) ? ((cos(((const_pi * (block.time - user.last_active)) / (2 * epoch))) ** 2)) : 0)", "user_participation": "sum(users[i].weight * users[i].activity_weight)", "user_yield": "((block.user_participation == 0) ? 0 : ((((block.influx) + (time_capsule * ((1 - exp((-1 * const_pi * (block.time - last_genesis.time)) / epoch)) / (1 - exp(-1 * const_pi))))) * (user.weight * user.activity_weight)) / block.user_participation))", "user_distribution": "user.balance += user.yield", "time_capsule_distribution": "((block.user_participation == 0) ? (time_capsule += block.influx) : (time_capsule = max(0, (time_capsule - (time_capsule * ((1 - exp((-1 * const_pi * (block.time - last_genesis.time)) / epoch)) / (1 - exp(-1 * const_pi))))))))", "deploy_time": "1861985705", "tip_tick": "TIMELAPSE", "tip_amt": "60", "pk": "deployer_public_key", "hash_id": "commitment_inscription_id", "nonce": "0", "sig": "deployer_sig" } ## Timelapse Deploy inscription parameter breakdown The Timelapse protocol supports a Pure Expression Grammar which includes the following mathematical operators: addition (+), subtraction (-), multiplication (*), division (/), modulus (%), and exponentiation (**). The protocol also supports comparison operators (==, !=, >, <, >=, <=), logical operators (&&, ||), and the conditional ternary form (? :). Supported intrinsic functions include abs(x), round(x), floor(x), ceil(x), pow(x, y), exp(x), log(x), sin(x), cos(x), asin(x), acos(x), sign(x), min(x, y), max(x, y), gcd(x, y), sum(array), avg(array), and rand(seed). Mathematical constants const_pi and const_e are available, and rand(seed) must derive from deterministic inputs such as 'block.hash' combined with other contextual data to ensure verifiable yet unpredictable results across indexers. All expressions are parsed deterministically and evaluated as pure functions using arbitrary-precision integers and fixed-point decimal arithmetic (18 decimals), producing identical results for identical inputs across all implementations. The variables 'user', 'token', and 'block' correspond to the current user, token, and block being indexed. These define the local execution context for each formula within the protocol (e.g., token.supply, token.minted, user.balance, user.yield, block.size, block.time). By convention, variables such as 'supply', 'minted', or 'influx' implicitly reference the current token’s state, while prefixed forms like 'user.balance' or 'block.time' are used to clarify scope when referring to user or block data. In addition to these local variables, the protocol exposes three global maps: 'users', 'tokens', and 'blocks'. These allow users to reference external data directly from within any deploy inscription. users['address']: Returns the user object associated with the specified wallet address. tokens['inscription_id']: Returns the token object associated with the given inscription ID. blocks['input']: Returns a Bitcoin block containing all standard data and may be referenced by 'hash' or 'height'. Each token object also includes 'genesis' and 'last_genesis', referencing the original deployment block and most recent revival block. When first deployed, 'last_genesis' = 'genesis'; it only updates if the protocol becomes inactive and is later revived. They can be accessed as 'token.genesis' or 'token.last_genesis', or across tokens by using tokens['inscription_id'].genesis. Within blocks, token-specific parameters can be queried as block['token_id'].influx or block['token_id'].user_participation, while shorthand references (e.g., influx) default to the currently indexed token. This structure allows deployers to reference both local and global states deterministically, enabling formulas to operate across users, tokens, and blocks with complete temporal and contextual precision. "p": Identifies the protocol name, which is "time-lapse". "op": Specifies the operation type, here it is "deploy". "tick": The ticker symbol of the token (e.g., BTC). This is the short code used to identify the token at the time of deployment. When deploying a token natively on Timelapse, the ticker is set manually. When Timelapse is used as an extension protocol, the ticker is automatically inherited from the parent protocol unless explicitly specified, in which case the provided ticker overrides the inherited value. After deployment, tickers are not used to reference tokens, as tickers can be reused. If sending $TIMELAPSE, users may set "tick" = "TIMELAPSE" or the inscription ID of this document. For all other tokens, the inscription ID alone is used to uniquely identify the token. "name": The full name of the token ecosystem (e.g., Bitcoin). This provides a descriptive, human-readable label for the token environment. In extensions using Timelapse, if the name is left empty, it is inherited from the parent token. This field is purely for descriptive purposes. "icon": Can be an emoji (e.g., "🍃"), an inscribed image (e.g., "icon_inscription_id"), or raw binary/serialized data (e.g., "Base64-encoded"). For native deploys: specify the icon, set as "none", or leave the field blank to have no icon. For extensions: leave blank to inherit parent icon, specify icon to override parent, or set as "none" to have no icon. "genesis": When using Timelapse as an extension protocol, the parent inscription acts as the genesis token being extended. This parameter is not necessary, as the parent inscription already contains this information, but the inscription ID or TXID can be added for reference. "max": Defines the maximum supply of tokens that can be minted. If deploying natively, this value must be greater than 0, or the token must generate yield based on the balances of another token to ensure the token has a circulating supply. If extending another protocol, this value may be set to 0, or greater than 0 to allow users to mint additional tokens. "lim": Specifies the maximum number of tokens that can be minted per operation. This value must be ≤ "max". "self_mint": Specifies the number of tokens minted directly to the deployer at deployment. This value must be ≤ "max", and if omitted defaults to 0. "epoch": Defines the length of an epoch in seconds, and defaults to the average length of a Gregorian calendar year (31,556,952 seconds). Epochs segment time into fixed windows used to measure activity and trigger protocol logic such as yield eligibility, timekeeper status, voting cycles, or other custom behaviors. For $TIMELAPSE, if no activity occurs for longer than one epoch, the user or timekeeper is considered inactive. "influx": Specifies how many tokens are generated each block, starting from "deploy_time". If "deploy_time" is in the past, the protocol retroactively credits the unclaimed yield to the 'time_capsule'; if it is current or in the future, generation starts from that point onward. "transfer_function": Defines how tokens are processed during a transfer. The 'burn_amount' must be between 0 and the original 'transfer_amount', and defaults to 0. The 'transfer_amount' is always reduced by the 'burn_amount', ensuring that total outputs never exceed inputs. Burned tokens may be added back into the 'time_capsule', or if left undefined, are permanently destroyed. "activity_scope": Determines what constitutes valid activity within an epoch, and by default is set to "token". Supported modes include "wallet" (any signed Bitcoin transaction or valid Timelapse inscription authorized by the wallet), "time-lapse" (any valid Timelapse inscription), and "token" (direct interaction with the token in question). "user_weight": Used in calculating the percentage of newly generated tokens each user receives. If the supply is zero, this returns 0. The variable 'last_block.user_participation' represents the total user participation recorded in the previous block, and 'minted' is the total number of tokens minted. If 'last_block.user_participation' is zero and all tokens have been minted (minted == max), this returns 1, allowing any active user to revive the protocol and claim previously unallocated yield stored in the 'time_capsule'. Otherwise, this returns (user.balance / supply). The 'user.balance' variable includes the tokens minted and generated. The 'supply' variable is the current circulating supply of all minted and generated tokens. "user_activity_weight": A dynamic function that factors user engagement into the yield calculation based on activity within an epoch. "user_participation": Defines how the protocol calculates the total participation score across all users. This score acts as the denominator in the yield calculation. The default implementation is: (Σᴺᵢ₌₀(wᵢ × aᵢ)) with wᵢ = users[i].weight, aᵢ = users[i].activity_weight, where each user’s participation is based on their token holdings (user.weight) and recent engagement (user.activity_weight). A token creator may adjust how "user_weight" and "user_activity_weight" are calculated, but the "user_participation" parameter must always represent the dot product of these two values across all active users. This ensures total yield is distributed proportionally and that no excess influx is ever created or lost. The flexibility of this system allows deployers to experiment with different weighting functions while maintaining deterministic yield distribution. "user_yield": This function determines how many tokens a user receives per block, and cannot be greater than the influx for that block. This field is completely customizable, but here we describe the specific implementation used for $TIMELAPSE. Values are not calculated continuously; they are only computed when a user performs an action that results in a transfer. The function begins by checking whether 'block.user_participation' is equal to zero, and if it is, the function returns zero for all users. This prevents division by zero and ensures that no yield is distributed when there are no active participants. In this scenario, the block’s entire influx is redirected into the time capsule through the "time_capsule_distribution" function. The 'time_capsule' accumulates all unallocated influx from periods of inactivity, preserving it for future distribution once users become active again. The protocol then distributes the current influx plus the accumulated influx stored in the time capsule to all active users. The amount each user receives is proportional to their 'user.weight' and 'user.activity_weight', divided by the total 'block.user_participation'. The system calculates each user’s share as: (((block.influx + (time_capsule * f_decay(block.time - last_genesis.time))) * (user.weight * user.activity_weight)) / block.user_participation), where 'f_decay(t)' defines the portion of the time capsule released as a function of time within the epoch. If either "user_weight" or "user_activity_weight" depends on variables that vary unpredictably between blocks (such as block.size or block.hash), yield must be recalculated sequentially for every block. For the purposes of this protocol, indexers should assume a per-block evaluation model to ensure deterministic yield distribution. For $TIMELAPSE, because both "user_weight" and "user_activity_weight" are time-based and deterministic, yield can instead be computed directly from the user’s last active block up to the end of their eligibility window within the current epoch. If the user remains inactive beyond the epoch length, yield generation stops until the user performs another action, at which point accumulation resumes from the new activity timestamp. $TIMELAPSE uses a simple binary activity function: 1 if the user has been active within the last epoch, and 0 if not. By dividing out 'block.user_participation', the protocol ensures that unclaimed yield is proportionally shared among active users. Users with zero activity weight receive nothing, and their share is redistributed to those who are active. This guarantees that each block’s generated tokens are fully allocated and that balances tied to inactive or lost wallets do not permanently dilute the distribution. If participants return after a period of inactivity, the tokens stored in the time capsule are unlocked and distributed along with the new influx. "user_distribution": Increments each user's token balance by their respective yield for the current block. The distribution algorithm can be customized to suit the needs of each token. "time_capsule_distribution": In the Timelapse protocol, time is never lost; it is held in the 'time_capsule' until the first active user returns. Any tokens not paid out in yield will accumulate in the time capsule. The timing and method of time capsule distribution are determined by each token’s deploy parameters. For $TIMELAPSE, the time capsule continues to accumulate when no users are active, and is gradually redistributed equally to all active participants once the protocol reactivates. "timekeeper_weight": Used in calculating the effective voting power of each timekeeper during governance proposals. This value defaults to "user_weight" if not explicitly defined. "timekeeper_activity_weight": A dynamic function that adjusts a timekeeper’s voting power based on their recent participation or activity within the epoch. This defaults to "user_activity_weight" if not explicitly defined. "proposal_balance": The minimum balance required to put forth a proposal. By default this is set to 1%, but constants, variables and functions are supported here too. "proposal_quorum": A proposal is considered valid only if the total timekeeper voting power exceeds this value. By default, this is set to 50%, but again, constants, variables and functions are supported here too. "proposal_duration": Specifies the number of blocks during which a proposal remains active for voting. By default, this is set to 1008 blocks (~1 week), with constants, variables and functions being supported here as well. "deploy_time": Sets the start time for token generation in Unix time. To start from a specific block, use the 'block' keyword before the block number (e.g., "block1000000"). "tip_tick": An optional field that allows users to specify the inscription ID of the token they wish to tip. The tip defaults to the miner who includes the inscription; however, if someone else inscribes the complete, valid JSON and pays BTC to get it confirmed, priority is given to the inscriber offering the highest BTC fee. "tip_amt": The amount tipped to the miner. Canonical JSON: For the purposes of computing "sig" and "hash_id", the protocol defines a deterministic canonical JSON form. The canonical JSON is obtained by parsing the complete object with a standard JSON parser and then serializing it again with all keys sorted alphabetically. The resulting UTF-8 encoded string is used as the input for all hash computations within this protocol. "pk": The public key used to verify signatures and recover the sender's address. "nonce": A sequential counter starting from 0 for each address. Each signed operation must increment the nonce to prevent replay attacks. "sig": The Schnorr signature (as defined in BIP-340) proving authorization. It is computed by signing the SHA-256 hash of the canonical JSON of the inscription, excluding only the "sig" field itself. "hash_id": An optional field used to prove first-to-deploy for native token deployments. Defined as the SHA-256 hash on the canonical JSON of the deploy object excluding the "hash_id", "nonce" and "sig" fields, used to commit deploy parameters prior to public reveal. If a user is deploying natively and wants to prove they were first, they follow this process: Step 1: Create the deploy JSON with all parameters including the "pk" field (but no "hash_id", "nonce" or "sig" yet). Step 2: Calculate the SHA-256 hash of the JSON. Step 3: Create and inscribe a commitment inscription: { "p": "time-lapse", "op": "commit", "hash": "sha256_hash_from_step_2" } Step 4: Add the "hash_id" field to the original deploy JSON. The "hash_id" should reference the commitment inscription ID from Step 3. Step 5: Add the "nonce" field to the original deploy JSON. Step 6: Sign the hash of the complete deploy JSON. This should contain all fields including "hash_id" and "nonce" but exclude the "sig" field. Step 7: Add the "sig" field with the signature. Step 8: Inscribe the complete deploy JSON. Verifiers can then: - Retrieve the commitment inscription via "hash_id" - Remove "hash_id", "nonce" and "sig" from the deploy JSON - Hash what remains and confirm it matches the "hash" in the commitment inscription - Verify the deploy "sig" is valid for the "pk" This provides cryptographic proof that a given wallet committed to the deploy parameters before revealing them, establishing priority unless another commitment with an earlier timestamp exists for the same ticker. The commitment requires no nonce because it doesn't change state, it's simply a timestamped proof of knowledge. This process is optional and only required if a user wants cryptographic proof of being first to claim a ticker on a native deployment. Also, while not formally a protocol parameter, inscriptions may include arbitrary text content following the root-level closing bracket for documentation purposes. ## Timelapse After Deployment For the Timelapse protocol, all calculations are rounded down; mints and burns round down, transfers round in favor of the recipient, trades in favor of the lister, and user yields round in favor of the 'time_capsule', with any fractional amounts stored for later distribution. Once a token is deployed using the Timelapse protocol, users can begin interacting with it by minting tokens, holding them to generate yield, transferring or trading them. In the future, users will also be able to withdraw tokens to other supported blockchains which we shall refer to as "timechains". ### Mint tokens Both native and extension deploys define how their tokens behave within Timelapse. Extensions may inherit balances and yield context from a parent protocol through a parent–child inscription or define their own relationships explicitly using inscription IDs. Either type can be configured to mint new tokens or generate yield from their own or external token holdings. The amount of tokens minted in the Timelapse protocol cannot exceed the max supply defined in the original deploy, and must respect the per-mint limit if one was set. Users must inscribe a mint operation, and once it's confirmed, the user's tokens become active and contribute to the circulating supply. The circulating supply includes all tokens that have been minted through the Timelapse or parent protocol, as well as all tokens that have been generated through time-based yield. A user's total token holdings (user.balance) include both tokens minted and tokens generated through yield. This applies whether the token was deployed natively through Timelapse or extended from another Ordinal protocol. Once the deploy time has been reached, additional tokens will be generated according to the parameters specified in the deploy inscription. Example of a Native Mint Inscription: { "p": "time-lapse", "op": "mint", "tick": "TIMELAPSE", "amt": "600" } ### Transfer Tokens This operation allows sending tokens to one or multiple recipients. The "tick" parameter points to the token deploy inscription ID. To transfer $TIMELAPSE, users can set "tick" = "TIMELAPSE" or the ID of this inscription itself. Example of a Transfer Inscription: { "p": "time-lapse", "op": "transfer", "tick": "TIMELAPSE", "items": [ { "amt": "100", "address": "btc_address1" }, { "amt": "150", "address": "btc_address2" } ] } ### Trading Timelapse Tokens When both "tick1" and "tick2" are Timelapse tokens, trading is handled entirely through inscription indexing. #### Create Listing Enables users to list their tokens for trade, specifying the amount offered and the amount requested in return. The parameters "tick1" and "amt1" define the token and amount being listed, while "tick2" and "amt2" define the token and amount being requested. If the parameter "tick2" is omitted, then by default it will be set to $TIMELAPSE. An optional parameter "expires" sets the time the listing expires; if no value is set, the listing will never expire unless canceled manually. Similar to the "deploy_time" parameter, this value is set in Unix time. To set the expiry time to a certain block, the keyword 'block' can be placed before the argument. The parameter "fill_type" defaults to 'any' for partial fills, or can be set to 'all' to require the entire listing to be filled in a single trade. Tokens listed for trade continue to generate yield for the user but remain locked and unavailable to spend until the listing is fulfilled, canceled, or expires. Example of a Timelapse Listing Inscription: { "p": "time-lapse", "op": "list", "tick1": "token_deploy_inscription_id1", "tick2": "token_deploy_inscription_id2", "amt1": "10", "amt2": "100", "fill_type": "any", "expires": "block1000000" } #### Trade When a user inscribes a trade operation, the protocol verifies that the user has sufficient balance and that the referenced listing inscription ID is valid. If the requested amount exceeds the remaining available amount, the trade automatically fills the maximum available quantity based on the listing's exchange rate. If valid, the token amounts are swapped via the indexer, and the listing balance is reduced accordingly. Example of a Trade Inscription: { "p": "time-lapse", "op": "trade", "inscription_id": "listing_inscription_id", "amt": "5" } The "amt" parameter specifies the amount of "tick1" tokens the trader wishes to acquire from the listing. The required "tick2" amount is calculated proportionally based on the listing's exchange rate (amt2/amt1). #### Offer Allows users to offer any token and amount for a specific listing. The offer references the target listing inscription ID, specifies the amount desired from the listing, and specifies the amount being offered in exchange. If no "expires" parameter is added, the offer remains active until it is accepted or canceled. Example of an Offer Inscription: { "p": "time-lapse", "op": "offer", "inscription_id": "listing_inscription_id", "tick": "listing_inscription_id_tick2", "amt1": "10", "amt2": "50", "expires": "block1000000" } The "amt1" parameter is the amount of the listing's "tick1" the trader wants to receive. The "amt2" parameter is the amount of their token (specified in "tick") they are offering in exchange. #### Accept Allows the original listing owner to accept or reject an active offer. The accept inscription references the offer's inscription ID. If valid, the trade is executed and indexed. Example of an Accept Inscription: { "p": "time-lapse", "op": "accept", "inscription_id": "offer_inscription_id", "decision": "accept" } Accept operations may be batched using the items array. The user must replace "inscription_id" and "offer_inscription_id" with "items" and "['offer_id1', ..., 'offer_idn']". #### Cancel Allows users to cancel a previously made listing or offer. This operation references the specific inscription ID of the listing or offer that needs to be canceled. Example of a Cancel Inscription: { "p": "time-lapse", "op": "cancel", "inscription_id": "listing_or_offer_inscription_id" } Cancel operations may also be batched using the items array. The user must replace "inscription_id" and "listing_or_offer_inscription_id" with "items" and "['id1', ..., 'idn']". For all batch operations, if any item fails the entire batch is ignored. ### Trading for BTC When trading Timelapse tokens for other Timelapse tokens, trades are handled entirely through inscription indexing and state transitions. However, when trading for BTC, Bitcoin's UTXO model must be accounted for. BTC listings must be inscribed onto a dedicated UTXO that gets transferred from lister to trader. This ensures the listing itself moves on-chain as a UTXO, making fulfillment atomic. While Timelapse token listings support partial fills, Bitcoin listings must be filled in full. #### Create Listing To create a BTC listing, the lister inscribes the listing operation on a dedicated UTXO. This UTXO becomes the listing itself and must remain unspent for the listing to stay active. BTC listings remain valid indefinitely as long as the listing UTXO remains unspent. Example of a BTC Listing Inscription: { "p": "time-lapse", "op": "list", "tick1": "token_deploy_inscription_id", "tick2": "BTC", "amt1": "1000", "amt2": "0.01" } When the indexer sees this listing inscription confirmed, it marks the listing as active and associates it with the specific UTXO. The lister's tokens (amt1) are placed in escrow by the indexer, preventing double-listing or spending while the listing remains active. #### Trade The lister creates a partially signed transaction. This allows the lister to commit to spending the listing UTXO and receiving "amt2" in BTC. The lister broadcasts this incomplete transaction, which cannot be confirmed until the required outputs are fully funded. Any trader can complete the transaction by adding their BTC payment input and specifying outputs that send "amt2" to the lister and the listing UTXO to themselves. The trader pays all transaction fees from their input. The trader whose transaction confirms first wins the trade and receives the escrowed tokens. Once confirmed, the indexer sees the listing UTXO transferred to the trader and credits them with the escrowed tokens. The listing is then marked as filled. #### Offer To make an offer, the trader creates a PSBT that spends the listing UTXO (unsigned) and adds their own BTC payment input (signed). The PSBT specifies outputs that send the offer amount to the lister and the listing UTXO to the trader. The trader pays all transaction fees from their input. The trader shares this PSBT with the lister. If acceptable, the lister signs the listing UTXO input and broadcasts the completed transaction. If not acceptable, the lister ignores the PSBT. #### Cancel To cancel a BTC listing, the lister creates a transaction that spends the listing UTXO back to themselves. The lister must provide an additional input to cover Bitcoin transaction fees. When the listing UTXO is spent, the indexer credits the escrowed tokens to whoever receives the listing UTXO. If the listing UTXO returns to the lister, the listing is marked as canceled, and the tokens are unlocked. If the listing UTXO goes to a new address, the listing is marked as filled and that address receives the tokens. For traders who created offers, they can cancel by spending their payment input in another transaction, which invalidates the PSBT. ## Inscription and Authentication While certain operations require UTXO tracking, the Timelapse protocol operates primarily through the indexing of inscriptions on Bitcoin. As such, authentication must respect this design and accommodate it accordingly. There are two methods to interact with the protocol: METHOD 1: Direct Inscription from a Wallet If a user controls their own inscription tooling, they may inscribe directly from their address. The indexer validates that the inscription was made from the address claiming to perform the operation. METHOD 2: Secondary Wallet Inscription with Signature If using a secondary wallet, a user must include the "pk", "nonce", and "sig" parameters in every operation. The signature proves that the operation was authorized by the controlling address, even if inscribed by another wallet. The public key allows the indexer to verify the signature and recover the authorizing address. Example with Signature: { "p": "time-lapse", "op": "transfer", "tick": "token_deploy_inscription_id", "items": [ { "amt": "100", "address": "btc_address1" } ], "pk": "user_public_key", "nonce": "last_nonce_plus_1", "sig": "user_sig" } The signature is computed as: sign(sha256(JSON_without_sig_field), sender_private_key). The nonce is a sequential counter starting from 0 for each address. Each new operation must have nonce = previous_nonce + 1. This prevents replay attacks where the same signed message could be inscribed multiple times. The indexer maintains a record of the last used nonce for each address. When a new inscription arrives, the indexer verifies: - The signature is valid for the sender's address (by verifying the signature against the SHA-256 hash of the JSON without the sig field) - The nonce is exactly one greater than the last recorded nonce for that address - If either check fails, the inscription is ignored All operations (mint, transfer, trade, vote, etc.) support the "pk", "nonce" and "sig" parameters. If inscribing directly from a wallet, these parameters are optional but encouraged. If inscribing via another address, they are required, and the inscription must be sent to the address corresponding with "pk" to be considered valid. ## Governance and Protocol Updates The Timelapse protocol is designed to evolve over time through on-chain governance. All protocol changes, including updates to token parameters, require consensus from active participants called timekeepers. This system is built to run entirely by indexing Ordinal inscriptions. ### Timekeepers Timekeepers are token holders who actively participate in protocol governance. To become a timekeeper, a user must inscribe a serve operation indicating their intent to participate in governance. A user can serve or resign as a timekeeper for a specific token by including the "tick" parameter in their serve and resign inscriptions respectively. In this case, the user will vote on changes that affect only that token. A user may also serve as a timekeeper for the Timelapse protocol itself by omitting the "tick" parameter, and the user will vote on changes that impact the entire protocol. A timekeeper must not remain inactive for longer than a single epoch, measured from their last activity, or their timekeeper status will be revoked. Example of a Serve as Timekeeper Inscription: { "p": "time-lapse", "op": "serve", "tick": "token_deploy_inscription_id" } Example of a Resign as Timekeeper Inscription: { "p": "time-lapse", "op": "resign", "tick": "token_deploy_inscription_id" } ### Token Parameter Updates Token parameters can be updated through formal on-chain proposals. These updates require consensus via a vote from active timekeepers. By default, an active timekeeper must hold at least 1% of the token's circulating supply to propose a parameter update. They must inscribe an "update-token" operation referencing the deploy inscription ID via the "tick" field, specify which field should be updated, and set its new value. Example of an Update Token Inscription: { "p": "time-lapse", "op": "update-token", "tick": "token_deploy_inscription_id", "field": "icon", "value": "inscription_id_of_icon" } Timekeepers will use this inscription ID in subsequent vote inscriptions. For each token, only one "update-token" proposal per field may be active at any given time. Example of a Vote Inscription: { "p": "time-lapse", "op": "vote", "inscription_id": "update_proposal_inscription_id", "decision": "yes" } Votes are tallied based on the combined weighting of all active timekeepers at the block height the proposal was inscribed. Each vote is weighted by the product of the timekeeper’s "timekeeper_weight" and "timekeeper_activity_weight" values for the token in question. Voting power is calculated as the dot product of all active timekeepers’ "timekeeper_weight" and "timekeeper_activity_weight" values. A proposal is considered valid if the proposer holds at least "proposal_balance" at the time of inscription. If the total voting power in favor exceeds "proposal_quorum" within "proposal_duration", the proposal passes. If not, it is rejected and will not be indexed. Once a proposal passes, all future inscriptions must respect the new parameter value. This allows protocols to evolve over time with full transparency and verifiable consensus. ### Protocol Updates While token parameter updates modify the behavior of individual tokens, a Timelapse Improvement Proposal (TIP) extends the Timelapse protocol itself with entirely new functionality. These updates follow the same governance process, but affect how the core protocol operates across all tokens. To put forth a TIP, an active timekeeper must hold at least 1% of the $TIMELAPSE circulating supply. The proposal must specify which new operations are being added and provide complete technical specifications. Example of a Protocol Update Inscription: ══════════════ BEGIN INSCRIPTION DATA ══════════════ { "p": "time-lapse", "op": "update-protocol", "title": "Enable Timechain Bridge Support", "new_ops": "['withdraw', 'deposit']", "target_chain": "timechain_name", "target_contract_address": "TimechainContractAddress123456789", "verification_key": "hash(circuit_definition)", "genesis_hash": "TimechainGenesisHash123", "require_clock_in": "true", "effective_time": "1799000105" } This proposal enables bridging between Bitcoin and the specified timechain using zero-knowledge proofs for trustless state verification. Once approved, users will be able to withdraw Timelapse tokens to the timechain and later deposit them back to Bitcoin without relying on oracles or attestations. The timechain smart contract has been deployed at the specified address and implements ZK proof verification using the provided verification key. The genesis hash is the hash of the timechain's genesis block, ensuring proofs reference the correct network and not testnets or forks. OPERATION: WITHDRAW Allows users to move tokens from Bitcoin to the timechain. { "p": "time-lapse", "op": "withdraw", "tick": "token_deploy_inscription_id", "source_chain": "bitcoin", "source_address": "btc_address_123456789", "target_chain": "timechain_name", "target_contract_address": "TimechainContractAddress123456789", "target_address": "timechain_address_123456789", "amt": "500" } When a withdraw inscription is confirmed in a Bitcoin block, the indexer reduces the user's balance on the Bitcoin timechain by the withdrawn amount. The user must then generate a ZK proof demonstrating their complete balance history. For a user's first withdrawal, the proof covers all inscriptions from protocol genesis through the withdrawal block. For subsequent withdrawals, the proof covers all inscriptions from their last checkpoint through the current withdrawal block. This proof shows that after processing all inscriptions sequentially, their balance at the withdrawal block is sufficient to cover the withdrawal amount. The user submits this proof to the timechain smart contract, which verifies it mathematically using the verification key. The proof includes a Merkle proof of the inscription's inclusion in a Bitcoin block and an SPV proof that the block is part of the canonical Bitcoin chain. Once verified, the timechain contract mints tokens to the user's address and records their checkpoint for future withdrawals. OPERATION: DEPOSIT Allows users to return tokens from the timechain back to Bitcoin. { "p": "time-lapse", "op": "deposit", "tick": "token_deploy_inscription_id", "proof_type": "zk_stark", "proof_data": "base64_encoded_zk_proof", "public_inputs": { "source_chain": "timechain_name", "source_address": "timechain_address_123456789", "source_contract_address": "TimechainContractAddress123456789", "target_chain": "bitcoin", "target_address": "btc_address_123456789", "timechain_slot": "250000000", "timechain_timestamp": "1830536105", "timechain_epoch": "525", "burn_tx_signature": "timechain_tx_signature_123456789", "amt": "550" } } To deposit, users must first burn their tokens on the timechain using the "burn_and_withdraw" method. The user inputs the following parameters: - source_chain (the originating timechain where the burn occurred) - source_address (the address of the user on the timechain) - source_contract_address (the contract address on the timechain) - target_chain (the destination chain, in this case Bitcoin) - target_address (the Bitcoin address receiving the deposit) - timechain_slot (the finalized slot or block number containing the burn) - timechain_timestamp (the UNIX timestamp when the burn was confirmed on the timechain) - timechain_epoch (the epoch that finalized the burn block) - burn_tx_signature (the transaction signature of the burn operation) - amt (the amount being deposited) The burn transaction permanently removes tokens from the timechain and emits a log event containing these parameters. The user then generates a ZK proof demonstrating: - The burn transaction occurred at the specified slot and timestamp. - The transaction is included in a finalized timechain block (Merkle proof). - The validator set active during that epoch, proven by referencing the timechain’s on-chain validator registry for epoch X. - The validator set finalized the block containing the burn according to that timechain’s consensus threshold. - The burn parameters match the public inputs (source_chain, source_address, source_contract_address, target_chain, target_address, timechain_slot, timechain_timestamp, timechain_epoch, burn_tx_signature, amt). The user inscribes this proof on Bitcoin. The indexer verifies the ZK proof using the verification key without querying the timechain or trusting any oracle. If valid, the indexer increments the user’s balance on the Bitcoin timechain by the burned amount. Yield generation resumes from the "timechain_timestamp", ensuring no yield is lost during cross-chain transfers. TIMECHAIN YIELD MECHANICS Yield on the timechain is calculated using Bitcoin block headers. The timechain contract enforces a 6-block confirmation delay. This ensures all yield is based on a finalized Bitcoin state that cannot be reversed by a reorg. When users perform actions on the timechain (transfers, burns), they must submit Bitcoin block headers for the last 6 confirmed blocks. The contract verifies these headers form a valid proof-of-work chain by checking difficulty targets and hash values. To prevent fake forks, the contract maintains a record of the highest cumulative difficulty chain it has observed. Headers with insufficient cumulative work are rejected, preventing the protocol from forking off into those timelines. In the event of equal cumulative difficulty between competing chains, the chain with the earlier timestamp is accepted. The contract calculates yield based on timestamps from blocks that are at least 6 confirmations deep. For each action, it computes the elapsed time since the user's last action, applies the yield formula from the deploy inscription, and credits yield to both the sender and receiver. The contract tracks each user's last action block to enable incremental yield calculations. Yield earned on the timechain follows that chain's token economy. Only tokens physically present on the timechain participate in that chain's yield distribution. When tokens return to Bitcoin via deposit, they resume participating in Bitcoin's yield distribution from the moment of the burn timestamp. TIMECHAIN CLOCK IN To ensure that yield does not accumulate indefinitely on inactive chains, each timechain must not remain inactive for longer than a single epoch, measured from its last clock-in attestation or deposit. Clocking in can be done either through a deposit from the timechain back to Bitcoin, or through a clock-in attestation inscribed on Bitcoin. A clock-in attestation serves as a proof that the external timechain and its associated smart contract remain live, synchronized, and operating on the canonical chain. This mechanism ensures that yield associated with the chain’s token balances remains active only if both the chain and the deployed contract are verifiably alive. A valid clock-in attestation references a finalized block on the external timechain and includes the data required to prove both the block’s inclusion and the current state of the target smart contract. This consists of the finalized block number, the block header (which includes the Merkle root), the contract address or identifier, and a zero-knowledge proof demonstrating that the block and contract state belong to the canonical chain. The exact structure depends on the consensus and proof system of the target timechain. For chains using validator sets, the attestation must also include the validator set commitment for the epoch to prove finality. This ensures the attestation corresponds to the finalized network and not a forked or purged contract state. All proofs must be verifiable by the Bitcoin indexer without relying on oracles or trusted intermediaries. If no clock-in attestation or deposit is inscribed within an epoch, the timechain is considered inactive. Once inactive, yield distribution for the token balances held on that chain is suspended, and all subsequent yield is fully reallocated to the Bitcoin timechain. If a valid clock-in or deposit is later submitted, the chain is reactivated and yield resumes from that point forward. There is no retroactive yield for periods of inactivity, and each clock-in "timechain_timestamp" must be greater than the last one recorded. Below is an example of a clock-in attestation inscription: { "p": "time-lapse", "op": "clock-in", "tick": "token_deploy_inscription_id", "proof_type": "zk_stark", "proof_data": "base64_encoded_zk_proof", "public_inputs": { "timechain_name": "timechain_name", "timechain_contract_address": "TimechainContractAddress123456789", "timechain_slot": "250000000", "timechain_timestamp": "1830536105", "timechain_epoch": "525", "block_root": "T1me88XRvGp7NqE8mTZiM3aDYc7sB2J8H9A5eQnWkCp", "validator_set_commitment": "8T1mEvR3xN8YaP7GqZtE2mHnD8sJwKpC5QbR7VxM8tA", "contract_state_root": "t1MeX8QaB8rN7YwS9mP8KjG2E3vZcR8F6TnD1sM5qA" } } The indexer verifies the clock-in using the "proof_data" and "public_inputs" fields. If valid, it resets the epoch timer for that timechain, ensuring yield generation continues without interruption. If the chain fails to provide either a valid clock-in or deposit within the epoch duration, its yield is suspended until the next valid clock-in. WRAPPED TOKEN BRIDGING The timechain contract also supports the deposit of external native tokens. Users may deposit any supported native token into the contract, at which point those tokens are locked on the timechain and a wrapped version of the token is issued. These wrapped tokens behave like any other token within the contract environment: they can be transferred, traded, or even bridged to other supported chains. To bridge wrapped tokens to Bitcoin, the process follows the same deposit flow as for native Timelapse tokens. All proof requirements and verification steps are identical. The only difference is that the "tick" field must reference the timechain contract address of the wrapped token rather than a Timelapse deploy inscription ID. The first time an external token is deposited to Bitcoin, it introduces the wrapped token to the Bitcoin layer. From that point forward, the wrapped token can be traded, transferred, and updated just like any other Timelapse token. Users referencing the token must use the wrapped token timechain contract address as the identifier. If wrapped tokens are later withdrawn, their corresponding Bitcoin supply is reduced accordingly. Circulating supply on Bitcoin always reflects the total amount of wrapped tokens currently deposited. TECHNICAL REQUIREMENTS - All ZK proofs must be generated by users locally using the published circuit specification - Proofs verify complete state transitions, not just individual events - The timechain contract must reject Bitcoin headers with insufficient cumulative proof-of-work - Withdrawals require proving balance history from genesis or last checkpoint - Deposits require proving burn finality using validator set consensus - No oracles, relayers, or trusted third parties are required - All verification is purely cryptographic ═══════════════ END INSCRIPTION DATA ═══════════════ The inscription ID of the TIP above would be used in subsequent vote inscriptions. Timekeepers vote using the same mechanism as token parameter updates. More than 50% of the total timekeeper voting power must vote "yes" within 1008 blocks for the proposal to pass. Example of a Vote Inscription: { "p": "time-lapse", "op": "vote", "inscription_id": "update_protocol_inscription_id", "decision": "yes" } Once approved, indexers must update their implementation to recognize and process the new operations defined in the update. ### The Time-Lattice With the Timelapse protocol, indexers are free to compute only the subsets of tokens they wish to maintain. When a token’s deploy parameters are computationally intensive or recursive, indexers may simply skip them. Users who still wish to interact with such tokens must either compute the new state themselves or pay another participant to do so. The prover who performs this computation publishes or inscribes a zero-knowledge proof verifying the resulting state transition. Once published, the proof acts as a checkpoint, enabling any indexer to synchronize the verified state without re-execution. This forms a ZK-Lattice: a topology of verifiable states linked by recursive proofs. Each proof serves as an edge, each state as a node, together forming a web of computation anchored to Bitcoin’s immutable timeline. Rather than forcing global consensus, every participant maintains only the regions of the lattice they value. Unindexed or non-terminating tokens simply remain dormant until someone proves a valid transition. In this way, the halting problem is sidestepped not by logic, but by market incentive; computation halts where interest does. Over time, this Proof-Lattice naturally extends toward becoming an optimized execution layer shaped by the passage of verified time itself. This layer anchors to Bitcoin but exists independently, serving as a shared registry of verified state and computation. Whether proofs are generated directly by users, produced through peer-to-peer proof markets, or verified on external timechains, the outcome remains the same: Truth is not mined but proven, and computation itself becomes the currency of time. ## Future Timeline Bitcoin may be the most resilient monetary system ever created. Its scarcity, security, and simplicity make it the strongest store of value in human history. By ~2140, no new Bitcoin will be mined, and that scarcity will present a long-term challenge. Once the final coin is issued, miners will rely solely on transaction fees for compensation. As deflation accelerates and more coins are lost or locked in cold storage, users may grow reluctant to spend or move their BTC. If this happens, fee volume will trend toward zero. By embedding logic and economic incentives directly into Bitcoin’s timechain, parallel economies can be unlocked. These Ordinal economies will allow the network to reward miners not just in BTC, but with tokenized value layers built atop it. There is no protocol-level mandate requiring transaction fees to be paid in BTC. Even after the last Bitcoin is mined, Ordinal meta-protocols can keep the chain economically alive. Perhaps Timelapse will do just that.. — The Timekeeper