@sorandomains/owner 0.5.1 → 0.7.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 +20 -4
- package/dist/index.d.ts +51 -1
- package/dist/index.js +109 -4
- package/dist/native-allowlist.d.ts +21 -0
- package/dist/native-allowlist.js +34 -0
- package/dist/native-auth.d.ts +31 -0
- package/dist/native-auth.js +85 -0
- package/dist/native-codec.d.ts +38 -0
- package/dist/native-codec.js +95 -0
- package/dist/native-transport.d.ts +66 -0
- package/dist/native-transport.js +136 -0
- package/dist/native-types.d.ts +166 -0
- package/dist/native-types.js +98 -0
- package/dist/payment.d.ts +37 -0
- package/dist/payment.js +148 -0
- package/package.json +2 -2
package/dist/payment.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/** Direct v1 and muxed v2 payment records. Kept identical in the independently published lookup
|
|
2
|
+
* and holder packages; the conformance tests exercise both copies. */
|
|
3
|
+
import { StrKey, nativeToScVal, xdr } from "@stellar/stellar-sdk";
|
|
4
|
+
export const PAYMENT_RECORD_KEY = "payment";
|
|
5
|
+
const MAX_U64 = 18446744073709551615n;
|
|
6
|
+
const utf8 = (value) => new TextEncoder().encode(value);
|
|
7
|
+
/** Reject invalid data without normalizing meaningful memo bytes. */
|
|
8
|
+
export function validatePaymentDestination(value) {
|
|
9
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
10
|
+
throw new Error("invalid payment destination");
|
|
11
|
+
const p = value;
|
|
12
|
+
if (Object.keys(p).sort().join(",") !== "address,memo")
|
|
13
|
+
throw new Error("unexpected payment fields");
|
|
14
|
+
if (typeof p.address !== "string" || (!StrKey.isValidEd25519PublicKey(p.address) && !StrKey.isValidContract(p.address) && !StrKey.isValidMed25519PublicKey(p.address)))
|
|
15
|
+
throw new Error("payment address must be a valid G, C or M address");
|
|
16
|
+
if (!p.memo || typeof p.memo !== "object" || Array.isArray(p.memo))
|
|
17
|
+
throw new Error("invalid memo");
|
|
18
|
+
const m = p.memo;
|
|
19
|
+
if (m.type === "none") {
|
|
20
|
+
if (Object.keys(m).join(",") !== "type")
|
|
21
|
+
throw new Error("none memo cannot have a value");
|
|
22
|
+
if (StrKey.isValidMed25519PublicKey(p.address))
|
|
23
|
+
decodeMuxedAddress(p.address);
|
|
24
|
+
return { address: p.address, memo: { type: "none" } };
|
|
25
|
+
}
|
|
26
|
+
if (!StrKey.isValidEd25519PublicKey(p.address))
|
|
27
|
+
throw new Error("memos require a classic G address");
|
|
28
|
+
if (Object.keys(m).sort().join(",") !== "type,value" || typeof m.value !== "string")
|
|
29
|
+
throw new Error("memo value must be a string");
|
|
30
|
+
switch (m.type) {
|
|
31
|
+
case "id":
|
|
32
|
+
if (!/^(0|[1-9][0-9]{0,19})$/.test(m.value) || BigInt(m.value) > MAX_U64)
|
|
33
|
+
throw new Error("memo ID must be canonical decimal u64");
|
|
34
|
+
return { address: p.address, memo: { type: "id", value: m.value } };
|
|
35
|
+
case "text": {
|
|
36
|
+
const bytes = utf8(m.value);
|
|
37
|
+
if (!bytes.length || bytes.length > 28 || new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes) !== m.value)
|
|
38
|
+
throw new Error("memo text must be valid UTF-8, 1–28 bytes");
|
|
39
|
+
return { address: p.address, memo: { type: "text", value: m.value } };
|
|
40
|
+
}
|
|
41
|
+
case "hash":
|
|
42
|
+
if (!/^[0-9a-f]{64}$/.test(m.value))
|
|
43
|
+
throw new Error("memo hash must be 32 bytes as lowercase hex");
|
|
44
|
+
return { address: p.address, memo: { type: "hash", value: m.value } };
|
|
45
|
+
default: throw new Error("unsupported memo type");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function encodePaymentRecord(value) {
|
|
49
|
+
const p = validatePaymentDestination(value);
|
|
50
|
+
if (StrKey.isValidMed25519PublicKey(p.address)) {
|
|
51
|
+
const muxed = decodeMuxedAddress(p.address);
|
|
52
|
+
return `2|${muxed.account}|muxed|${muxed.id}`;
|
|
53
|
+
}
|
|
54
|
+
return `1|${p.address}|${p.memo.type}|${p.memo.type === "none" ? "" : p.memo.value}`;
|
|
55
|
+
}
|
|
56
|
+
/** Empty/missing records are not explicit payment instructions. */
|
|
57
|
+
export function parsePaymentRecord(raw) {
|
|
58
|
+
if (typeof raw !== "string" || utf8(raw).length > 128)
|
|
59
|
+
throw new Error("invalid payment record length");
|
|
60
|
+
const first = raw.indexOf("|");
|
|
61
|
+
const second = raw.indexOf("|", first + 1);
|
|
62
|
+
const third = raw.indexOf("|", second + 1);
|
|
63
|
+
if (first < 0 || second < 0 || third < 0)
|
|
64
|
+
throw new Error("unsupported payment record");
|
|
65
|
+
const version = raw.slice(0, first);
|
|
66
|
+
const address = raw.slice(first + 1, second);
|
|
67
|
+
const type = raw.slice(second + 1, third);
|
|
68
|
+
const value = raw.slice(third + 1);
|
|
69
|
+
if (version === "2" && type === "muxed")
|
|
70
|
+
return { address: encodeMuxedAddress(address, value), memo: { type: "none" } };
|
|
71
|
+
if (version !== "1" || (!StrKey.isValidEd25519PublicKey(address) && !StrKey.isValidContract(address)))
|
|
72
|
+
throw new Error("unsupported payment record");
|
|
73
|
+
if (type === "none" && value !== "")
|
|
74
|
+
throw new Error("none memo cannot have a value");
|
|
75
|
+
return validatePaymentDestination({ address, memo: type === "none" ? { type } : { type, value } });
|
|
76
|
+
}
|
|
77
|
+
export function paymentMemoToScVal(memo) {
|
|
78
|
+
const symbol = (s) => nativeToScVal(s, { type: "symbol" });
|
|
79
|
+
switch (memo.type) {
|
|
80
|
+
case "none": return xdr.ScVal.scvVec([symbol("None")]);
|
|
81
|
+
case "id": return xdr.ScVal.scvVec([symbol("Id"), nativeToScVal(BigInt(memo.value), { type: "u64" })]);
|
|
82
|
+
case "text": return xdr.ScVal.scvVec([symbol("Text"), nativeToScVal(memo.value, { type: "string" })]);
|
|
83
|
+
case "hash": return xdr.ScVal.scvVec([symbol("Hash"), nativeToScVal(Uint8Array.from(memo.value.match(/../g), (b) => parseInt(b, 16)), { type: "bytes" })]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Decode Soroban contract enum values, checking arity and native types. */
|
|
87
|
+
export function paymentFromNative(raw) {
|
|
88
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
89
|
+
throw new Error("invalid payment result");
|
|
90
|
+
const p = raw;
|
|
91
|
+
if (Object.keys(p).sort().join(",") !== "address,memo" || !Array.isArray(p.memo))
|
|
92
|
+
throw new Error("invalid payment result fields");
|
|
93
|
+
if (typeof p.address !== "string" || (!StrKey.isValidEd25519PublicKey(p.address) && !StrKey.isValidContract(p.address)))
|
|
94
|
+
throw new Error("direct payment must contain a G or C address");
|
|
95
|
+
const m = p.memo;
|
|
96
|
+
let memo;
|
|
97
|
+
if (m.length === 1 && m[0] === "None")
|
|
98
|
+
memo = { type: "none" };
|
|
99
|
+
else if (m.length === 2 && m[0] === "Id" && typeof m[1] === "bigint")
|
|
100
|
+
memo = { type: "id", value: m[1].toString() };
|
|
101
|
+
else if (m.length === 2 && m[0] === "Text" && typeof m[1] === "string")
|
|
102
|
+
memo = { type: "text", value: m[1] };
|
|
103
|
+
else if (m.length === 2 && m[0] === "Hash" && m[1] instanceof Uint8Array && m[1].length === 32)
|
|
104
|
+
memo = { type: "hash", value: Array.from(m[1], (b) => b.toString(16).padStart(2, "0")).join("") };
|
|
105
|
+
else
|
|
106
|
+
throw new Error("invalid payment memo result");
|
|
107
|
+
return validatePaymentDestination({ address: p.address, memo });
|
|
108
|
+
}
|
|
109
|
+
/** Decode a full muxed destination without treating its operation ID as a memo. */
|
|
110
|
+
export function decodeMuxedAddress(address) {
|
|
111
|
+
if (typeof address !== "string" || !StrKey.isValidMed25519PublicKey(address))
|
|
112
|
+
throw new Error("invalid muxed M address");
|
|
113
|
+
const raw = StrKey.decodeMed25519PublicKey(address);
|
|
114
|
+
if (raw.length !== 40 || StrKey.encodeMed25519PublicKey(raw) !== address)
|
|
115
|
+
throw new Error("noncanonical muxed address");
|
|
116
|
+
let id = 0n;
|
|
117
|
+
for (const byte of raw.subarray(32))
|
|
118
|
+
id = (id << 8n) | BigInt(byte);
|
|
119
|
+
return { account: StrKey.encodeEd25519PublicKey(raw.subarray(0, 32)), id: id.toString() };
|
|
120
|
+
}
|
|
121
|
+
/** Encode only canonical G + exact decimal u64; numbers are never accepted. */
|
|
122
|
+
export function encodeMuxedAddress(account, id) {
|
|
123
|
+
if (typeof account !== "string" || !StrKey.isValidEd25519PublicKey(account))
|
|
124
|
+
throw new Error("muxed base must be a G account");
|
|
125
|
+
if (typeof id !== "string" || !/^(0|[1-9][0-9]{0,19})$/.test(id) || BigInt(id) > MAX_U64)
|
|
126
|
+
throw new Error("muxed ID must be canonical decimal u64");
|
|
127
|
+
const raw = new Uint8Array(40);
|
|
128
|
+
raw.set(StrKey.decodeEd25519PublicKey(account));
|
|
129
|
+
let value = BigInt(id);
|
|
130
|
+
for (let i = 39; i >= 32; i--) {
|
|
131
|
+
raw[i] = Number(value & 255n);
|
|
132
|
+
value >>= 8n;
|
|
133
|
+
}
|
|
134
|
+
return StrKey.encodeMed25519PublicKey(raw);
|
|
135
|
+
}
|
|
136
|
+
/** Strict additive on-chain destination enum; v1 payment structs stay separate. */
|
|
137
|
+
export function destinationFromNative(raw) {
|
|
138
|
+
if (!Array.isArray(raw) || raw.length !== 2)
|
|
139
|
+
throw new Error("invalid destination enum");
|
|
140
|
+
if (raw[0] === "Direct")
|
|
141
|
+
return paymentFromNative(raw[1]);
|
|
142
|
+
if (raw[0] !== "Muxed" || !raw[1] || typeof raw[1] !== "object" || Array.isArray(raw[1]))
|
|
143
|
+
throw new Error("invalid destination variant");
|
|
144
|
+
const value = raw[1];
|
|
145
|
+
if (Object.keys(value).sort().join(",") !== "account,id" || typeof value.account !== "string" || typeof value.id !== "bigint")
|
|
146
|
+
throw new Error("invalid muxed destination fields");
|
|
147
|
+
return { address: encodeMuxedAddress(value.account, value.id.toString()), memo: { type: "none" } };
|
|
148
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sorandomains/owner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Run your Soran namespace on Stellar \u2014 issue names, manage lifecycle, transfer, and walk the one-way permanence door.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"typecheck": "tsc --noEmit",
|
|
14
14
|
"prepublishOnly": "tsc",
|
|
15
15
|
"check:browser": "esbuild dist/index.js --bundle --platform=browser --external:@stellar/stellar-sdk --outfile=browser-check.js --legal-comments=none --log-level=error && node -e \"const fs=require('fs');const s=fs.readFileSync('browser-check.js','utf8');fs.unlinkSync('browser-check.js');if(/\\bBuffer\\b/.test(s)){console.error('FAIL: bare Buffer reference in browser bundle');process.exit(1)}console.log('browser bundle clean')\"",
|
|
16
|
-
"test": "node --import tsx test/decode-failure.test.mts && node --import tsx --test test/integration.test.mts"
|
|
16
|
+
"test": "node --import tsx test/decode-failure.test.mts && node --import tsx --test test/integration.test.mts test/native-*.test.mts"
|
|
17
17
|
},
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"repository": {
|