Skip to content

Commit

Permalink
chore: init adding version param
Browse files Browse the repository at this point in the history
  • Loading branch information
einaralex committed Aug 7, 2024
1 parent 2540e69 commit 25fba47
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 35 deletions.
75 changes: 61 additions & 14 deletions packages/sdk/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export class MoneriumClient {
#env: Environment;

#authorizationHeader?: string;

#version?: string;
/**
* The PKCE code verifier
* @deprecated, use localStorage, will be removed in v3
Expand Down Expand Up @@ -91,6 +93,7 @@ export class MoneriumClient {
// No arguments, default to sandbox
if (!envOrOptions) {
this.#env = MONERIUM_CONFIG.environments['sandbox'];
this.#version = 'v2';
return;
}
// String argument
Expand All @@ -99,6 +102,7 @@ export class MoneriumClient {
} else {
this.#env =
MONERIUM_CONFIG.environments[envOrOptions.environment || 'sandbox'];
this.#version = envOrOptions?.version || 'v2';

if (!isServer) {
const { clientId, redirectUri } =
Expand Down Expand Up @@ -348,15 +352,42 @@ export class MoneriumClient {
* {@link https://monerium.dev/api-docs#operation/profile-addresses}
* @category Accounts
*/
linkAddress(profileId: string, body: LinkAddress): Promise<LinkedAddress> {
body = mapChainIdToChain(body);
body.accounts = body.accounts.map((account) => mapChainIdToChain(account));
linkAddress(
body: Omit<LinkAddress, 'accounts'> & { profile: string }
): Promise<LinkedAddress>;
/** @deprecated this function should only take one parameter, body with profile id included */
linkAddress(profileId: string, body: LinkAddress): Promise<LinkedAddress>;

linkAddress(
profileIdOrBody:
| string
| (Omit<LinkAddress, 'accounts'> & { profile: string }),
/** @deprecated this function should only take one parameter, body with profile id included */
body?: LinkAddress
): Promise<LinkedAddress> {
if (typeof profileIdOrBody === 'string' && body && this.#version === 'v1') {
body = mapChainIdToChain(body);
if (body?.accounts) {
body.accounts = body?.accounts.map((account) =>
mapChainIdToChain(account)
);
}

return this.#api(
'post',
`profiles/${profileId}/addresses`,
JSON.stringify(body)
);
return this.#api<LinkedAddress>(
'post',
`profiles/${profileIdOrBody}/addresses`,
JSON.stringify(body)
);
} else if (typeof profileIdOrBody === 'object' && this.#version === 'v2') {
profileIdOrBody = mapChainIdToChain(profileIdOrBody);
return this.#api<LinkedAddress>(
'post',
`addresses`,
JSON.stringify(profileIdOrBody)
);
} else {
throw new Error('Invalid arguments');
}
}

/**
Expand Down Expand Up @@ -397,16 +428,32 @@ export class MoneriumClient {
body?: BodyInit | Record<string, string>,
isFormEncoded?: boolean
): Promise<T> {
const headers: Record<string, string> = {
Authorization: this.#authorizationHeader || '',
'Content-Type': `application/${
isFormEncoded ? 'x-www-form-urlencoded' : 'json'
}`,
};
if (this.#version === 'v2') {
headers['Accept'] = 'application/vnd.monerium.api-v2+json';
}
console.log(
'%c headers',
'color:white; padding: 30px; background-color: darkgreen',
headers
);

console.log(
'%c ${this.#env.api}/${resource}',
'color:white; padding: 30px; background-color: darkgreen',
`${this.#env.api}/${resource}`
);

return rest<T>(
`${this.#env.api}/${resource}`,
method,
isFormEncoded ? urlEncoded(body as Record<string, string>) : body,
{
Authorization: this.#authorizationHeader || '',
'Content-Type': `application/${
isFormEncoded ? 'x-www-form-urlencoded' : 'json'
}`,
}
headers
);
}

Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ export type MoneriumEventListener = (notification: OrderNotification) => void;

export type ClassOptions = {
environment?: ENV;
version?: 'v1' | 'v2';
} & BearerTokenCredentials;

export interface AuthFlowOptions {
Expand Down
232 changes: 232 additions & 0 deletions packages/sdk/test/client-server-v2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/**
* @jest-environment node
*/

// Login: [email protected]
// Password: Passw0rd!

// punkWallet: https://punkwallet.io/pk#0x30fa9f64fb85dab6b4bf045443e08315d6570d4eabce7c1363acda96042a6e1a

import 'jest-localstorage-mock';

import constants from '../src/constants';
import { MoneriumClient } from '../src/index';
import {
Currency,
CurrencyAccounts,
Individual,
Order,
PaymentStandard,
} from '../src/types';
import { rfc3339 } from '../src/utils';
import {
APP_ONE_CREDENTIALS_CLIENT_ID,
APP_ONE_CREDENTIALS_SECRET,
APP_ONE_OWNER_USER_ID,
DEFAULT_PROFILE,
OWNER_SIGNATURE,
PUBLIC_KEY,
} from './constants.js';

const { LINK_MESSAGE } = constants;

const message = LINK_MESSAGE;

let client: MoneriumClient;

// Can't run in CI because of Cloudflare
process.env.CI !== 'true' &&
beforeAll(async () => {
client = new MoneriumClient({
clientId: APP_ONE_CREDENTIALS_CLIENT_ID,
clientSecret: APP_ONE_CREDENTIALS_SECRET,
// version: 'v2',
});
try {
await client.getAccess();
} catch (error) {
console.error('Error, could not authenticate');
}
});

process.env.CI !== 'true' &&
describe('MoneriumClient', () => {
test('authenticate with client credentials', async () => {
const authContext = await client.getAuthContext();

expect(authContext.userId).toBe(APP_ONE_OWNER_USER_ID);
});
// TODO:
// something off with this endpoint
test.skip('link address', async () => {
const authContext = await client.getAuthContext();

const res = await client.linkAddress({
profile: authContext.defaultProfile as string,
address: PUBLIC_KEY,
message: message,
chain: 11155111,
signature: OWNER_SIGNATURE,
});

expect(res).toMatchObject({
address: '0xBd78A5C7efBf7f84C75ef638689A006512E1A6c4',
id: 'ebec4eed-6dcb-11ee-8aa6-5273f65ed05b',
message: 'I hereby declare that I am the address owner.',
meta: {
linkedBy: '9fdfd981-6dca-11ee-8aa6-5273f65ed05b',
},
profile: '9fdfd8f1-6dca-11ee-8aa6-5273f65ed05b',
});
});

test('get profile', async () => {
const authContext = await client.getAuthContext();
const profile = await client.getProfile(
authContext.defaultProfile as string
);

expect(profile.id).toBe(authContext.defaultProfile);
});

test('get balances', async () => {
const balances = await client.getBalances();

expect(balances).toEqual(
expect.arrayContaining([
expect.objectContaining({
// id: '4b208818-44e3-11ed-adac-b2efc0e6677d',
chain: 'ethereum',
address: PUBLIC_KEY,
}),
])
);
}, 15000);

test('get orders', async () => {
const orders = await client.getOrders();
const order = orders.find((o: Order) => o.memo === 'UNIT-TEST') as Order;

expect(order.kind).toBe('redeem');
expect((order.counterpart.details as Individual).firstName).toBe('Test');
expect((order.counterpart.details as Individual).lastName).toBe(
'Testerson'
);
expect(order.amount).toBe('1.33');
expect(order.memo).toBe('UNIT-TEST');
});

test('get orders by profileId', async () => {
const orders = await client.getOrders({
profile: DEFAULT_PROFILE,
});

orders.map((o: Order) => {
expect(DEFAULT_PROFILE).toBe(o.profile);
});
});

test('get order', async () => {
const order = await client.getOrder(
'7859502a-4b5d-11ef-88d4-46da5b198e23'
);

expect(order.kind).toBe('redeem');
expect(order.amount).toBe('1.33');
expect(order.memo).toBe('UNIT-TEST');
});

test('get tokens', async () => {
const tokens = await client.getTokens();

const expected = [
{
address: '0x67b34b93ac295c985e856E5B8A20D83026b580Eb',
chain: 'ethereum',
currency: 'eur',
decimals: 18,
symbol: 'EURe',
ticker: 'EUR',
},
];
expect(tokens).toEqual(expect.arrayContaining(expected));
});

// there is no way to test this without a real time signature, the date is now verified
test('place order signature error', async () => {
const date = new Date().toISOString();
const rfc3339date = rfc3339(new Date(date));

const placeOrderMessage = `Send EUR 10 to GR1601101250000000012300695 at ${rfc3339date}`;
const placeOrderSignatureHash =
'0x23bf7e1b240d238b13cb293673c3419915402bb34435af62850b1d8e63f82c564fb73ab19691cf248594423dd01e441bb2ccb38ce2e2ecc514dfc3075bea829e1c';

await client
.placeOrder({
amount: '10',
signature: placeOrderSignatureHash,
currency: Currency.eur,
address: PUBLIC_KEY,
counterpart: {
identifier: {
standard: PaymentStandard.iban,
iban: 'GR1601101250000000012300695',
},
details: {
firstName: 'Mockbank',
lastName: 'Testerson',
},
},
message: placeOrderMessage,
memo: 'Powered by Monerium SDK',
chain: 11155111,
})
.catch((err) => {
expect(err.errors?.signature).toBe('invalid signature');
});
});

test('place order timestamp error', async () => {
const date = 'Thu, 29 Dec 2022 14:58 +00:00';
const placeOrderMessage = `Send EUR 10 to GR1601101250000000012300695 at ${date}`;
const placeOrderSignatureHash =
'0x23bf7e1b240d238b13cb293673c3419915402bb34435af62850b1d8e63f82c564fb73ab19691cf248594423dd01e441bb2ccb38ce2e2ecc514dfc3075bea829e1c';

await client
.placeOrder(
{
amount: '10',
signature: placeOrderSignatureHash,
currency: Currency.eur,
address: PUBLIC_KEY,
counterpart: {
identifier: {
standard: PaymentStandard.iban,
iban: 'GR1601101250000000012300695',
},
details: {
firstName: 'Mockbank',
lastName: 'Testerson',
},
},
message: placeOrderMessage,
memo: 'Powered by Monerium SDK',
chain: 'ethereum',
} as any /** to bypass typeerror for chain and network */
)
.catch((err) => {
expect(err.errors?.message).toBe('timestamp is expired');
});
});
});
// TODO:
// test("upload supporting document", async () => {

// // const document = client.uploadSupportingDocument();
// // assertObjectMatch(document, {});
// });

process.env.CI === 'true' &&
it('SKIPPED', () => {
expect(true).toBe(true);
});
1 change: 1 addition & 0 deletions packages/sdk/test/client-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ process.env.CI !== 'true' &&
client = new MoneriumClient({
clientId: APP_ONE_CREDENTIALS_CLIENT_ID,
clientSecret: APP_ONE_CREDENTIALS_SECRET,
version: 'v1',
});
try {
await client.getAccess();
Expand Down
Loading

0 comments on commit 25fba47

Please sign in to comment.