Skip to content

Commit

Permalink
Feature/wallet link (#311)
Browse files Browse the repository at this point in the history
Co-authored-by: Tyler Whitman <[email protected]>
  • Loading branch information
matthewcarlreetz and tyler-whitman authored Jul 1, 2022
1 parent 37ac9ef commit bf4e89a
Show file tree
Hide file tree
Showing 13 changed files with 718 additions and 12 deletions.
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ Please view the [Documentation](https://helium.github.io/helium-js/) for usage a
This SDK is a collection of TypeScrypt libraries for interacting with the Helium blockchain. For additional documentation about the Helium network, visit the [Developer Site](https://docs.helium.com/).


| Package | NPM Version | What it's for |
|-------------------------------------------------------------------------------------------------|-----------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| [`@helium/crypto`](https://github.com/helium/helium-js/tree/master/packages/crypto) | ![npm](https://img.shields.io/npm/v/@helium/crypto) | Cryptography utilities including keypairs, mnemonics and base58-check encoding |
| [`@helium/crypto-react-native`](https://github.com/helium/helium-js/tree/master/packages/crypto-react-native) | ![npm](https://img.shields.io/npm/v/@helium/crypto-react-native) | Cryptography utilities following the same interface as `@helium/crypto` but for React Native |
| [`@helium/transactions`](https://github.com/helium/helium-js/tree/master/packages/transactions) | ![npm](https://img.shields.io/npm/v/@helium/transactions) | Construct and serialize transaction primitives from their [protobuf](https://developers.google.com/protocol-buffers) definitions |
| [`@helium/proto`](https://github.com/helium/proto) | ![npm](https://img.shields.io/npm/v/@helium/proto) | Protobuf definitions for Helium transactions |
| [`@helium/http`](https://github.com/helium/helium-js/tree/master/packages/http) | ![npm](https://img.shields.io/npm/v/@helium/http) | An HTTP client for the blockchain REST API |
| [`@helium/currency`](https://github.com/helium/helium-js/tree/master/packages/currency) | ![npm](https://img.shields.io/npm/v/@helium/currency) | Utilities for representing amounts of the different currencies supported by Helium |
| Package | NPM Version | What it's for |
|---------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| [`@helium/crypto`](https://github.com/helium/helium-js/tree/master/packages/crypto) | ![npm](https://img.shields.io/npm/v/@helium/crypto) | Cryptography utilities including keypairs, mnemonics and base58-check encoding |
| [`@helium/crypto-react-native`](https://github.com/helium/helium-js/tree/master/packages/crypto-react-native) | ![npm](https://img.shields.io/npm/v/@helium/crypto-react-native) | Cryptography utilities following the same interface as `@helium/crypto` but for React Native |
| [`@helium/transactions`](https://github.com/helium/helium-js/tree/master/packages/transactions) | ![npm](https://img.shields.io/npm/v/@helium/transactions) | Construct and serialize transaction primitives from their [protobuf](https://developers.google.com/protocol-buffers) definitions |
| [`@helium/proto`](https://github.com/helium/proto) | ![npm](https://img.shields.io/npm/v/@helium/proto) | Protobuf definitions for Helium transactions |
| [`@helium/proto-ble`](https://github.com/helium/helium-js/tree/master/packages/proto-ble) | ![npm](https://img.shields.io/npm/v/@helium/proto-ble) | Protobuf definitions for Helium Hotspot ble transactions |
| [`@helium/http`](https://github.com/helium/helium-js/tree/master/packages/http) | ![npm](https://img.shields.io/npm/v/@helium/http) | An HTTP client for the blockchain REST API |
| [`@helium/currency`](https://github.com/helium/helium-js/tree/master/packages/currency) | ![npm](https://img.shields.io/npm/v/@helium/currency) | Utilities for representing amounts of the different currencies supported by Helium |
| [`@helium/onboarding`](https://github.com/helium/helium-js/tree/master/packages/onboarding) | ![npm](https://img.shields.io/npm/v/@helium/onboarding) | An HTTP client for interfacing with an Onboarding Server |
| [`@helium/address`](https://github.com/helium/helium-js/tree/master/packages/address) | ![npm](https://img.shields.io/npm/v/@helium/address) | Utilities for Helium Addresses |
| [`@helium/wallet-link`](https://github.com/helium/helium-js/tree/master/packages/wallet-link) | ![npm](https://img.shields.io/npm/v/@helium/wallet-link) | Utilities for linking a 3rd party app to the Helium Wallet |


## Installation
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"packages/transactions/src",
"packages/currency/src",
"packages/address/src",
"packages/wallet-link/src",
"integration_tests/tests"
],
"collectCoverageFrom": [
Expand Down
4 changes: 2 additions & 2 deletions packages/crypto-react-native/src/Keypair.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ export default class Keypair {
return new Keypair(keypair, netType)
}

async sign(message: string | Uint8Array): Promise<Buffer> {
async sign(message: string | Uint8Array): Promise<Uint8Array> {
const messageBuffer = Buffer.from(message)
const signature = await Sodium.crypto_sign_detached(
messageBuffer.toString('base64'),
this.privateKey.toString('base64'),
)
return Buffer.from(signature, 'base64')
return Uint8Array.from(Buffer.from(signature, 'base64'))
}
}
2 changes: 1 addition & 1 deletion packages/onboarding/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* [[include:http/README.md]]
* [[include:onboarding/README.md]]
* @packageDocumentation
* @module onboarding
*/
Expand Down
69 changes: 69 additions & 0 deletions packages/wallet-link/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# `@helium/wallet-link`

Utilities for linking a 3rd party app to the helium wallet. The link token is used for hotspot onboarding, location
assertion, and ownership transfer transaction signing with the Helium Wallet and Hotspot apps.

## Installation

### React Native

```shell
$ yarn add @helium/wallet-link @helium/crypto-react-native
$ yarn add --dev patch-package postinstall-postinstall
# or
$ npm install @helium/wallet-link @helium/crypto-react-native
$ npm install --save-dev patch-package
```

When using this library in React Native you must patch the `@helium/crypto` calls
with `@helium/crypto-react-native`. You can do this using [patch-package](https://github.com/ds300/patch-package)
by adding the following file to your React Native root at `/patches/@wallet-link+4.4.0.patch`.

```
TODO: Add patch
```

### Browser or other JS environments

```shell
$ yarn add @helium/wallet-link @helium/crypto
# or
$ npm install @helium/wallet-link @helium/crypto
```

## Usage

```ts
// Create link to Helium app
const url = createWalletLinkUrl({
universalLink: 'https://wallet.helium.com/',
requestAppId: 'com.maker.app',
callbackUrl: 'makerappscheme://',
appName: 'Maker App',
})

Linking.openURL(url)

// parse received token
const parsed = parseWalletLinkToken(token)

// verify token
const verified = verifyWalletLinkToken(parsed)

// verify token with max age
const verified = verifyWalletLinkToken(parsed, { maxAgeInSeconds: 60 })

// Create link to update a hotspot
const updateParams = {
token,
platform: Platform.OS,
addGatewayTxn: 'your_optional_unsigned_txn',
assertLocationTxn: 'your_optional_unsigned_txn',
transferHotspotTxn: 'your_optional_unsigned_txn',
} as SignHotspotRequest

const url = createUpdateHotspotUrl(updateParams)
Linking.openURL(url)

// submit signed txn
```
40 changes: 40 additions & 0 deletions packages/wallet-link/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@helium/wallet-link",
"version": "4.4.0",
"description": "Utilities for linking a 3rd party app to the helium wallet.",
"keywords": [
"helium",
"blockchain",
"react-native"
],
"contributors": [
"Matt Reetz <[email protected]>"
],
"homepage": "https://github.com/helium/helium-js#readme",
"license": "Apache-2.0",
"main": "build/index.js",
"module": "build/index.es.js",
"files": [
"build"
],
"publishConfig": {
"access": "public"
},
"scripts": {
"test": "echo \"Error: run tests from root\" && exit 1",
"clean": "rimraf build",
"build": "yarn run clean && tsc"
},
"dependencies": {
"@helium/address": "^4.3.1",
"@helium/transactions": "^4.3.1",
"date-fns": "^2.28.0",
"query-string": "^7.1.1"
},
"peerDependencies": {
"@helium/crypto": "^4.3.1"
},
"devDependencies": {
"@helium/crypto": "^4.3.1"
}
}
156 changes: 156 additions & 0 deletions packages/wallet-link/src/__tests__/WalletLink.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { Keypair } from '@helium/crypto'
import { getUnixTime } from 'date-fns'
import {
createLinkWalletCallbackUrl,
createSignHotspotCallbackUrl,
createUpdateHotspotUrl,
createWalletLinkUrl,
LinkWalletResponse,
makeAppLinkAuthToken,
parseWalletLinkToken,
SignHotspotRequest,
SignHotspotResponse,
verifyWalletLinkToken,
} from '../index'

const createToken = async ({
time,
signingAppId,
} : { time?: number, signingAppId?: string } = {}) => {
const keypair = await Keypair.makeRandom()
const opts = {
address: keypair.address.b58,
appName: 'tacos',
callbackUrl: 'myscheme://',
requestAppId: 'com.tacos',
signingAppId: signingAppId || 'com.burrito',
time: time || new Date().getTime(),
}
const token = await makeAppLinkAuthToken(opts, keypair)
return { token: parseWalletLinkToken(token), opts, tokenString: token }
}

describe('wallet-link', () => {
describe('create link', () => {
it('successfully creates and parses', async () => {
const { token, opts } = await createToken()
expect(token.address).toBe(opts.address)
expect(token.signature).toBeDefined()
expect(token.appName).toBe(opts.appName)
expect(token.callbackUrl).toBe(opts.callbackUrl)
expect(token.requestAppId).toBe(opts.requestAppId)
expect(token.signingAppId).toBe(opts.signingAppId)
expect(token.time).toBe(opts.time)
})
})

describe('verify link', () => {
it('validates token without opts', async () => {
const { token } = await createToken()
const verified = await verifyWalletLinkToken(token)
expect(verified).toBe(true)
})

it('validates token with max age', async () => {
const { token } = await createToken()
const verified = await verifyWalletLinkToken(token, { maxAgeInSeconds: 60 })
expect(verified).toBe(true)
})

it('invalidates expired token', async () => {
const { token } = await createToken({ time: getUnixTime(new Date()) - 60 })
expect(() => verifyWalletLinkToken(token, { maxAgeInSeconds: 30 })).toThrow('Token is expired')
})
})

describe('create urls', () => {
it('creates wallet link url', async () => {
const params = {
requestAppId: 'testRequestAppId',
callbackUrl: 'testCallbackUrl',
appName: 'testAppName',
universalLink: 'testUniversalLink',
path: 'testPath',
}
const callbackUrl = createWalletLinkUrl(params)
expect(callbackUrl).toBe('testUniversalLinktestPath?appName=testAppName&callbackUrl=testCallbackUrl'
+ '&requestAppId=testRequestAppId')
})

it('creates callback url', async () => {
const params: LinkWalletResponse = {
status: 'success',
token: 'testToken',
}
const callbackUrl = createLinkWalletCallbackUrl('test://', 'testAddress', params)
expect(callbackUrl).toBe('test://link_wallet/testAddress?status=success&token=testToken')
})

it('creates sign hotspot callback url', async () => {
const params: SignHotspotResponse = {
status: 'success',
assertTxn: 'testAssertTxn',
gatewayTxn: 'testGatewayTxn',
transferTxn: 'testTransferTxn',
gatewayAddress: 'testGatewayAddress',
}
const callbackUrl = createSignHotspotCallbackUrl('test://', params)
expect(callbackUrl).toBe('test://sign_hotspot?assertTxn=testAssertTxn&gatewayAddress=testGatewayAddress'
+ '&gatewayTxn=testGatewayTxn&status=success&transferTxn=testTransferTxn')
})

it('creates ios update hotspot url', async () => {
const { tokenString } = await createToken({ signingAppId: 'com.helium.wallet.app' })
const params: SignHotspotRequest = {
token: tokenString,
addGatewayTxn: 'testAddGatewayTxn',
assertLocationTxn: 'testAssertLocationTxn',
transferHotspotTxn: 'testTransferHotspotTxn',
platform: 'ios',
}
const callbackUrl = createUpdateHotspotUrl(params)
expect(callbackUrl).toBe('https://wallet.helium.com/sign_hotspot?addGatewayTxn=testAddGatewayTxn'
+ `&assertLocationTxn=testAssertLocationTxn&platform=ios&token=${encodeURIComponent(tokenString)}`
+ '&transferHotspotTxn=testTransferHotspotTxn')
})

it('creates android update hotspot url', async () => {
const { tokenString } = await createToken({ signingAppId: 'com.helium.wallet.app' })
const params: SignHotspotRequest = {
token: tokenString,
addGatewayTxn: 'testAddGatewayTxn',
assertLocationTxn: 'testAssertLocationTxn',
transferHotspotTxn: 'testTransferHotspotTxn',
platform: 'android',
}
const callbackUrl = createUpdateHotspotUrl(params)
expect(callbackUrl).toBe('https://wallet.helium.com/sign_hotspot?addGatewayTxn=testAddGatewayTxn'
+ `&assertLocationTxn=testAssertLocationTxn&platform=android&token=${encodeURIComponent(tokenString)}`
+ '&transferHotspotTxn=testTransferHotspotTxn')
})

it('fails for invalid platform', async () => {
const { tokenString } = await createToken({ signingAppId: 'com.helium.wallet.app' })
const params: SignHotspotRequest = {
token: tokenString,
addGatewayTxn: 'testAddGatewayTxn',
assertLocationTxn: 'testAssertLocationTxn',
transferHotspotTxn: 'testTransferHotspotTxn',
platform: 'test',
}
expect(() => createUpdateHotspotUrl(params)).toThrow("Platform 'test' is not supported")
})

it('fails for invalid signing app id', async () => {
const { tokenString } = await createToken()
const params: SignHotspotRequest = {
token: tokenString,
addGatewayTxn: 'testAddGatewayTxn',
assertLocationTxn: 'testAssertLocationTxn',
transferHotspotTxn: 'testTransferHotspotTxn',
platform: 'android',
}
expect(() => createUpdateHotspotUrl(params)).toThrow('Could not find delegate app')
})
})
})
8 changes: 8 additions & 0 deletions packages/wallet-link/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* [[include:wallet-link/README.md]]
* @packageDocumentation
* @module wallet-link
*/

export * from './walletLink'
export * from './types'
Loading

0 comments on commit bf4e89a

Please sign in to comment.