Skip to content

Commit 2e349f2

Browse files
committed
chore: add basic example
1 parent b84fc85 commit 2e349f2

28 files changed

+3719
-5
lines changed

examples/turnkey/app/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

examples/turnkey/app/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Vue 3 + TypeScript + Vite
2+
3+
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
4+
5+
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).

examples/turnkey/app/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite + Vue + TS</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="/src/main.ts"></script>
12+
</body>
13+
</html>

examples/turnkey/app/package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "app",
3+
"private": true,
4+
"version": "0.0.0",
5+
"type": "module",
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "vue-tsc -b && vite build",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"@injectivelabs/networks": "link:../../packages/networks",
13+
"@injectivelabs/sdk-ts": "link:../../packages/sdk-ts",
14+
"@injectivelabs/wallet-base": "link:../../packages/wallets/wallet-base",
15+
"@injectivelabs/wallet-core": "link:../../packages/wallets/wallet-core",
16+
"@injectivelabs/wallet-turnkey": "link:../../packages/wallets/wallet-turnkey",
17+
"vue": "^3.5.13"
18+
},
19+
"devDependencies": {
20+
"@vitejs/plugin-vue": "^5.2.1",
21+
"@vue/tsconfig": "^0.7.0",
22+
"typescript": "~5.7.2",
23+
"vite": "^6.2.0",
24+
"vite-plugin-node-polyfills": "^0.23.0",
25+
"vue-tsc": "^2.2.4"
26+
}
27+
}

examples/turnkey/app/public/vite.svg

Lines changed: 1 addition & 0 deletions
Loading

examples/turnkey/app/src/App.vue

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<script setup lang="ts">
2+
import { BaseWalletStrategy, MsgBroadcaster } from '@injectivelabs/wallet-core'
3+
import {
4+
TurnkeyWallet,
5+
TurnkeyWalletStrategy,
6+
type TurnkeyStatus,
7+
} from '@injectivelabs/wallet-turnkey/src/index.ts'
8+
import { injectiveClients } from './injective-clients'
9+
import { computed, onMounted, ref, watch } from 'vue'
10+
import { Wallet } from '@injectivelabs/wallet-base'
11+
import LoginForm from './components/LoginForm.vue'
12+
import Connected from './components/Connected.vue'
13+
import {
14+
getEthereumAddress,
15+
type Msgs,
16+
type TxResponse,
17+
} from '@injectivelabs/sdk-ts'
18+
const turnkeyAuthIframeContainerId = 'turnkey-auth-iframe-container-id'
19+
20+
const turnkeyStrategy = ref<TurnkeyWalletStrategy | null>(null)
21+
const broadcaster = ref<MsgBroadcaster | null>(null)
22+
const turnkeyStatus = ref<TurnkeyStatus | null>(null)
23+
const address = ref<string | null>(null)
24+
25+
const turnkeyReadyAndLoggedIn = computed(() => {
26+
return turnkeyStatus.value === 'logged-in' && turnkeyStrategy.value
27+
})
28+
29+
onMounted(async () => {
30+
const _turnkeyStrategy = await TurnkeyWallet.create({
31+
onStatusChange(status) {
32+
turnkeyStatus.value = status
33+
},
34+
chainId: injectiveClients.chainId,
35+
ethereumOptions: {
36+
ethereumChainId: injectiveClients.ethereumChainId!,
37+
},
38+
metadata: {
39+
turnkeyAuthIframeContainerId,
40+
defaultOrganizationId: import.meta.env
41+
.VITE_TURNKEY_DEFAULT_ORGANIZATION_ID,
42+
apiBaseUrl: 'https://api.turnkey.com',
43+
},
44+
})
45+
46+
turnkeyStrategy.value = _turnkeyStrategy
47+
})
48+
49+
watch(turnkeyReadyAndLoggedIn, async (_ready) => {
50+
if (!_ready) {
51+
return
52+
}
53+
54+
console.log('Watching and updating broadcaster')
55+
56+
const _walletStrategy = new BaseWalletStrategy({
57+
strategies: {
58+
[Wallet.Turnkey]: turnkeyStrategy.value as TurnkeyWalletStrategy,
59+
},
60+
chainId: injectiveClients.chainId,
61+
wallet: Wallet.Turnkey,
62+
})
63+
64+
broadcaster.value = new MsgBroadcaster({
65+
network: injectiveClients.network,
66+
walletStrategy: _walletStrategy,
67+
ethereumChainId: injectiveClients.ethereumChainId!,
68+
endpoints: injectiveClients.endpoints,
69+
})
70+
71+
const addresses = await _walletStrategy?.getAddresses()
72+
console.log('🪵 | addresses:', addresses)
73+
console.log('relevant address: ', addresses[0])
74+
address.value = addresses[0]
75+
})
76+
77+
watch(turnkeyStatus, (status) => {
78+
console.log('🪵 | turnkeyStatus:', status)
79+
})
80+
81+
async function sendTx(msgs: Msgs): Promise<TxResponse> {
82+
if (!broadcaster.value) {
83+
throw new Error('Broadcaster not initialized')
84+
}
85+
86+
if (!address.value) {
87+
throw new Error('Address not initialized')
88+
}
89+
90+
const result = await broadcaster.value.broadcastWithFeeDelegation({
91+
msgs,
92+
injectiveAddress: address.value,
93+
ethereumAddress: getEthereumAddress(address.value),
94+
})
95+
96+
console.log('🪵 | result:', result)
97+
98+
return result
99+
}
100+
</script>
101+
102+
<template>
103+
<h1>Injective + Turnkey</h1>
104+
<LoginForm
105+
:init-email-o-t-p="turnkeyStrategy.initEmailOTP.bind(turnkeyStrategy)"
106+
:confirm-email-o-t-p="turnkeyStrategy.confirmEmailOTP.bind(turnkeyStrategy)"
107+
:turnkey-status="turnkeyStatus"
108+
v-if="turnkeyStrategy && turnkeyStatus && turnkeyStatus !== 'logged-in'"
109+
/>
110+
<Connected
111+
v-if="address && turnkeyStatus === 'logged-in'"
112+
:address="address"
113+
:send-tx="sendTx"
114+
:logout="turnkeyStrategy?.disconnect.bind(turnkeyStrategy)"
115+
/>
116+
<div :id="turnkeyAuthIframeContainerId" style="display: none"></div>
117+
</template>
118+
119+
<style>
120+
input {
121+
padding: 10px;
122+
border-radius: 5px;
123+
border: 1px solid #ccc;
124+
margin-bottom: 10px;
125+
}
126+
</style>
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<script lang="ts" setup>
2+
import { MsgSend, type Msgs, type TxResponse } from '@injectivelabs/sdk-ts'
3+
import { ref } from 'vue'
4+
5+
const { address, sendTx, logout } = defineProps<{
6+
address: string
7+
sendTx: (msgs: Msgs) => Promise<TxResponse>
8+
logout?: () => Promise<void>
9+
}>()
10+
11+
const to = ref(address)
12+
const from = ref(address)
13+
const amount = ref('1000000')
14+
// const amount = ref('1')
15+
const denom = ref(
16+
'factory/inj1dm0yt646fsjsvznjz2twyht9ytcmwz3aqydjjp/RealTrumPepe',
17+
)
18+
// const denom = ref('inj')
19+
20+
const txHash = ref('')
21+
22+
const isLoading = ref(false)
23+
24+
const handleSubmit = async () => {
25+
console.log('to', to.value)
26+
console.log('amount', amount.value)
27+
28+
if (!to.value || !amount.value || !denom.value) {
29+
throw new Error('Invalid input')
30+
}
31+
32+
isLoading.value = true
33+
34+
const msgs = MsgSend.fromJSON({
35+
srcInjectiveAddress: from.value,
36+
dstInjectiveAddress: to.value,
37+
amount: [
38+
{
39+
denom: denom.value,
40+
amount: amount.value,
41+
},
42+
],
43+
})
44+
45+
try {
46+
const result = await sendTx(msgs)
47+
if (result.txHash) {
48+
txHash.value = result.txHash
49+
}
50+
} catch (error) {
51+
console.error('Error sending tx', error)
52+
} finally {
53+
isLoading.value = false
54+
}
55+
}
56+
</script>
57+
58+
<template>
59+
<h2>Connected!</h2>
60+
<button @click="logout">Logout</button>
61+
<div>
62+
<span>Addresses: {{ address }}</span>
63+
</div>
64+
65+
<h3>Send Tx</h3>
66+
<form>
67+
<label>
68+
<span>To</span>
69+
<input type="text" v-model="to" />
70+
</label>
71+
<label>
72+
<span>Amount</span>
73+
<input type="text" v-model="amount" />
74+
</label>
75+
<label>
76+
<span>Denom</span>
77+
<input type="text" v-model="denom" />
78+
</label>
79+
<button type="submit" @click.prevent="handleSubmit" :disabled="isLoading">
80+
{{ isLoading ? 'Sending...' : 'Submit' }}
81+
</button>
82+
<div v-if="txHash">
83+
<span>Tx Hash: {{ txHash }}</span>
84+
<a :href="`https://injscan.com/transaction/${txHash}`" target="_blank">
85+
<span>View on Explorer</span>
86+
</a>
87+
</div>
88+
</form>
89+
</template>
90+
91+
<style>
92+
form {
93+
display: flex;
94+
flex-direction: column;
95+
gap: 10px;
96+
}
97+
98+
label {
99+
display: flex;
100+
flex-direction: column;
101+
gap: 5px;
102+
}
103+
</style>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
import {
4+
TurnkeyWallet,
5+
type TurnkeyStatus,
6+
} from '@injectivelabs/wallet-turnkey/src/index.ts'
7+
8+
const { initEmailOTP, confirmEmailOTP, turnkeyStatus } = defineProps<{
9+
initEmailOTP: typeof TurnkeyWallet.prototype.initEmailOTP
10+
confirmEmailOTP: typeof TurnkeyWallet.prototype.confirmEmailOTP
11+
turnkeyStatus: TurnkeyStatus
12+
}>()
13+
14+
const email = ref('')
15+
const OTP = ref('')
16+
17+
function handleEmailSubmit() {
18+
console.log('email', email.value)
19+
initEmailOTP({
20+
email: email.value,
21+
endpoint: 'http://localhost:3000/turnkey/init-email-auth',
22+
})
23+
}
24+
25+
async function handleOTPSubmit() {
26+
console.log('OTP', OTP.value)
27+
await confirmEmailOTP({
28+
otpCode: OTP.value,
29+
endpoint: 'http://localhost:3000/turnkey/verify-email-auth',
30+
})
31+
}
32+
33+
function handleSubmit() {
34+
if (turnkeyStatus === 'waiting-otp') {
35+
return handleOTPSubmit()
36+
}
37+
38+
if (turnkeyStatus === 'ready') {
39+
return handleEmailSubmit()
40+
}
41+
42+
throw new Error('Turnkey not ready')
43+
}
44+
</script>
45+
46+
<template>
47+
<div style="gap: 10px; display: flex; flex-direction: column">
48+
<form @submit.prevent="handleSubmit">
49+
<label>
50+
<span>Email address </span>
51+
<input
52+
type="email"
53+
v-model="email"
54+
required
55+
:disabled="turnkeyStatus === 'waiting-otp'"
56+
/>
57+
</label>
58+
<label v-if="turnkeyStatus === 'waiting-otp'">
59+
<span>OTP</span>
60+
<input
61+
type="text"
62+
v-model="OTP"
63+
:required="turnkeyStatus === 'waiting-otp'"
64+
/>
65+
</label>
66+
<button type="submit">Submit</button>
67+
</form>
68+
<!-- TODO -->
69+
<!-- <div>or</div>
70+
<button @click="">Login with Google</button> -->
71+
</div>
72+
</template>
73+
74+
<style scoped>
75+
form {
76+
display: flex;
77+
flex-direction: column;
78+
gap: 10px;
79+
}
80+
81+
label {
82+
display: flex;
83+
flex-direction: column;
84+
gap: 5px;
85+
}
86+
</style>

0 commit comments

Comments
 (0)