Skip to content

Commit

Permalink
add SEP6 deposit and withdrawal (#77)
Browse files Browse the repository at this point in the history
* add sep6 deposit and withdrawal

* comment

* make better response

* remove wrapper type
  • Loading branch information
acharb authored Nov 8, 2023
1 parent 016314d commit f56265a
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 9 deletions.
89 changes: 83 additions & 6 deletions src/walletSdk/Anchor/Sep6.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { AxiosInstance } from "axios";
import queryString from "query-string";

import { Anchor } from "../Anchor";
import { ServerRequestFailedError } from "../Exceptions";
import { Sep6Info } from "../Types";

type Sep6Params = {
anchor: Anchor;
httpClient: AxiosInstance;
};
import {
Sep6Info,
Sep6Params,
Sep6DepositParams,
Sep6WithdrawParams,
Sep6DepositResponse,
Sep6WithdrawResponse,
} from "../Types";

/**
* Flow for creating deposits and withdrawals with an anchor using SEP-6.
Expand Down Expand Up @@ -53,4 +56,78 @@ export class Sep6 {
throw new ServerRequestFailedError(e);
}
}

/**
* Deposits funds using the SEP-6 protocol. Next steps by
* the anchor are given in the response.
*
* @param {object} options - The options for the deposit.
* @param {string} options.authToken - The authentication token.
* @param {Sep6DepositParams} options.params - The parameters for the deposit request.
*
* @returns {Promise<Sep6DepositResponse>} Sep6 deposit response, containing next steps if needed
* to complete the deposit.
*
* @throws {Error} If an unexpected error occurs during the deposit operation.
*/
async deposit({
authToken,
params,
}: {
authToken: string;
params: Sep6DepositParams;
}): Promise<Sep6DepositResponse> {
return this.flow({ type: "deposit", authToken, params });
}

/**
* Initiates a withdrawal using SEP-6.
*
* @param {object} options - The options for the withdrawal operation.
* @param {string} options.authToken - The authentication token.
* @param {Sep6WithdrawParams} options.params - The parameters for the withdrawal request.
*
* @returns {Promise<Sep6WithdrawResponse>} Sep6 withdraw response.
*/
async withdraw({
authToken,
params,
}: {
authToken: string;
params: Sep6WithdrawParams;
}): Promise<Sep6WithdrawResponse> {
return this.flow({ type: "withdraw", authToken, params });
}

private async flow({
type,
authToken,
params,
}: {
type: "deposit" | "withdraw";
authToken: string;
params: Sep6DepositParams | Sep6WithdrawParams;
}) {
const { transferServer } = await this.anchor.sep1();

try {
const resp = await this.httpClient.get(
`${transferServer}/${type}?${queryString.stringify(params)}`,
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
},
},
);
return resp.data;
} catch (e) {
if (e.response?.data?.type === "non_interactive_customer_info_needed") {
return e.response?.data;
} else if (e.response?.data?.type === "customer_info_status") {
return e.response?.data;
}
throw e;
}
}
}
91 changes: 91 additions & 0 deletions src/walletSdk/Types/sep6.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { AxiosInstance } from "axios";
import { Anchor } from "../Anchor";

export interface Sep6EndpointInfo {
enabled: boolean;
authentication_required?: boolean;
Expand Down Expand Up @@ -56,3 +59,91 @@ export interface Sep6Info {
claimable_balances: boolean;
};
}

export type Sep6Params = {
anchor: Anchor;
httpClient: AxiosInstance;
};

export interface Sep6DepositParams {
asset_code: string;
account: string;
memo_type?: string;
memo?: string;
email_address?: string;
type?: string;
lang?: string;
on_change_callback?: string;
amount?: string;
country_code?: string;
claimable_balance_supported?: string;
customer_id?: string;
}

export interface Sep6WithdrawParams {
asset_code: string;
type: string;
dest?: string;
dest_extra?: string;
account?: string;
memo?: string;
lang?: string;
on_change_callback?: string;
amount?: string;
country_code?: string;
refund_memo?: string;
refund_memo_type?: string;
customer_id?: string;
}

export type Sep6DepositResponse =
| Sep6DepositSuccess
| Sep6MissingKYC
| Sep6Pending;

export interface Sep6DepositSuccess {
how?: string;
instructions?: {
[key: string]: {
value: string;
description: string;
};
};
id?: string;
eta?: number;
min_amoun?: number;
max_amount?: number;
fee_fixed?: number;
fee_percent?: number;
extra_info?: { message?: string };
}

export interface Sep6MissingKYC {
type: string;
fields: Array<string>;
}

export interface Sep6Pending {
type: string;
status: string;
more_info_url?: string;
eta?: number;
}

export type Sep6WithdrawResponse =
| Sep6WithdrawSuccess
| Sep6MissingKYC
| Sep6Pending;

export interface Sep6WithdrawSuccess {
account_id?: string;
memo_type?: string;
memo?: string;
id?: string;
eta?: number;
min_amount?: number;
max_amount?: number;
fee_fixed?: number;
fee_percent?: number;
extra_info?: { message?: string };
}
86 changes: 83 additions & 3 deletions test/sep6.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { Wallet } from "../src";
import axios from "axios";

let wallet;
let anchor;
let sep6;
let accountKp;

describe("SEP-6", () => {
beforeAll(async () => {
wallet = Wallet.TestNet();
anchor = wallet.anchor({ homeDomain: "testanchor.stellar.org" });
sep6 = anchor.sep6();

accountKp = wallet.stellar().account().createKeypair();
await axios.get(
"https://friendbot.stellar.org/?addr=" + accountKp.publicKey,
);
}, 10000);
it("should get anchor info", async () => {
const wallet = Wallet.TestNet();
const anchor = wallet.anchor({ homeDomain: "testanchor.stellar.org" });
const sep6 = anchor.sep6();
const resp = await sep6.info();
expect(resp.deposit).toBeTruthy();
expect(resp.withdraw).toBeTruthy();
Expand All @@ -13,4 +26,71 @@ describe("SEP-6", () => {
expect(refreshed.deposit).toBeTruthy();
expect(refreshed.withdraw).toBeTruthy();
});
it("should deposit", async () => {
const auth = await anchor.sep10();
const authToken = await auth.authenticate({ accountKp });

const sep12 = await anchor.sep12(authToken);

// Make first call with missing KYC info
let resp = await sep6.deposit({
authToken,
params: {
asset_code: "SRT",
account: accountKp.publicKey,
type: "bank_account",
},
});
expect(resp.type).toBe("non_interactive_customer_info_needed");

// Add the missing KYC info
await sep12.add({
sep9Info: {
first_name: "john",
last_name: "smith",
email_address: "[email protected]",
bank_number: "12345",
bank_account_number: "12345",
},
});

// Make deposit call again with all info uploaded
resp = await sep6.deposit({
authToken,
params: {
asset_code: "SRT",
account: accountKp.publicKey,
type: "bank_account",
},
});
expect(resp.id).toBeTruthy();
});
it("should withdraw", async () => {
const auth = await anchor.sep10();
const authToken = await auth.authenticate({ accountKp });

const sep12 = await anchor.sep12(authToken);

await sep12.add({
sep9Info: {
first_name: "john",
last_name: "smith",
email_address: "[email protected]",
bank_number: "12345",
bank_account_number: "12345",
},
});

const resp = await sep6.withdraw({
authToken,
params: {
asset_code: "SRT",
account: accountKp.publicKey,
type: "bank_account",
dest: "123",
dest_extra: "12345",
},
});
expect(resp.id).toBeTruthy();
});
});

0 comments on commit f56265a

Please sign in to comment.