Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First version CoinosProvider interface #1

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
name: Build, lint, and test on Node ${{ matrix.node }}

runs-on: ubuntu-latest
strategy:
matrix:
node: ['12.x', '14.x', '16.x']

steps:
- name: Checkout repo
uses: actions/checkout@v2

- name: Use Node ${{ matrix.node }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}

- name: Install deps and build
run: yarn install --frozen-lockfile

- name: Lint
run: yarn lint

- name: Build
run: yarn build
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "marina-provider",
"version": "1.4.0",
"description": "Marina injected API",
"repository": "[email protected]:vulpemventures/marina-provider.git",
"name": "coinos-marina-provider",
"version": "1.4.2",
"description": "Marina injected API for Coinos security assets",
"repository": "[email protected]:coinos/marina-provider.git",
"author": "Vulpem Ventures",
"license": "MIT",
"main": "./dist/index.js",
Expand Down
158 changes: 3 additions & 155 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,155 +1,3 @@
export interface AddressInterface {
confidentialAddress: string;
blindingPrivateKey: string;
derivationPath?: string;
}

export interface SignedMessage {
signature: SignatureBase64;
address: NativeSegwitAddress;
}

export enum TxStatusEnum {
Confirmed = 1,
Pending = 0,
}

export interface Transaction {
txId: string;
status: TxStatusEnum;
fee: number;
transfers: Array<{ asset: string; amount: number }>;
explorerURL: string;
blocktimeMs: number;
}

export interface Utxo {
txid: string;
vout: number;
asset?: string;
value?: number;
}

export interface Balance {
asset: {
assetHash: string;
ticker?: string;
name?: string;
precision: number;
};
amount: number;
}

export interface Recipient {
address: string; // the recipient address
value: number; // the amount of sats to send
asset: string; // the asset to send
}

export type MarinaEventType =
| 'NEW_UTXO'
| 'NEW_TX'
| 'SPENT_UTXO'
| 'ENABLED'
| 'DISABLED'
| 'NETWORK';

export type TransactionHex = string;
export type PsetBase64 = string;
export type SignatureBase64 = string;
export type NativeSegwitAddress = string;
export type EventListenerID = string;

export interface MarinaProvider {
enable(): Promise<void>;

disable(): Promise<void>;

isEnabled(): Promise<boolean>;

setAccount(account: number): Promise<void>;

getNetwork(): Promise<'liquid' | 'regtest'>;

getAddresses(): Promise<AddressInterface[]>;

getNextAddress(): Promise<AddressInterface>;

getNextChangeAddress(): Promise<AddressInterface>;

sendTransaction(
recipients: Recipient[],
feeAsset?: string
): Promise<TransactionHex>;

blindTransaction(pset: PsetBase64): Promise<PsetBase64>;

signTransaction(pset: PsetBase64): Promise<PsetBase64>;

signMessage(message: string): Promise<SignedMessage>;

getCoins(): Promise<Utxo[]>;

getTransactions(): Promise<Transaction[]>;

getBalances(): Promise<Balance[]>;

on(type: MarinaEventType, callback: (payload: any) => void): EventListenerID;

off(listenerId: EventListenerID): void;

getFeeAssets(): Promise<string[]>;
}

/**
* listen with timeout the `providerName#initialized` event
* @param provider the name of the provider to detect "marina" for window.marina
* @param timeout configurable timeout, default is 3000 (expressed in milliseconds)
*/
export async function detectProvider<T = MarinaProvider>(
provider: string = 'marina',
timeout: number = 3000
): Promise<T> {
let handled = false;
let windowObject = window as any;

return new Promise<T>((resolve, reject) => {
if (windowObject[provider]) {
handleProvider();
} else {
window.addEventListener(`${provider}#initialized`, handleProvider, {
once: true,
});

setTimeout(() => {
handleProvider();
}, timeout);
}

function handleProvider() {
if (handled) return;
handled = true;

window.removeEventListener(`${provider}#initialized`, handleProvider);
if (typeof windowObject[provider] !== 'undefined') {
resolve(windowObject[provider]);
return;
}

reject(new DetectProviderTimeoutError(provider, timeout));
}
});
}

export class DetectProviderTimeoutError extends Error {
provider: string;
timeout: number;

constructor(provider: string, timeout: number) {
super(
`detectProviderTimeout: detection of ${provider} timeout (${timeout} ms)`
);
this.provider = provider;
this.timeout = timeout;
}
}
export * from './utils';
export * from './types';
export * from './provider';
40 changes: 40 additions & 0 deletions src/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
AddressInterface,
AssetValue,
Balance,
PsetBase64,
Recipient,
Transaction,
TransactionHex,
Utxo,
WalletInfo,
} from './types';

/**
* Define the Coinos provider methods.
* Provided by marina extension at window.coinos
*/
export interface CoinosProvider {
getNetwork(): Promise<'liquid' | 'regtest' | 'testnet'>;

sendTransaction(
recipients: Recipient[],
feeAsset?: string
): Promise<TransactionHex>;

signTransaction(pset: PsetBase64): Promise<PsetBase64>;

getCoins(): Promise<Utxo[]>;

getTransactions(): Promise<Transaction[]>;

getBalances(): Promise<Balance[]>;

getNextAddress(): Promise<AddressInterface>;

getNextChangeAddress(): Promise<AddressInterface>;

approveSpend(toApprove: AssetValue[]): Promise<PsetBase64>;

getWalletInfo(): Promise<WalletInfo>;
}
63 changes: 63 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
export interface AddressInterface {
confidentialAddress: string;
blindingPrivateKey: string;
derivationPath?: string;
}

export enum TxStatusEnum {
Confirmed = 1,
Pending = 0,
}

export interface Transaction {
txId: string;
status: TxStatusEnum;
fee: number;
transfers: Array<{ asset: string; amount: number }>;
explorerURL: string;
blocktimeMs: number;
}

export interface Utxo {
txid: string;
vout: number;
asset?: string;
value?: number;
}

export interface Balance {
asset: {
assetHash: string;
ticker?: string;
name?: string;
precision: number;
};
amount: number;
}

// add an OP_RETURN output
export type DataRecipient = {
data: string;
} & AssetValue;

export type AddressRecipient = {
address: string; // the recipient address
} & AssetValue;

export interface AssetValue {
value: number; // the amount of sats to send
asset: string; // the asset to send
}

export interface WalletInfo {
marinaXPub: string;
coinosXPub: string;
}

export type Recipient = AddressRecipient | DataRecipient;

export type TransactionHex = string;
export type PsetBase64 = string;
export type SignatureBase64 = string;
export type NativeSegwitAddress = string;
export type ECPublicKey = string;
69 changes: 69 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { CoinosProvider } from './provider';
import { DataRecipient, AddressRecipient, Recipient } from './types';

/**
* listen with timeout the `providerName#initialized` event
* @param provider the name of the provider to detect "marina" for window.marina
* @param timeout configurable timeout, default is 3000 (expressed in milliseconds)
*/
export async function detectProvider<T = CoinosProvider>(
provider: string = 'coinos',
timeout: number = 3000
): Promise<T> {
let handled = false;
let windowObject = window as any;

return new Promise<T>((resolve, reject) => {
if (windowObject[provider]) {
handleProvider();
} else {
window.addEventListener(`${provider}#initialized`, handleProvider, {
once: true,
});

setTimeout(() => {
handleProvider();
}, timeout);
}

function handleProvider() {
if (handled) return;
handled = true;

window.removeEventListener(`${provider}#initialized`, handleProvider);
if (typeof windowObject[provider] !== 'undefined') {
resolve(windowObject[provider]);
return;
}

reject(new DetectProviderTimeoutError(provider, timeout));
}
});
}

export class DetectProviderTimeoutError extends Error {
provider: string;
timeout: number;

constructor(provider: string, timeout: number) {
super(
`detectProviderTimeout: detection of ${provider} timeout (${timeout} ms)`
);
this.provider = provider;
this.timeout = timeout;
}
}

export function isDataRecipient(
recipient: Recipient
): recipient is DataRecipient {
const data = (recipient as DataRecipient).data;
return data !== undefined && typeof data === 'string';
}

export function isAddressRecipient(
recipient: Recipient
): recipient is AddressRecipient {
const address = (recipient as AddressRecipient).address;
return address !== undefined && typeof address === 'string';
}