@t-0/provider-starter-ts 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +204 -0
- package/bin/cli.js +5 -0
- package/dist/create.d.ts +3 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +121 -0
- package/dist/create.js.map +1 -0
- package/package.json +43 -0
- package/template/.dockerignore +43 -0
- package/template/.env +11 -0
- package/template/.env.example +14 -0
- package/template/.eslintrc.json +20 -0
- package/template/Dockerfile +56 -0
- package/template/package-lock.json +2071 -0
- package/template/package.json +30 -0
- package/template/src/get_quote.ts +29 -0
- package/template/src/index.ts +67 -0
- package/template/src/lib.ts +13 -0
- package/template/src/publish_quotes.ts +34 -0
- package/template/src/service.ts +50 -0
- package/template/src/submit_payment.ts +24 -0
- package/template/tsconfig.json +20 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Node.js service built with your SDK",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "ts-node src/index.ts",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"lint": "eslint src --ext .ts",
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@t-0/provider-sdk": "1.0.33",
|
|
18
|
+
"dotenv": "^16.3.1",
|
|
19
|
+
"tiny-invariant": "^1.3.3"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.10.0",
|
|
23
|
+
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
|
24
|
+
"@typescript-eslint/parser": "^6.12.0",
|
|
25
|
+
"eslint": "^8.54.0",
|
|
26
|
+
|
|
27
|
+
"ts-node": "^10.9.1",
|
|
28
|
+
"typescript": "^5.3.2"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {type Client, NetworkService, PaymentMethodType, QuoteType} from "@t-0/provider-sdk";
|
|
2
|
+
import {fromProtoDecimal, toProtoDecimal} from "./lib";
|
|
3
|
+
|
|
4
|
+
export default async function getQuote(networkClient: Client<typeof NetworkService>) {
|
|
5
|
+
const eurgbp = await networkClient.getQuote({
|
|
6
|
+
payInCurrency: "EUR",
|
|
7
|
+
payInMethod: PaymentMethodType.SEPA,
|
|
8
|
+
payOutCurrency: "GBP",
|
|
9
|
+
payOutMethod: PaymentMethodType.SWIFT,
|
|
10
|
+
quoteType: QuoteType.REALTIME,
|
|
11
|
+
amount: {
|
|
12
|
+
amount: {
|
|
13
|
+
case: 'payInAmount',
|
|
14
|
+
value: toProtoDecimal(1000, 0)
|
|
15
|
+
},
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
switch (eurgbp.result.case) {
|
|
20
|
+
case 'success':
|
|
21
|
+
console.log(`EUR/GBP: ${fromProtoDecimal(eurgbp.result.value.rate!)}`)
|
|
22
|
+
break;
|
|
23
|
+
case 'failure':
|
|
24
|
+
console.error(eurgbp.result.value.reason)
|
|
25
|
+
break;
|
|
26
|
+
default:
|
|
27
|
+
console.error("unexpected result type")
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
import {
|
|
3
|
+
createClient,
|
|
4
|
+
createService,
|
|
5
|
+
NetworkService,
|
|
6
|
+
nodeAdapter,
|
|
7
|
+
ProviderService,
|
|
8
|
+
signatureValidation,
|
|
9
|
+
} from "@t-0/provider-sdk";
|
|
10
|
+
import invariant from 'tiny-invariant';
|
|
11
|
+
import http from "http";
|
|
12
|
+
import publishQuotes from "./publish_quotes";
|
|
13
|
+
import CreateProviderService from "./service";
|
|
14
|
+
import getQuote from "./get_quote";
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
16
|
+
import submitPayment from "./submit_payment";
|
|
17
|
+
|
|
18
|
+
dotenv.config();
|
|
19
|
+
|
|
20
|
+
const privateKeyHex = process.env.PROVIDER_PRIVATE_KEY;
|
|
21
|
+
const port = process.env.PORT || 3000;
|
|
22
|
+
const endpoint = process.env.TZERO_ENDPOINT || "https://api-sandbox.t-0.network";
|
|
23
|
+
const quotePublishingInterval: number = Number(process.env.QUOTE_PUBLISHING_INTERVAL || "5000");
|
|
24
|
+
const networkPublicKeyHex = process.env.NETWORK_PUBLIC_KEY;
|
|
25
|
+
|
|
26
|
+
invariant(privateKeyHex, 'Private key not set');
|
|
27
|
+
invariant(quotePublishingInterval > 0, 'Interval must be positive');
|
|
28
|
+
invariant(networkPublicKeyHex, 'Network public key is not set');
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
console.log('🚀 Service starting...');
|
|
32
|
+
console.log(`📡 Port: ${port}`);
|
|
33
|
+
const networkClient = createClient(privateKeyHex!, endpoint, NetworkService);
|
|
34
|
+
|
|
35
|
+
await publishQuotes(networkClient, quotePublishingInterval)
|
|
36
|
+
|
|
37
|
+
const server = http.createServer(
|
|
38
|
+
signatureValidation(
|
|
39
|
+
nodeAdapter(
|
|
40
|
+
createService(networkPublicKeyHex!, (r) => {
|
|
41
|
+
r.service(ProviderService, CreateProviderService());
|
|
42
|
+
})))
|
|
43
|
+
).listen(port);
|
|
44
|
+
console.log("✅ Service ready and is listening at", server.address());
|
|
45
|
+
|
|
46
|
+
// Step 1.1 is done. You successfully initialised starter template
|
|
47
|
+
|
|
48
|
+
// TODO: Step 1.2 take you generated public key from .env and share it with t-0 team
|
|
49
|
+
|
|
50
|
+
// TODO: Step 1.3 implement publishing of quotes in the ./publish_quotes.ts
|
|
51
|
+
|
|
52
|
+
// TODO: Step 1.4 check that quote for target currency is successfully received
|
|
53
|
+
await getQuote(networkClient)
|
|
54
|
+
|
|
55
|
+
// TODO: Step 2.2 deploy your integration and provide t-0 team base URL of your deployment
|
|
56
|
+
|
|
57
|
+
// TODO: Step 2.3 check that you can submit payment by revisiting ./submit_payment.ts and uncommenting following line
|
|
58
|
+
// await submitPayment(networkClient)
|
|
59
|
+
|
|
60
|
+
// TODO: Step 2.5 ask t-0 team to submit a payment which would trigger your payOut endpoint
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
main().catch((error) => {
|
|
64
|
+
console.error('❌ Error starting service:', error);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
67
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {Decimal, DecimalSchema} from "@t-0/provider-sdk";
|
|
2
|
+
import {create} from "@bufbuild/protobuf";
|
|
3
|
+
|
|
4
|
+
export const toProtoDecimal = (unscaled: number, exponent: number): Decimal => {
|
|
5
|
+
return create(DecimalSchema, {
|
|
6
|
+
unscaled: BigInt(unscaled),
|
|
7
|
+
exponent: exponent,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const fromProtoDecimal = (value: Decimal): number => {
|
|
12
|
+
return Number(value.unscaled) * Math.pow(10, value.exponent)
|
|
13
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {type Client, NetworkService, PaymentMethodType, QuoteType} from "@t-0/provider-sdk";
|
|
2
|
+
import {toProtoDecimal} from "./lib";
|
|
3
|
+
import {randomUUID} from "node:crypto";
|
|
4
|
+
import {timestampFromDate} from "@bufbuild/protobuf/wkt";
|
|
5
|
+
|
|
6
|
+
export default async function publishQuotes(networkClient: Client<typeof NetworkService>, quotePublishingInterval: number): Promise<void> {
|
|
7
|
+
// TODO: Step 1.3 replace this with receiving quotes from you systems and publishing them into t-0 Network. We recommend publishing at least once per 5 seconds, but not more than once per second
|
|
8
|
+
const tick = async () => {
|
|
9
|
+
try {
|
|
10
|
+
await networkClient.updateQuote({
|
|
11
|
+
payIn: [{
|
|
12
|
+
bands: [{
|
|
13
|
+
// note that rate is always USD/XXX, os that for EUR quote should be USD/EUR
|
|
14
|
+
rate: toProtoDecimal(863, -3), // rate 0.863
|
|
15
|
+
maxAmount: toProtoDecimal(1000, 0), // maximum amount in USD, could be 1000,5000,10000 or 25000
|
|
16
|
+
clientQuoteId: randomUUID(),
|
|
17
|
+
}],
|
|
18
|
+
currency: 'EUR',
|
|
19
|
+
expiration: timestampFromDate(new Date(Date.now() + 30 * 1000)), // expiration time (30 seconds from now)
|
|
20
|
+
quoteType: QuoteType.REALTIME, // REALTIME is only one supported right now
|
|
21
|
+
paymentMethod: PaymentMethodType.SEPA,
|
|
22
|
+
timestamp: timestampFromDate(new Date()), // Current timestamp
|
|
23
|
+
}]
|
|
24
|
+
})
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(error);
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
console.log("quote published")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await tick()
|
|
33
|
+
setInterval(tick, quotePublishingInterval);
|
|
34
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PayoutRequest,
|
|
3
|
+
PayoutResponse,
|
|
4
|
+
UpdatePaymentRequest,
|
|
5
|
+
UpdateLimitRequest,
|
|
6
|
+
UpdateLimitResponse,
|
|
7
|
+
AppendLedgerEntriesRequest,
|
|
8
|
+
AppendLedgerEntriesResponse,
|
|
9
|
+
UpdatePaymentResponse,
|
|
10
|
+
HandlerContext,
|
|
11
|
+
} from "@t-0/provider-sdk";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
Please refer to docs, proto definition comments or source code comments to understand purpose of fields
|
|
15
|
+
*/
|
|
16
|
+
const CreateProviderService = () => {
|
|
17
|
+
return {
|
|
18
|
+
async updatePayment(req: UpdatePaymentRequest, _: HandlerContext) {
|
|
19
|
+
// TODO: Step 2.1 implement how you handle updates of payment initiated by you
|
|
20
|
+
console.log(`Received payment update for ${req.paymentId}, payment ${req.result.case}`)
|
|
21
|
+
return {} as UpdatePaymentResponse
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
async payOut(req: PayoutRequest, _: HandlerContext) {
|
|
25
|
+
// TODO: Step 2.4 implement how you do payouts (payments initiated by your counterparts)
|
|
26
|
+
console.log(`Received payout request ${req.payoutId}`)
|
|
27
|
+
return {
|
|
28
|
+
result: {
|
|
29
|
+
case: "accepted",
|
|
30
|
+
value: {},
|
|
31
|
+
},
|
|
32
|
+
} as PayoutResponse
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async updateLimit(req: UpdateLimitRequest, _: HandlerContext) {
|
|
36
|
+
// TODO: optionally implement updates on your limits and limits usage
|
|
37
|
+
console.log(`Received update of limits with provider ${req.limits[0].creditorId}`)
|
|
38
|
+
return {} as UpdateLimitResponse
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
async appendLedgerEntries(req: AppendLedgerEntriesRequest, _: HandlerContext) {
|
|
42
|
+
// TODO: optionally implement handling of new ledger transactions and new ledger entries
|
|
43
|
+
console.log(`Received ledger entries for ${req.transactions} transaction(s)`)
|
|
44
|
+
return {
|
|
45
|
+
} as AppendLedgerEntriesResponse
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default CreateProviderService;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {type Client, NetworkService, PaymentMethodType} from "@t-0/provider-sdk";
|
|
2
|
+
|
|
3
|
+
export default async function submitPayment(networkClient: Client<typeof NetworkService>): Promise<void> {
|
|
4
|
+
// TODO: Step 2.3 replace this with receiving quotes from you systems and publishing them into t-0 Network. We recommend publishing at least once per 5 seconds, but not more than once per second
|
|
5
|
+
await networkClient.createPayment({
|
|
6
|
+
payIn: {
|
|
7
|
+
currency: 'EUR',
|
|
8
|
+
paymentMethod: PaymentMethodType.SEPA,
|
|
9
|
+
},
|
|
10
|
+
payOut: {
|
|
11
|
+
currency: 'GBP',
|
|
12
|
+
paymentDetails: {
|
|
13
|
+
details: {
|
|
14
|
+
case: "sepa",
|
|
15
|
+
value: {
|
|
16
|
+
iban: 'GB12345567890',
|
|
17
|
+
beneficiaryName: 'Max Mustermann',
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
})
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true,
|
|
16
|
+
"moduleResolution": "node"
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|