Skip to content

Commit 0f9338c

Browse files
committed
Add tutorial documentation
1 parent 3fafeb5 commit 0f9338c

File tree

10 files changed

+525
-188
lines changed

10 files changed

+525
-188
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
ISSUER_ACCOUNT_SEED="<your_issuer_account_seed>"
1+
ISSUER_ACCOUNT_SEED=<your-seed-goes-here>
22
PORT=3005

_code-samples/issue-credentials/js/accept_credential.js

100644100755
Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import dotenv from "dotenv";
44
import inquirer from "inquirer";
55
import { Client, Wallet } from "xrpl";
66
import { lookUpCredentials } from "./look_up_credentials.js";
7-
import { decodeHex } from "./utils.js";
7+
import { decodeHex } from "./decode_hex.js";
8+
9+
const XRPL_SERVER = "wss://s.devnet.rippletest.net:51233"
810

911
dotenv.config();
1012

@@ -19,28 +21,27 @@ async function initWallet() {
1921
validate: (input) => (input ? true : "Please specify the subject's master seed"),
2022
},
2123
]);
22-
2324
seed = seedInput;
2425
}
2526

2627
return Wallet.fromSeed(seed);
2728
}
2829

2930
async function main() {
30-
const XRPL_SERVER = "wss://s.devnet.rippletest.net:51233"
3131
const client = new Client(XRPL_SERVER);
3232
await client.connect();
3333

3434
const wallet = await initWallet();
3535

36-
const pendingCredentials = await lookUpCredentials(client, "", wallet.address, "no");
37-
if (pendingCredentials.length === 0) {
38-
console.log("No pending credentials to accept");
39-
process.exit(0);
40-
}
36+
const pendingCredentials = await lookUpCredentials(
37+
client,
38+
"",
39+
wallet.address,
40+
"no"
41+
);
4142

4243
const choices = pendingCredentials.map((cred, i) => ({
43-
name: `${i+1})'${decodeHex(cred.CredentialType)}' issued by ${cred.Issuer}`,
44+
name: `${i+1}) '${decodeHex(cred.CredentialType)}' issued by ${cred.Issuer}`,
4445
value: i,
4546
}));
4647
choices.unshift({ name: "0) No, quit.", value: -1 });
Lines changed: 51 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import {
2-
strToHex,
3-
decodeHex,
4-
datetimeToRippleTime,
5-
rippleTimeToDatetime,
6-
} from "./utils.js";
7-
8-
import { isValidClassicAddress } from "xrpl";
2+
isoTimeToRippleTime,
3+
rippleTimeToISOTime,
4+
isValidClassicAddress,
5+
} from "xrpl";
6+
import { stringToHex } from "@xrplf/isomorphic/dist/utils/index.js";
97

8+
import { decodeHex } from "./decode_hex.js";
109
import { ValueError } from "./errors.js";
1110

1211
// Regex constants
@@ -15,7 +14,6 @@ const URI_REGEX = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]{1,256}$/;
1514

1615
/**
1716
* Validate credential request.
18-
*
1917
* This function performs parameter validation. Validated fields:
2018
* - subject (required): the subject of the credential, as a classic address
2119
* - credential (required): the credential type, in human-readable (ASCII) chars
@@ -35,19 +33,16 @@ export function validateCredentialRequest({ subject, credential, uri, expiration
3533
if (typeof credential !== "string") {
3634
throw new ValueError("Must provide a string 'credential' field");
3735
}
38-
39-
/*
40-
Checks if the specified credential type is one that this service issues.
41-
42-
XRPL credential types can be any binary data; this service issues
43-
any credential that can be encoded from the following ASCII chars:
44-
alphanumeric characters, underscore, period, and dash.
45-
(min length 1, max 64)
46-
47-
You might want to further limit the credential types, depending on your
48-
use case; for example, you might only issue one specific credential type.
49-
*/
5036
if (!CREDENTIAL_REGEX.test(credential)) {
37+
/**
38+
* Checks if the specified credential type is one that this service issues.
39+
* XRPL credential types can be any binary data; this service issues
40+
* any credential that can be encoded from the following ASCII chars:
41+
* alphanumeric characters, underscore, period, and dash. (min length 1, max 64)
42+
*
43+
* You might want to further limit the credential types, depending on your
44+
* use case; for example, you might only issue one specific credential type.
45+
*/
5146
throw new ValueError(`credential not allowed: '${credential}'.`);
5247
}
5348

@@ -76,12 +71,10 @@ export function validateCredentialRequest({ subject, credential, uri, expiration
7671
// Validate and parse expiration
7772
let parsedExpiration;
7873
if (expiration !== undefined) {
79-
if (typeof expiration === "string") {
80-
parsedExpiration = new Date(expiration);
81-
} else {
74+
if (typeof expiration !== "string") {
8275
throw new ValueError(`Unsupported expiration format: ${typeof expiration}`);
8376
}
84-
77+
parsedExpiration = new Date(expiration);
8578
if (isNaN(parsedExpiration.getTime())) {
8679
throw new ValueError(`Invalid expiration date: ${expiration}`);
8780
}
@@ -95,20 +88,41 @@ export function validateCredentialRequest({ subject, credential, uri, expiration
9588
};
9689
}
9790

98-
/**
99-
* As a credential issuer, you typically need to verify some information
100-
* about someone before you issue them a credential. For this example,
101-
* the user passes relevant information in a documents field of the API request.
102-
* The documents are kept confidential, off-chain.
103-
*/
104-
export function verifyDocuments({documents}) {
105-
/*
106-
This is where you would check the user's documents to see if you
107-
should issue the requested Credential to them.
108-
Depending on the type of credentials your service needs, you might
109-
need to implement different types of checks here.
91+
// Convert an XRPL ledger entry into a usable credential object
92+
export function credentialFromXrpl(entry) {
93+
const { Subject, CredentialType, URI, Expiration, Flags } = entry;
94+
return {
95+
subject: Subject,
96+
credential: decodeHex(CredentialType),
97+
uri: URI ? decodeHex(URI) : undefined,
98+
expiration: Expiration ? rippleTimeToISOTime(Expiration) : undefined,
99+
accepted: Boolean(Flags & 0x00010000), // lsfAccepted
100+
};
101+
}
102+
103+
// Convert to an object in a format closer to the XRP Ledger representation
104+
export function credentialToXrpl(cred) {
105+
// Credential type and URI are hexadecimal;
106+
// Expiration, if present, is in seconds since the Ripple Epoch.
107+
return {
108+
subject: cred.subject,
109+
credential: stringToHex(cred.credential),
110+
uri: cred.uri ? stringToHex(cred.uri) : undefined,
111+
expiration: cred.expiration
112+
? isoTimeToRippleTime(cred.expiration)
113+
: undefined,
114+
};
115+
}
116+
117+
118+
export function verifyDocuments({ documents }) {
119+
/**
120+
* This is where you would check the user's documents to see if you
121+
* should issue the requested Credential to them.
122+
* Depending on the type of credentials your service needs, you might
123+
* need to implement different types of checks here.
110124
*/
111-
if (typeof documents !== 'object' || Object.keys(documents).length === 0) {
125+
if (typeof documents !== "object" || Object.keys(documents).length === 0) {
112126
throw new ValueError("you must provide a non-empty 'documents' field");
113127
}
114128

@@ -123,50 +137,3 @@ export function verifyDocuments({documents}) {
123137
throw new ValueError("reason must include 'please'");
124138
}
125139
}
126-
127-
/**
128-
* Convert to a Credential object in a format closer to the XRP Ledger representation.
129-
* Credential type and URI are hexadecimal;
130-
* Expiration, if present, is in seconds since the Ripple Epoch.
131-
*/
132-
export function credentialToXrpl(cred) {
133-
return {
134-
subject: cred.subject,
135-
credential: strToHex(cred.credential),
136-
uri: cred.uri ? strToHex(cred.uri) : undefined,
137-
expiration: cred.expiration ? datetimeToRippleTime(cred.expiration) : undefined,
138-
};
139-
}
140-
141-
// Convert an XRPL ledger entry into a usable credential object
142-
export function parseCredentialFromXrpl(entry) {
143-
const { Subject, CredentialType, URI, Expiration, Flags } = entry;
144-
145-
if (!Subject || !CredentialType) {
146-
throw new Error("Missing required fields from XRPL credential entry");
147-
}
148-
149-
return {
150-
subject: Subject,
151-
credential: decodeHex(CredentialType),
152-
uri: URI ? decodeHex(URI) : undefined,
153-
expiration: Expiration ? rippleTimeToDatetime(Expiration) : undefined,
154-
accepted: Boolean(Flags & 0x00010000), // lsfAccepted
155-
};
156-
}
157-
158-
/**
159-
* Convert a credential object into API-friendly JSON
160-
*/
161-
export function serializeCredential(cred) {
162-
const result = {
163-
subject: cred.subject,
164-
credential: cred.credential,
165-
};
166-
167-
if (cred.uri) result.uri = cred.uri;
168-
if (cred.expiration) result.expiration = cred.expiration.toISOString();
169-
if (cred.accepted !== undefined) result.accepted = cred.accepted;
170-
171-
return result;
172-
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export function decodeHex(sHex) {
2+
/**
3+
* Try decoding a hex string as ASCII; return the decoded string on success,
4+
* or the un-decoded string prefixed by '(BIN) ' on failure.
5+
*/
6+
try {
7+
const buffer = Buffer.from(sHex, "hex");
8+
return buffer.toString("ascii");
9+
// Could use utf-8 instead, but it has more edge cases.
10+
// Optionally, sanitize the string for display before returning
11+
} catch (err) {
12+
return "(BIN) " + sHex;
13+
}
14+
}

0 commit comments

Comments
 (0)