Skip to content

Commit 3369fd2

Browse files
committed
Add TerraLedgerWallet
1 parent 03e7894 commit 3369fd2

8 files changed

Lines changed: 1934 additions & 43 deletions

File tree

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ wallet
2626
.then(({ address }) => console.log(address))
2727
```
2828

29+
> If you are using `TerraLedgerWallet` you can specify more options on the connect function, to select a custom derivation index (default is 0) and to use bluetooth (default is USB)
30+
31+
2932
Get the balance (wallet must be already connected):
3033
```js
3134
wallet
@@ -67,11 +70,12 @@ wallet
6770
})
6871
```
6972
70-
> You can use the same functions on the `KeplrWallet` and `MetaMaskWallet` to send a tx from those wallets
73+
> You can use the same functions on the `TerraLedgerWallet`,`KeplrWallet` and `MetaMaskWallet` to send a tx from those wallets
7174
>
7275
> You can find more info about the available functions on the [Wallet inteface](/src/wallets/Wallet.ts#25)
7376
7477
78+
7579
## Coming soon
7680
7781
- More IBC chains

package/package-lock.json

Lines changed: 1647 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@terra-money/bridge-sdk",
3-
"version": "1.0.0-beta.1",
3+
"version": "1.0.0-beta.2",
44
"description": "",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",
@@ -41,8 +41,13 @@
4141
"dependencies": {
4242
"@axelar-network/axelarjs-sdk": "^0.11.4",
4343
"@cosmjs/stargate": "^0.28.11",
44+
"@ledgerhq/hw-transport-web-ble": "^6.27.6",
4445
"@metamask/onboarding": "^1.0.1",
46+
"@terra-money/ledger-terra-js": "^1.3.0",
4547
"@terra-money/terra.js": "^3.1.5",
48+
"@walletconnect/core": "^1.8.0",
49+
"@walletconnect/iso-crypto": "^1.8.0",
50+
"@walletconnect/utils": "^1.8.0",
4651
"ethers": "^5.6.9"
4752
}
4853
}

package/src/wallets/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './Wallet'
22
export * from './station/StationWallet'
33
export * from './keplr/KeplrWallet'
44
export * from './metamask/MetaMaskWallet'
5+
export * from './terraLedger/TerraLedgerWallet'
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
import {
2+
Coin,
3+
CreateTxOptions,
4+
LCDClient,
5+
MsgTransfer,
6+
Wallet,
7+
} from '@terra-money/terra.js'
8+
import { LedgerKey } from "@terra-money/ledger-terra-js"
9+
import BluetoothTransport from "@ledgerhq/hw-transport-web-ble"
10+
import { BridgeType } from '../../const/bridges'
11+
import { ChainType, ibcChannels } from '../../const/chains'
12+
import { getAxelarDepositAddress } from '../../packages/axelar'
13+
import { QueryResult, Tx, TxResult, Wallet as WalletInterface } from '../Wallet'
14+
15+
16+
export class TerraLedgerWallet implements WalletInterface {
17+
private address: string = ''
18+
private key: LedgerKey | undefined = undefined
19+
20+
isSupported(): boolean {
21+
// supported on chrome and edge (only on desktop)
22+
return (
23+
!navigator.userAgent.match(/Android|iPhone/i) &&
24+
!!navigator.userAgent.match(/chrome|chromium|edg/i)
25+
)
26+
}
27+
28+
isInstalled(): boolean {
29+
return true
30+
}
31+
32+
async connect(chain: ChainType, options?: { index?: number, bluetooth?: boolean }): Promise<{ address: string }> {
33+
if (!this.supportedChains.includes(chain)) {
34+
throw new Error(`${chain} is not supported by ${this.description.name}`)
35+
}
36+
37+
const transport = options?.bluetooth
38+
? await BluetoothTransport.create(120000)
39+
: undefined
40+
41+
const key = await LedgerKey.create(transport, options?.index || 0)
42+
const address = (await key.showAddressAndPubKey()).bech32_address
43+
44+
this.key = key
45+
this.address = address
46+
return { address }
47+
}
48+
49+
async getBalance(
50+
token: string,
51+
): Promise<QueryResult<number>> {
52+
if (!this.address) {
53+
return {
54+
success: false,
55+
error: `You must connect the wallet before the query`,
56+
}
57+
}
58+
59+
const lcd = new LCDClient({
60+
URL: 'https://phoenix-lcd.terra.dev',
61+
chainID: 'phoenix-1',
62+
})
63+
64+
if (token.startsWith('terra1')) {
65+
const result = (await lcd.wasm.contractQuery(token, {
66+
balance: {
67+
address: this.address,
68+
},
69+
})) as {
70+
data: {
71+
balance: string
72+
}
73+
}
74+
75+
return {
76+
success: true,
77+
data: parseInt(result.data.balance),
78+
}
79+
} else {
80+
const result = await lcd.bank.balance(this.address)
81+
const coin = result[0].get(token)
82+
83+
return {
84+
success: true,
85+
data: coin?.amount?.toNumber() || 0,
86+
}
87+
}
88+
}
89+
90+
async transfer(tx: Tx): Promise<TxResult> {
91+
if (!this.address || !this.key) {
92+
return {
93+
success: false,
94+
error: `You must connect the wallet before the transfer`,
95+
}
96+
}
97+
if (!this.supportedChains.includes(tx.src)) {
98+
return {
99+
success: false,
100+
error: `${tx.src} is not supported by ${this.description.name}`,
101+
}
102+
}
103+
if (tx.src === tx.dst) {
104+
return {
105+
success: false,
106+
error: `Source chain and destination chain must be different`,
107+
}
108+
}
109+
110+
const lcd = new LCDClient({
111+
URL: 'https://phoenix-lcd.terra.dev',
112+
chainID: 'phoenix-1',
113+
})
114+
const wallet = new Wallet(lcd, this.key)
115+
116+
switch (tx.bridge) {
117+
case BridgeType.ibc:
118+
// @ts-expect-error
119+
if (!ibcChannels[tx.src]?.[tx.dst]) {
120+
return {
121+
success: false,
122+
error: `One of the chains is not supported by IBC, select a different bridge`,
123+
}
124+
}
125+
126+
127+
const ibcTx: CreateTxOptions = {
128+
msgs: [
129+
new MsgTransfer(
130+
'transfer',
131+
// @ts-expect-error
132+
ibcChannels[tx.src][tx.dst],
133+
new Coin(tx.coin.denom, tx.coin.amount),
134+
this.address,
135+
tx.address,
136+
undefined,
137+
(Date.now() + 120 * 1000) * 1e6,
138+
),
139+
],
140+
}
141+
142+
const ibcRes = await lcd.tx.broadcastSync(await wallet.createAndSignTx(ibcTx))
143+
144+
return {
145+
success: !!ibcRes.height,
146+
txhash: ibcRes.txhash,
147+
error: ibcRes.raw_log,
148+
}
149+
150+
case BridgeType.axelar:
151+
const depositAddress = await getAxelarDepositAddress(
152+
tx.address,
153+
tx.src,
154+
tx.dst,
155+
tx.coin.denom,
156+
)
157+
158+
if (!depositAddress)
159+
return {
160+
success: false,
161+
error: "Can't generate the Axelar deposit address",
162+
}
163+
164+
const axlTx: CreateTxOptions = {
165+
msgs: [
166+
new MsgTransfer(
167+
'transfer',
168+
// @ts-expect-error
169+
ibcChannels[tx.src]?.axelar,
170+
new Coin(tx.coin.denom, tx.coin.amount),
171+
this.address,
172+
depositAddress,
173+
undefined,
174+
(Date.now() + 120 * 1000) * 1e6,
175+
),
176+
],
177+
}
178+
179+
const res = await lcd.tx.broadcastSync(await wallet.createAndSignTx(axlTx))
180+
181+
return {
182+
success: !!res.height,
183+
txhash: res.txhash,
184+
error: res.raw_log,
185+
}
186+
187+
case BridgeType.wormhole:
188+
// TODO: handle wormhole
189+
190+
return {
191+
success: false,
192+
error: 'wormole is not yet supported',
193+
}
194+
}
195+
}
196+
197+
supportedChains = [ChainType.terra]
198+
199+
description = {
200+
name: 'Terra Ledger',
201+
icon: 'https://assets.terra.money/bridge/ledgerTerra.png',
202+
installLink: 'https://ledger.com/',
203+
}
204+
}

template/package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

template/src/LedgerTerra.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { TerraLedgerWallet, ChainType } from '@terra-money/bridge-sdk'
2+
import { useState } from 'react'
3+
4+
export default function LedgerTerraWallet() {
5+
const [address, setAddress] = useState<string | undefined>()
6+
const [balance, setBalance] = useState<number>(0)
7+
const wallet = new TerraLedgerWallet()
8+
9+
return (
10+
<section>
11+
<h3>
12+
<img src={wallet.description.icon} alt={wallet.description.name} />
13+
{wallet.description.name}
14+
</h3>
15+
16+
<p>
17+
Supported:{' '}
18+
{wallet.isSupported() ? (
19+
<b className='green'>Yes</b>
20+
) : (
21+
<b className='red'>No</b>
22+
)}
23+
</p>
24+
<p>
25+
Installed:{' '}
26+
{wallet.isInstalled() ? (
27+
<b className='green'>Yes</b>
28+
) : (
29+
<b className='red'>No</b>
30+
)}
31+
</p>
32+
{address ? (
33+
<>
34+
<p className='blue'>
35+
<b>{address}</b>
36+
</p>
37+
<p>Balance: {balance} uluna</p>
38+
</>
39+
) : (
40+
<>
41+
<p className='red'>
42+
<b>Not connected</b>
43+
</p>
44+
<button
45+
onClick={async () => {
46+
setAddress((await wallet.connect(ChainType.terra)).address)
47+
const balResult = await wallet.getBalance('uluna')
48+
if (balResult.success) {
49+
setBalance(balResult.data)
50+
}
51+
}}
52+
>
53+
Connect
54+
</button>
55+
</>
56+
)}
57+
</section>
58+
)
59+
}

template/src/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import './index.css'
55
import Keplr from './Keplr'
66
import Metamask from './Metamask'
77
import Station from './Station'
8+
import LedgerTerra from './LedgerTerra'
89

910
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
1011
root.render(
@@ -14,6 +15,7 @@ root.render(
1415
<a href='https://github.com/terra-money/bridge-sdk' target='_blank' rel='noreferrer'>GitHub</a>
1516
</header>
1617
<Station />
18+
<LedgerTerra />
1719
<Keplr />
1820
<Metamask />
1921
</React.StrictMode>,

0 commit comments

Comments
 (0)