Your pass
A code-signing-ID that still says Active. Dalais Org grants that coin. If it is Revoked, Expired, or not in your wallet, Link will not answer.
For developers
Link is the wallet on their computer. Your program asks Link. Link reads the ID cards. You never open the wallet yourself.
The person runs Link. Your program knocks on a door that only exists on that same computer. The door is http://127.0.0.1:21736. Do not change that address. Do not send it to a website.
Connect first, then ask. GET /v1/who and GET /v1/id do not ask. POST /v1/id/open always asks, even when the Security lamp is on.
A website cannot read these cards. A website-signing-ID does not spend and does not read an ID. This page is for a program you ship, after Dalais Org has given you a code-signing-ID.
A code-signing-ID that still says Active. Dalais Org grants that coin. If it is Revoked, Expired, or not in your wallet, Link will not answer.
Sign the exe you ship in Nexus Signing Tool. Leave YourApp.exe.nnlink.json beside that exe. Link reads the program that opened the connection. python.exe and node.exe are not your program.
Link is open. Your program writes the connect file. They press Yes in Link and pick the wallet. Your program does not pick the wallet.
Every sample below uses the same door. Copy one language. The five names are the five ID cards. Delete a name to leave that card out.
Write one small JSON file. Link reads it, checks the manifest, and asks the person. Payment and your own note are a different page: Connect an app to Link.
Pick one instance name and keep it. Letters and numbers, such as PeytonApp. The same name every run. Link turns any other character into _, makes it lower case, and keeps the last 96 characters.
The request file is %LOCALAPPDATA%\NexusNode\connect\<id>.req.json. The id inside the file is the file name without .req.json. Use w plus the current Unix time in milliseconds.
{
"id": "w1730000000000",
"app": "PeytonApp",
"instance": "PeytonApp",
"exe": "C:\\Program\\PeytonApp.exe",
"ts": 1730000000
}
exe is the full path of the program you signed. The manifest is that path plus .nnlink.json. ts is Unix seconds. Do not put the exe path on the ID knocks below. Link already knows which program connected.
Then wait. Link writes %LOCALAPPDATA%\NexusNode\connect\<id>.rep.json.
{ "ok": true, "wallet_id": "the-wallet-they-picked" }
A yes also writes %LOCALAPPDATA%\NexusNode\binds\<instance>.json. A bind file with a wallet_id means this program is connected. Poll the reply about twice a second for 60 seconds. Delete that bind file to disconnect. Leave %LOCALAPPDATA%\NexusNode\wallets alone.
The first ask after a new coin can miss. Link refreshes a local cache and says the program is not signed. Ask again. The next ask reads the cache.
First you get a list. Each row is a card name and a state. That list does not include the secret written on the card.
They are holding it, and it still counts.
Dalais Org cancelled it.
That card is not on this wallet.
Link could not see the chain. Do not treat Unread as Active.
The five ID cards are ID-age, ID-addr, ID-gov-photo, ID-bank, and ID-paypal. Require all five, or any of them. One card does not need the others.
Active on that card means it counts. The photo card does not give you the picture. The bank card does not give you the bank. The PayPal card does not give you the email.
The date and the country are a second ask. Link shows the person a question. If they press No, you do not get that field. You cannot skip that question. Delete that ask if you do not need the field.
If they say yes, you get the date of birth. Nothing else from that card.
If they say yes, you get the country. Not the city. Not the street.
Do not ask for these. Link will not send them.
The address card does not give you where they live.
The bank card tells you they hold one. It does not give you the bank details.
The PayPal card does not give you the email.
You do not get their full name, and you do not get the code that ties their cards together.
Copy the shape. The words in the answers are the words your program should look for.
The samples list all five cards. Delete a name to leave that card out. Keep one, or keep all five.
Who connected. GET /v1/who
{
"ok": true,
"paymail": "example@nexusnode.link"
}
Which cards they hold. GET /v1/id
{
"ok": true,
"ids": [
{ "name": "ID-age", "state": "Active", "origin": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" },
{ "name": "ID-addr", "state": "Active", "origin": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" },
{ "name": "ID-gov-photo", "state": "Active", "origin": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" },
{ "name": "ID-bank", "state": "Not held", "origin": "" },
{ "name": "ID-paypal", "state": "Revoked", "origin": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" }
]
}
origin is that coin's transaction id. It is not a secret, and it is not a value you hard-code. Read state. Active means that card counts. Unread is not a yes. A card you deleted from your list is ignored, even if it is in this answer.
The date, only if they say yes. POST /v1/id/open with {"name":"ID-age"}. Leave this out if you do not need the date.
{ "ok": true, "name": "ID-age", "date": "1990-01-31" }
date is YYYY-MM-DD. There is no other field from that card.
The country, only if they say yes. Same knock, with {"name":"ID-addr"}. Leave this out if you do not need the country.
{ "ok": true, "name": "ID-addr", "country": "United Kingdom" }
country is the text written on the card. Do not assume a two-letter code.
The photo, the bank, and the PayPal card stop at Active. There is no third field on those three.
A console program on .NET 6 or newer. Publish an exe, sign that exe, and run it. The five names are the five cards. Delete a name to leave that card out. Delete a field block at the bottom if you do not need that field. The open ask waits for a Yes, so leave time for that.
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
// Link is on this computer. Do not change this address.
const string Link = "http://127.0.0.1:21736";
using var http = new HttpClient();
http.Timeout = TimeSpan.FromMinutes(3);
// Who connected.
Console.WriteLine(await http.GetStringAsync(Link + "/v1/who"));
// The five ID cards. Delete a line to leave that card out.
// Keep one, or keep all five.
string[] need =
{
"ID-age", // date of birth is the field block below
"ID-addr", // country is the field block below
"ID-gov-photo", // Active means they hold it. No picture.
"ID-bank", // Active means they hold it. No bank details.
"ID-paypal", // Active means they hold it. No email.
};
// Active means that card counts. Anything else is a no.
var list = JsonDocument.Parse(await http.GetStringAsync(Link + "/v1/id"));
foreach (var card in list.RootElement.GetProperty("ids").EnumerateArray())
{
string name = card.GetProperty("name").GetString() ?? "";
bool wanted = false;
foreach (var n in need) if (n == name) wanted = true;
if (!wanted) continue;
Console.WriteLine(name + " " + card.GetProperty("state").GetString());
}
// Fields. Link asks the person. Delete a block you do not need.
// Only ask for a card you kept in the list above.
// Date of birth. If they press No, you do not get it.
var age = new StringContent("""{"name":"ID-age"}""", Encoding.UTF8, "application/json");
Console.WriteLine(await (await http.PostAsync(Link + "/v1/id/open", age)).Content.ReadAsStringAsync());
// Country. If they press No, you do not get it.
var addr = new StringContent("""{"name":"ID-addr"}""", Encoding.UTF8, "application/json");
Console.WriteLine(await (await http.PostAsync(Link + "/v1/id/open", addr)).Content.ReadAsStringAsync());
Python 3, no extra package. This is the shape. Ship it inside the exe you signed. Running it with python.exe is not your program, and Link will not answer.
import json
import urllib.request
# Link is on this computer. Do not change this address.
LINK = "http://127.0.0.1:21736"
def ask(path, body=None):
data = None if body is None else json.dumps(body).encode("utf-8")
req = urllib.request.Request(LINK + path, data=data)
if body is not None:
req.add_header("Content-Type", "application/json")
with urllib.request.urlopen(req, timeout=180) as res:
return json.load(res)
# Who connected.
print(ask("/v1/who"))
# The five ID cards. Delete a line to leave that card out.
# Keep one, or keep all five.
need = [
"ID-age", # date of birth is the field block below
"ID-addr", # country is the field block below
"ID-gov-photo", # Active means they hold it. No picture.
"ID-bank", # Active means they hold it. No bank details.
"ID-paypal", # Active means they hold it. No email.
]
# Active means that card counts. Anything else is a no.
held = ask("/v1/id")
for card in held["ids"]:
if card["name"] not in need:
continue
print(card["name"], card["state"])
# Fields. Link asks the person. Delete a block you do not need.
# Only ask for a card you kept in the list above.
# Date of birth. If they press No, you do not get it.
print(ask("/v1/id/open", {"name": "ID-age"}))
# Country. If they press No, you do not get it.
print(ask("/v1/id/open", {"name": "ID-addr"}))
Node 18 or newer. This is the shape, inside the exe you signed. It is not a script you paste into a website. node.exe is not your program, and Link will not answer that.
// Run with: node read-id.js
// Link is on this computer. Do not change this address.
const LINK = "http://127.0.0.1:21736";
async function ask(path, body) {
const res = await fetch(LINK + path, {
method: body ? "POST" : "GET",
headers: body ? { "Content-Type": "application/json" } : {},
body: body ? JSON.stringify(body) : undefined
});
return res.json();
}
// Who connected.
console.log(await ask("/v1/who"));
// The five ID cards. Delete a line to leave that card out.
// Keep one, or keep all five.
const need = [
"ID-age", // date of birth is the field block below
"ID-addr", // country is the field block below
"ID-gov-photo", // Active means they hold it. No picture.
"ID-bank", // Active means they hold it. No bank details.
"ID-paypal", // Active means they hold it. No email.
];
// Active means that card counts. Anything else is a no.
const held = await ask("/v1/id");
for (const card of held.ids) {
if (!need.includes(card.name)) continue;
console.log(card.name, card.state);
}
// Fields. Link asks the person. Delete a block you do not need.
// Only ask for a card you kept in the list above.
// Date of birth. If they press No, you do not get it.
console.log(await ask("/v1/id/open", { name: "ID-age" }));
// Country. If they press No, you do not get it.
console.log(await ask("/v1/id/open", { name: "ID-addr" }));
Link is not open on that computer. Ask them to open Link, then try again.
Your pass is missing, Revoked, or Expired, the signed file is not beside the exe, or the running program is python.exe or node.exe.
They pressed No on that ask. You do not get that field. The other cards still stand.
They do not have that card. Do not treat a missing card as a yes.
A failed ask looks like {"ok":false,"error":"not allowed"}. Read ok before you read anything else.