diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml
new file mode 100644
index 0000000..4a34685
--- /dev/null
+++ b/.github/workflows/style.yml
@@ -0,0 +1,29 @@
+name: styling
+
+on: [push]
+
+jobs:
+  style:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+
+      - name: Run PHP CS Fixer
+        uses: docker://oskarstark/php-cs-fixer-ga
+        with:
+          args: --config=tools/.php-cs-fixer.php --allow-risky=yes
+
+      - name: Extract branch name
+        shell: bash
+        run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
+        id: extract_branch
+
+      - name: Commit changes
+        uses: stefanzweifel/git-auto-commit-action@v2.3.0
+        with:
+          commit_message: Fix styling
+          branch: ${{ steps.extract_branch.outputs.branch }}
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..1e27e4d
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,43 @@
+name: tests
+
+on: [push, pull_request]
+
+jobs:
+    phpunit:
+        runs-on: ${{ matrix.os }}
+        strategy:
+            fail-fast: false
+            matrix:
+                os: [ubuntu-latest]
+                php: [8.1]
+                laravel: [8.*]
+                dependency-version: [prefer-stable]
+                include:
+                    -   laravel: 8.*
+                        testbench: 6.*
+
+        name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
+
+        steps:
+            -   name: Checkout code
+                uses: actions/checkout@v2
+
+            -   name: Install SQLite
+                run: |
+                    sudo apt-get update
+                    sudo apt-get install sqlite3
+
+            -   name: Setup PHP
+                uses: shivammathur/setup-php@v2
+                with:
+                    php-version: ${{ matrix.php }}
+                    extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv
+                    coverage: none
+
+            -   name: Install dependencies
+                run: |
+                    composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
+                    composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
+
+            -   name: Execute tests
+                run: vendor/bin/phpunit
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..a68e7e3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+build
+composer.lock
+vendor
+storage
+tests/World/database.sqlite
+.DS_Store
+coverage
+.phpunit.result.cache
+.idea
+.php_cs.cache
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100755
index 0000000..7eaad29
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright © Matt Kingshott and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..a0a49ff
--- /dev/null
+++ b/README.md
@@ -0,0 +1,126 @@
+<!-- Screenshot -->
+<p align="center">
+    <img src="resources/wallpaper.jpg" alt="Wallpaper">
+</p>
+
+<!-- Badges -->
+<p align="center">
+  <img src="resources/version.svg" alt="Version">
+  <img src="resources/license.svg" alt="License">
+</p>
+
+# Snowflake
+
+This package enables a Laravel application to create Twitter Snowflake identifiers. It is a very thin wrapper around the excellent [Snowflake PHP](https://github.com/godruoyi/php-snowflake) library created by Godruoyi.
+
+## What are Snowflakes?
+
+Snowflakes are a form of unique identifier devised by Twitter. In this respect, they are similar to other unique identifier algorithms such as UUID or ULID.
+
+## Why should I use them?
+
+I've written an [article](https://itnext.io/choosing-the-right-data-type-means-of-generating-unique-primary-keys-d7aac92968c6) exploring the benefits of Snowflakes over other unique identifiers. However, in short:
+
+- They consists entirely of integers.
+- They uses less space (16 characters, so it fits in a `BIGINT`).
+- Indexing of integers is much faster than indexing a string.
+- Keys begin with a timestamp, so are sortable.
+- Keys end with a random number, so guessing table size is not possible.
+- Databases handle integers more efficiently than strings.
+- Generation of new keys is faster (less than 1 ms).
+
+## Installation
+
+Pull in the package using Composer:
+
+```bash
+composer require mattkingshott/snowflake
+```
+
+## Configuration
+
+Snowflake includes a configuration file that allows you to set:
+
+1. The data center number.
+2. The worker node number.
+3. The starting timestamp.
+4. The sequence resolver.
+
+Most developers won't need to alter these values unless they need to set up a distributed architecture for generating Snowflakes.
+
+If you want to change any of the values, publish the configuration file using Artisan:
+
+```bash
+php artisan vendor:publish
+```
+
+## Usage
+
+You can generate a Snowflake by resolving the service out of the container and calling its `id` method:
+
+```php
+resolve('snowflake')->id(); // (string) "5585066784854016"
+```
+
+Since this is a little cumbersome, the package also registers a global `snowflake()` helper method that you can use anywhere. This helper also converts the Snowflake from a `string` into an `integer`, which better reflects its data type:
+
+```php
+snowflake(); // (int) 5585066784854016
+```
+
+### Eloquent models
+
+If you want to use a Snowflake as the primary key for an Eloquent model, then you'll need to perform a couple of steps.
+
+First, modify the model's migration so that it no longer uses auto-incrementing integers e.g.
+
+```php
+// Before
+$table->id();
+
+// After
+$table->unsignedBigInteger('id')->primary();
+```
+
+Here's an example:
+
+```php
+class CreateUsersTable extends Migration
+{
+    public function up()
+    {
+        Schema::create('users', function(Blueprint $table) {
+            $table->unsignedBigInteger('id')->primary();
+            $table->string('name', 100);
+            $table->timestamps();
+        });
+    }
+}
+```
+
+Finally, add the package's `Snowflakes` trait to the model:
+
+```php
+<?php
+
+namespace App\Models;
+
+use Snowflake\Snowflakes;
+
+class User extends Model
+{
+    use Snowflakes;
+}
+```
+
+## Contributing
+
+Thank you for considering a contribution to Snowflake. You are welcome to submit a PR containing improvements, however if they are substantial in nature, please also be sure to include a test or tests.
+
+## Support the project
+
+If you'd like to support the development of Snowflake, then please consider [sponsoring me](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=YBEHLHPF3GUVY&source=url). Thanks so much!
+
+## License
+
+The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100755
index 0000000..8976ec7
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,45 @@
+{
+    "name": "mattkingshott/snowflake",
+    "description": "A package to create Twitter Snowflake identifiers",
+    "keywords": [
+        "snowflake",
+        "php",
+        "laravel",
+        "database"
+    ],
+    "type": "library",
+    "license": "MIT",
+    "homepage": "https://github.com/mattkingshott/snowflake",
+    "autoload": {
+        "psr-4": {
+            "Snowflake\\": "src"
+        },
+        "files": [
+            "src/Helpers.php"
+        ]
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Snowflake\\Tests\\": "tests"
+        }
+    },
+    "require": {
+        "php": "^8.0",
+        "godruoyi/php-snowflake": "^2.0"
+    },
+    "require-dev": {
+        "orchestra/testbench": "^6.0",
+        "phpunit/phpunit": "^9.0"
+    },
+    "extra": {
+        "laravel": {
+            "providers": [
+                "Snowflake\\ServiceProvider"
+            ]
+        }
+    },
+    "scripts": {
+        "test": "vendor/bin/phpunit"
+    },
+    "minimum-stability": "stable"
+}
diff --git a/config/snowflake.php b/config/snowflake.php
new file mode 100644
index 0000000..bc8e26e
--- /dev/null
+++ b/config/snowflake.php
@@ -0,0 +1,57 @@
+<?php declare(strict_types = 1);
+
+use Godruoyi\Snowflake\RandomSequenceResolver;
+
+return [
+
+    /*
+    |--------------------------------------------------------------------------
+    | Data Center
+    |--------------------------------------------------------------------------
+    |
+    | This value represents the data center reference that should be used by
+    | Snowflake when generating unique identifiers. The value must be 1 - 31.
+    |
+    */
+
+    'data_center' => 1,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Worker Node
+    |--------------------------------------------------------------------------
+    |
+    | This value represents the worker node reference that should be used by
+    | Snowflake when generating unique identifiers. The value must be 1 - 31.
+    |
+    */
+
+    'worker_node' => 1,
+
+    /*
+    |--------------------------------------------------------------------------
+    | Start Timestamp
+    |--------------------------------------------------------------------------
+    |
+    | This value represents the starting date for generating new timestamps.
+    | Snowflakes can be created for 69 years past this date. In most cases,
+    | you should set this value to the current date when building a new app.
+    |
+    */
+
+    'start_timestamp' => '2022-01-01',
+
+    /*
+    |--------------------------------------------------------------------------
+    | Sequence Resolver
+    |--------------------------------------------------------------------------
+    |
+    | This value represents the sequencing strategy that should be used to
+    | ensure that multiple Snowflakes generated within the same millisecond
+    | are unique. The default is a good choice, as it has no dependencies.
+    |
+    */
+
+    'sequence_resolver' => RandomSequenceResolver::class,
+
+];
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..f4b880c
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="vendor/autoload.php" backupGlobals="false" backupStaticAttributes="false" colors="true" verbose="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
+  <coverage>
+    <include>
+      <directory suffix=".php">src/</directory>
+    </include>
+  </coverage>
+  <testsuites>
+    <testsuite name="Test Suite">
+      <directory>tests</directory>
+    </testsuite>
+  </testsuites>
+  <php>
+    <env name="APP_NAME" value="waterfall"/>
+  </php>
+</phpunit>
\ No newline at end of file
diff --git a/resources/license.svg b/resources/license.svg
new file mode 100644
index 0000000..d43fcb9
--- /dev/null
+++ b/resources/license.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="81" height="20">
+    <linearGradient id="b" x2="0" y2="100%">
+        <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
+        <stop offset="1" stop-opacity=".1"/>
+    </linearGradient>
+    <mask id="a">
+        <rect width="81" height="20" rx="3" fill="#fff"/>
+    </mask>
+    <g mask="url(#a)">
+        <rect width="50" height="20" fill="#555"/>
+        <rect x="50" width="31" height="20" fill="#428F7E"/>
+        <rect width="81" height="20" fill="url(#b)"/>
+    </g>
+    <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
+        <text x="26" y="15" fill="#010101" fill-opacity=".3">license</text>
+        <text x="26" y="14">license</text>
+        <text x="64.5" y="15" fill="#010101" fill-opacity=".3">MIT</text>
+        <text x="64.5" y="14">MIT</text>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/resources/version.svg b/resources/version.svg
new file mode 100644
index 0000000..fafce6e
--- /dev/null
+++ b/resources/version.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20">
+    <linearGradient id="b" x2="0" y2="100%">
+        <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
+        <stop offset="1" stop-opacity=".1"/>
+    </linearGradient>
+    <mask id="a">
+        <rect width="92" height="20" rx="3" fill="#fff"/>
+    </mask>
+    <g mask="url(#a)">
+        <rect width="45" height="20" fill="#555"/>
+        <rect x="45" width="47" height="20" fill="#28a3df"/>
+        <rect width="92" height="20" fill="url(#b)"/>
+    </g>
+    <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
+        <text x="23.5" y="15" fill="#010101" fill-opacity=".3">stable</text>
+        <text x="23.5" y="14">stable</text>
+        <text x="67.5" y="15" fill="#010101" fill-opacity=".3">v1.0.0</text>
+        <text x="67.5" y="14">v1.0.0</text>
+    </g>
+</svg>
diff --git a/resources/wallpaper.jpg b/resources/wallpaper.jpg
new file mode 100644
index 0000000..c3caa75
Binary files /dev/null and b/resources/wallpaper.jpg differ
diff --git a/src/Helpers.php b/src/Helpers.php
new file mode 100644
index 0000000..c45ca86
--- /dev/null
+++ b/src/Helpers.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types = 1);
+
+/**
+ * Generate a new Snowflake identifier.
+ *
+ */
+function snowflake() : int
+{
+    return (int) resolve('snowflake')->id();
+}
diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php
new file mode 100644
index 0000000..d24c92f
--- /dev/null
+++ b/src/ServiceProvider.php
@@ -0,0 +1,49 @@
+<?php declare(strict_types=1);
+
+namespace Snowflake;
+
+use Godruoyi\Snowflake\Snowflake;
+use Godruoyi\Snowflake\RandomSequenceResolver;
+use Illuminate\Support\ServiceProvider as Provider;
+
+class ServiceProvider extends Provider
+{
+    /**
+     * Bootstrap any package services.
+     *
+     */
+    public function boot() : void
+    {
+        $this->publishes([__DIR__ . '/../config/snowflake.php' => config_path('snowflake.php')]);
+    }
+
+    /**
+     * Register any package services.
+     *
+     */
+    public function register() : void
+    {
+        $this->mergeConfigFrom(__DIR__ . '/../config/snowflake.php', 'snowflake');
+
+        $this->app->singleton('snowflake', fn() => $this->singleton());
+    }
+
+    /**
+     * Register the Snowflake singleton service.
+     *
+     */
+    protected function singleton() : Snowflake
+    {
+        $service = new Snowflake(
+            config('snowflake.data_center', 1),
+            config('snowflake.worker_node', 1)
+        );
+
+        $timestamp = strtotime(config('snowflake.start_timestamp', '2022-01-01')) * 1000;
+        $resolver  = config('snowflake.sequence_resolver', RandomSequenceResolver::class);
+
+        return $service
+            ->setStartTimeStamp($timestamp)
+            ->setSequenceResolver(new $resolver());
+    }
+}
diff --git a/src/Snowflakes.php b/src/Snowflakes.php
new file mode 100644
index 0000000..1e925fb
--- /dev/null
+++ b/src/Snowflakes.php
@@ -0,0 +1,28 @@
+<?php declare(strict_types=1);
+
+namespace Snowflake;
+
+trait Snowflakes
+{
+    /**
+     * Bootstrap the trait.
+     *
+     */
+    public static function bootSnowflakes() : void
+    {
+        static::creating(function($model) {
+            if (! $model->getKey()) {
+               $model->{$model->getKeyName()} = snowflake();
+            }
+        });
+    }
+
+    /**
+     * Disable auto-incrementing integers.
+     *
+     */
+    public function getIncrementing() : bool
+    {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/tests/Test.php b/tests/Test.php
new file mode 100644
index 0000000..bbc0c7c
--- /dev/null
+++ b/tests/Test.php
@@ -0,0 +1,36 @@
+<?php declare(strict_types=1);
+
+namespace Snowflake\Tests;
+
+use Snowflake\ServiceProvider;
+use Orchestra\Testbench\TestCase;
+
+class Test extends TestCase
+{
+    /**
+     * Setup the test environment.
+     *
+     */
+    protected function setUp() : void
+    {
+        parent::setUp();
+
+        (new ServiceProvider(app()))->register();
+    }
+
+    /** @test */
+    public function it_can_resolve_the_snowflake_service_and_generate_an_identifier() : void
+    {
+        $this->assertTrue(is_string(resolve('snowflake')->id()));
+
+        $this->assertEquals(16, strlen(resolve('snowflake')->id()));
+    }
+
+    /** @test */
+    public function it_can_generate_a_snowflake_identifier_using_the_global_helper() : void
+    {
+        $this->assertTrue(is_int(snowflake()));
+
+        $this->assertEquals(16, strlen((string) snowflake()));
+    }
+}
diff --git a/tools/.php-cs-fixer.php b/tools/.php-cs-fixer.php
new file mode 100644
index 0000000..7378429
--- /dev/null
+++ b/tools/.php-cs-fixer.php
@@ -0,0 +1,52 @@
+<?php declare(strict_types = 1);
+
+$finder = Symfony\Component\Finder\Finder::create()
+    ->notPath(dirname(__DIR__, 1) . '/bootstrap/*')
+    ->notPath(dirname(__DIR__, 1) . '/storage/*')
+    ->notPath(dirname(__DIR__, 1) . '/vendor')
+    ->notPath(dirname(__DIR__, 1) . '/resources/view/mail/*')
+    ->in([
+        dirname(__DIR__, 1) . '/src',
+        dirname(__DIR__, 1) . '/tests',
+    ])
+    ->name('*.php')
+    ->notName('*.blade.php')
+    ->ignoreDotFiles(true)
+    ->ignoreVCS(true);
+
+return (new PhpCsFixer\Config())
+    ->setRules([
+        '@PSR2'                             => true,
+        'array_syntax'                      => ['syntax' => 'short'],
+        'ordered_imports'                   => ['sort_algorithm' => 'length'],
+        'no_unused_imports'                 => true,
+        'not_operator_with_successor_space' => true,
+        'trailing_comma_in_multiline'       => ['elements' => ['arrays']],
+        'phpdoc_scalar'                     => true,
+        'unary_operator_spaces'             => true,
+        'binary_operator_spaces'            => [
+            'operators' => ['=' => 'align', '=>' => 'align'],
+        ],
+        'blank_line_before_statement' => [
+            'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'try'],
+        ],
+        'phpdoc_single_line_var_spacing' => true,
+        'phpdoc_var_without_name'        => true,
+        'class_attributes_separation'    => [
+            'elements' => [
+               'method'   => 'one',
+               'property' => 'one',
+            ],
+        ],
+        'method_argument_space' => [
+            'on_multiline'                     => 'ensure_fully_multiline',
+            'keep_multiple_spaces_after_comma' => true,
+        ],
+        'method_chaining_indentation'        => true,
+        'object_operator_without_whitespace' => true,
+        'no_superfluous_phpdoc_tags'         => true,
+        'function_declaration'               => [
+            'closure_function_spacing' => 'none',
+        ],
+    ])
+    ->setFinder($finder);