From f9b341f7c55ad67311d0ee66a797b847246515aa Mon Sep 17 00:00:00 2001
From: Arnaud Brousseau <rno@turnkey.io>
Date: Thu, 22 Feb 2024 15:40:22 -0600
Subject: [PATCH] Add message signing example

---
 .ruby-version                 |  1 +
 README.md                     |  1 +
 examples/signing/.env.example |  6 +++
 examples/signing/Gemfile      |  4 ++
 examples/signing/Gemfile.lock | 29 ++++++++++++
 examples/signing/README.md    | 22 +++++++++
 examples/signing/signing.rb   | 85 +++++++++++++++++++++++++++++++++++
 7 files changed, 148 insertions(+)
 create mode 100644 .ruby-version
 create mode 100644 examples/signing/.env.example
 create mode 100644 examples/signing/Gemfile
 create mode 100644 examples/signing/Gemfile.lock
 create mode 100644 examples/signing/README.md
 create mode 100644 examples/signing/signing.rb

diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 0000000..9f55b2c
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+3.0
diff --git a/README.md b/README.md
index 514016b..60e920f 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,7 @@ See the [examples](./examples/) folder if you're looking for a complete sample p
 ## Examples
 
 * [Whoami](./examples/whoami/): example showcasing API request signing in its simplest form
+* [Signing](./examples/whoami/): example showcasing wallet creation and ETH message signing
 
 ## Using Turnkey in your Rails projects
 
diff --git a/examples/signing/.env.example b/examples/signing/.env.example
new file mode 100644
index 0000000..34b4a2b
--- /dev/null
+++ b/examples/signing/.env.example
@@ -0,0 +1,6 @@
+# If you do not have an organization, create one at https://app.turnkey.com/dashboard/auth/initial
+TURNKEY_ORGANIZATION_ID=<your-organization-id>
+# API credentials. They need to be attached to a user in your organization.
+# These credentials must be a valid hex-encoded public/private key pair (P256)
+TURNKEY_API_PUBLIC_KEY=<your-public-key>
+TURNKEY_API_PRIVATE_KEY=<your-private-key>
diff --git a/examples/signing/Gemfile b/examples/signing/Gemfile
new file mode 100644
index 0000000..7f3948d
--- /dev/null
+++ b/examples/signing/Gemfile
@@ -0,0 +1,4 @@
+source 'https://rubygems.org'
+
+gem 'dotenv'
+gem 'turnkey_client', path: '../../turnkey_client/'
diff --git a/examples/signing/Gemfile.lock b/examples/signing/Gemfile.lock
new file mode 100644
index 0000000..dc089b4
--- /dev/null
+++ b/examples/signing/Gemfile.lock
@@ -0,0 +1,29 @@
+PATH
+  remote: ../../turnkey_client
+  specs:
+    turnkey_client (0.0.1)
+      json (~> 2.1, >= 2.1.0)
+      openssl (~> 3.2, >= 3.0.0)
+      typhoeus (~> 1.0, >= 1.0.1)
+
+GEM
+  remote: https://rubygems.org/
+  specs:
+    dotenv (3.0.2)
+    ethon (0.16.0)
+      ffi (>= 1.15.0)
+    ffi (1.16.3)
+    json (2.7.1)
+    openssl (3.2.0)
+    typhoeus (1.4.1)
+      ethon (>= 0.9.0)
+
+PLATFORMS
+  arm64-darwin-23
+
+DEPENDENCIES
+  dotenv
+  turnkey_client!
+
+BUNDLED WITH
+   2.2.3
diff --git a/examples/signing/README.md b/examples/signing/README.md
new file mode 100644
index 0000000..a8f497b
--- /dev/null
+++ b/examples/signing/README.md
@@ -0,0 +1,22 @@
+# Who am I?
+
+This examples shows how to use `turnkey_client` to create a new wallet and sign a message with it.
+
+To run this example:
+* `cp .env.example .env`
+* Follow the instructions in the `.env` file to fill in values for `TURNKEY_ORGANIZATION_ID`, `TURNKEY_API_PUBLIC_KEY`, and `TURNKEY_API_PRIVATE_KEY`
+* Install the dependencies: `bundle install`
+* Then run the `signing.rb` script: `bundle exec ruby signing.rb`
+
+You should be able to verify the signature produced via Etherscan. Here's an example run:
+
+```
+created new wallet successfully
+successful signature
+- address: 0xf0635c94F7bEEcF9abC77F512Bf33BB66Fe9997f
+- message: "hello from Turnkey, in Ruby!"
+- signature hash: 0x9b55914814658b4cb592ed6782f2e639b28b89d122240c34f7ea5d7afd48c5861271b008ae62d15131cdd95fbc6a8498ea8379e20ad8fa0956dfd207c6def1d21b
+You can verify for yourself at https://etherscan.io/verifiedSignatures > "Verify Signature"
+```
+
+Proof of validity: https://etherscan.io/verifySig/36331
diff --git a/examples/signing/signing.rb b/examples/signing/signing.rb
new file mode 100644
index 0000000..e651276
--- /dev/null
+++ b/examples/signing/signing.rb
@@ -0,0 +1,85 @@
+require 'turnkey_client'
+require 'dotenv'
+require 'json'
+require 'date'
+
+# Load local .env file
+Dotenv.load
+
+raise 'Please set TURNKEY_ORGANIZATION_ID in your .env file' if ENV['TURNKEY_ORGANIZATION_ID'].nil?
+
+# Make a whoami request
+begin
+  client = TurnkeyClient.configure do |c|
+    c.api_public_key = ENV['TURNKEY_API_PUBLIC_KEY']
+    c.api_private_key = ENV['TURNKEY_API_PRIVATE_KEY']
+  end
+
+  # https://docs.turnkey.com/api#tag/Wallets/operation/CreateWallet
+  now_in_ms = DateTime.now.strftime('%Q')
+
+  # We can also use `TurnkeyClient::V1CreateWalletRequest` to create the request object here
+  create_wallet_response = TurnkeyClient::WalletsApi.new(client).create_wallet(
+    {
+      type: 'ACTIVITY_TYPE_CREATE_WALLET',
+      organizationId: ENV['TURNKEY_ORGANIZATION_ID'],
+      timestampMs: DateTime.now.strftime('%Q'),
+      parameters: {
+        walletName: "Wallet @#{now_in_ms}",
+        mnemonicLength: 12,
+        accounts: [
+          {
+            curve: 'CURVE_SECP256K1',
+            pathFormat: 'PATH_FORMAT_BIP32',
+            # Default ETH account
+            path: "m/44'/60'/0'/0/0",
+            addressFormat: 'ADDRESS_FORMAT_ETHEREUM'
+          }
+        ]
+      }
+    }
+  )
+
+  created_wallet = create_wallet_response&.activity&.dig(:result, :createWalletResult)
+  raise "Something went wrong: no wallet in activity response: #{create_wallet_response.to_hash}" if created_wallet.nil?
+
+  puts 'created new wallet successfully'
+
+  eth_address = created_wallet[:addresses].first
+
+  # Now sign!
+  # https://docs.turnkey.com/api#tag/Signing/operation/SignRawPayload
+  message = 'hello from Turnkey, in Ruby!'
+  eth_message = "\u0019Ethereum Signed Message:\n#{message.size}#{message}"
+
+  signature_response = TurnkeyClient::SigningApi.new(client).sign_raw_payload(
+    {
+      type: 'ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2',
+      organizationId: ENV['TURNKEY_ORGANIZATION_ID'],
+      timestampMs: DateTime.now.strftime('%Q'),
+      parameters: {
+        signWith: eth_address,
+        payload: eth_message,
+        encoding: 'PAYLOAD_ENCODING_TEXT_UTF8',
+        hashFunction: 'HASH_FUNCTION_KECCAK256'
+      }
+    }
+  )
+
+  signature = signature_response&.activity&.dig(:result, :signRawPayloadResult)
+  raise "Something went wrong: no signature in activity response: #{signature_response.to_hash}" if signature.nil?
+
+  puts 'successful signature'
+
+  eth_v = if signature[:v] == '00'
+    '1b' # 27 in hex
+  else
+    '1c' # 28 in hex
+  end
+  puts "- address: #{eth_address}"
+  puts "- message: \"#{message}\""
+  puts "- signature hash: 0x#{signature[:r]}#{signature[:s]}#{eth_v}"
+  puts 'You can verify for yourself at https://etherscan.io/verifiedSignatures > "Verify Signature"'
+rescue TurnkeyClient::ApiError => e
+  puts "Exception when calling Whoami endpoint: #{e}"
+end