Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up exports and interfaces #58

Merged
merged 2 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 116 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,118 @@
# runestone-lib

A Typescript implementation of the Bitcoin Runestone protocol
This is a Typescript implementation of the Bitcoin Runestone protocol.
To see the original version, please go to the [Ordinals repo](/ordinals/ord);
you can find various [data structures](/ordinals/ord/tree/master/crates/ordinals/src) and
[indexer implementation](/ordinals/ord/blob/master/src/index/updater/rune_updater.rs) there.
General documentation of the runes protocol and how runestones are used can be found
[here](https://docs.ordinals.com/runes.html).

## Encode Runestone

To encode a runestone, use `encodeRunestone()` method, with an example below:

```ts
import { encodeRunestone } from '@magiceden-oss/runestone-lib';

// To deploy a new rune ticker
// (this will require a commitment in an input script)
const etchingRunestone = encodeRunestone({
etching: {
runeName: 'THIS•IS•AN•EXAMPLE•RUNE',
divisibility: 0,
premine: 0,
symbol: '',
terms: {
cap: 69,
amount: 420,
offset: {
end: 9001,
},
},
turbo: true,
},
});

// To mint UNCOMMON•GOODS
const mintRunestone = encodeRunestone({
mint: {
block: 1n,
tx: 0,
},
});

// Transfer 10 UNCOMMON•GOODS to output 1
const edictRunestone = encodeRunestone({
edicts: [
{
id: {
block: 1n,
tx: 0,
},
amount: 10n,
output: 1,
},
],
});
```

## Decode Runestone

Decoding a runestone within a transaction is as simple as passing in
the transaction data from Bitcoin Core RPC server.

```ts
import {
tryDecodeRunestone,
isRunestoneArtifact,
RunestoneSpec,
Cenotaph
} from '@magiceden-oss/runestone-lib';

// transaction retrieved with getrawtransaction RPC call
const tx = ...;

const artifact = tryDecodeRunestone(tx);

if (isRunestone(artifact)) {
const runestone: RunestoneSpec = artifact;
...
} else {
const cenotaph: Cenotaph = artifact;
...
}
```

## Indexing

To index, initialize a RunestoneIndexer, implement the interface arguments
to RunestoneIndexer constructor. Then it is just a matter of start() to finish
initializing the indexer, and then controlling the rate of syncing indexing
to latest state in RPC server.

```ts
// Initialize indexer
const indexer = new RunestoneIndexer(...);

// Preps the indexer to be ready to run updateRuneUtxoBalances()
await indexer.start()

// Example of a polling job running updateRuneUtxoBalances()
// every minute, with stop cleanup handling
let stop = false;
...

const intervalId = setInterval(async () => {
try {
await index.updateRuneUtxoBalances();
} catch (err) {
console.error('Error occurred while indexing runes', err);
}

if (stop) {
clearInterval(intervalId);
await indexer.stop();
}
}, 60 * 1000 /* one minute */);

```
50 changes: 46 additions & 4 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isRunestone } from './src/artifact';
import { isRunestone as isRunestoneArtifact } from './src/artifact';
import { MAX_DIVISIBILITY } from './src/constants';
import { Etching } from './src/etching';
import { Flaw as FlawEnum } from './src/flaw';
import { RuneEtchingSpec } from './src/indexer';
import { u128, u32, u64, u8 } from './src/integer';
import { None, Option, Some } from './src/monads';
Expand Down Expand Up @@ -56,15 +57,52 @@ export type RunestoneSpec = {
}[];
};

export type Flaw =
| 'edict_output'
| 'edict_rune_id'
| 'invalid_script'
| 'opcode'
| 'supply_overflow'
| 'trailing_integers'
| 'truncated_field'
| 'unrecognized_even_tag'
| 'unrecognized_flag'
| 'varint';

export type Cenotaph = {
flaws: string[];
flaws: Flaw[];
etching?: string;
mint?: {
block: bigint;
tx: number;
};
};

function getFlawString(flaw: FlawEnum): Flaw {
switch (flaw) {
case FlawEnum.EDICT_OUTPUT:
return 'edict_output';
case FlawEnum.EDICT_RUNE_ID:
return 'edict_rune_id';
case FlawEnum.INVALID_SCRIPT:
return 'invalid_script';
case FlawEnum.OPCODE:
return 'opcode';
case FlawEnum.SUPPLY_OVERFLOW:
return 'supply_overflow';
case FlawEnum.TRAILING_INTEGERS:
return 'trailing_integers';
case FlawEnum.TRUNCATED_FIELD:
return 'truncated_field';
case FlawEnum.UNRECOGNIZED_EVEN_TAG:
return 'unrecognized_even_tag';
case FlawEnum.UNRECOGNIZED_FLAG:
return 'unrecognized_flag';
case FlawEnum.VARINT:
return 'varint';
}
}

// Helper functions to ensure numbers fit the desired type correctly
const u8Strict = (n: number) => {
const bigN = BigInt(n);
Expand Down Expand Up @@ -195,14 +233,18 @@ export function encodeRunestone(runestone: RunestoneSpec): {
};
}

export function isRunestone(artifact: RunestoneSpec | Cenotaph): artifact is RunestoneSpec {
return !('flaws' in artifact);
}

export function tryDecodeRunestone(tx: RunestoneTx): RunestoneSpec | Cenotaph | null {
const optionArtifact = Runestone.decipher(tx);
if (optionArtifact.isNone()) {
return null;
}

const artifact = optionArtifact.unwrap();
if (isRunestone(artifact)) {
if (isRunestoneArtifact(artifact)) {
const runestone = artifact;

const etching = () => runestone.etching.unwrap();
Expand Down Expand Up @@ -286,7 +328,7 @@ export function tryDecodeRunestone(tx: RunestoneTx): RunestoneSpec | Cenotaph |
} else {
const cenotaph = artifact;
return {
flaws: [],
flaws: cenotaph.flaws.map(getFlawString),
...(cenotaph.etching.isSome() ? { etching: cenotaph.etching.unwrap().toString() } : {}),
...(cenotaph.mint.isSome()
? { mint: { block: cenotaph.mint.unwrap().block, tx: Number(cenotaph.mint.unwrap().tx) } }
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@magiceden-oss/runestone-lib",
"version": "0.9.10-alpha",
"version": "1.0.0",
"description": "",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
10 changes: 1 addition & 9 deletions src/indexer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,8 @@ export interface RunestoneStorage {

export type RunestoneIndexerOptions = {
bitcoinRpcClient: BitcoinRpcClient;

network: Network;

storage: RunestoneStorage;

/**
* The interval at which to poll the RPC for new blocks, in milliseconds.
* Defaults to `10000` (10 seconds), and must be positive.
*/
pollIntervalMs?: number;
};

export type BlockIdentifier = {
Expand Down Expand Up @@ -119,7 +111,7 @@ export type RuneUtxoBalance = {
export type RuneMintCount = { mint: RuneLocation; count: number };
export type RuneBalance = { runeId: RuneLocation; amount: bigint };

export type RuneEtchingBase = {
type RuneEtchingBase = {
divisibility?: number;
premine?: bigint;
symbol?: string;
Expand Down