@tesser-payments/sdk-ts 0.0.4 → 0.2.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 +63 -39
- package/dist/index.d.ts +1 -1
- package/dist/index.js +388 -26
- package/dist/internal/errors.d.ts +5 -5
- package/dist/internal/types.d.ts +13 -1
- package/dist/internal/version.d.ts +1 -0
- package/dist/signer.d.ts +24 -7
- package/dist/signing/create-wallet.d.ts +2 -1
- package/dist/signing/network-type.d.ts +2 -0
- package/dist/signing/unsigned-payload.d.ts +17 -0
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
# @tesser-payments/sdk-ts
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
it. Bring your own client.
|
|
3
|
+
TypeScript SDK for the Tesser API: sign `signCreateWallet` and `signStep`
|
|
4
|
+
requests locally with your signing key. The SDK does not handle OAuth or submit
|
|
5
|
+
requests for you; bring your own client.
|
|
7
6
|
|
|
8
7
|
## Install
|
|
9
8
|
|
|
@@ -12,9 +11,7 @@ bun add @tesser-payments/sdk-ts @tesser-payments/types
|
|
|
12
11
|
```
|
|
13
12
|
|
|
14
13
|
`@tesser-payments/types` is a peer dependency — install it alongside the SDK.
|
|
15
|
-
|
|
16
|
-
`signCreateWallet` and `signStep` activities) and is installed automatically;
|
|
17
|
-
its Apache-2.0 license aligns with the SDK's. Apache 2.0 licensed.
|
|
14
|
+
Apache 2.0 licensed.
|
|
18
15
|
|
|
19
16
|
## Quick start
|
|
20
17
|
|
|
@@ -25,7 +22,7 @@ const signer = new LocalSigner({
|
|
|
25
22
|
signing: {
|
|
26
23
|
publicKey: process.env.SIGNING_PUBLIC_KEY!, // 33-byte compressed P-256
|
|
27
24
|
privateKey: process.env.SIGNING_PRIVATE_KEY!,
|
|
28
|
-
enclaveId: process.env.SIGNING_ENCLAVE_ID!,
|
|
25
|
+
enclaveId: process.env.SIGNING_ENCLAVE_ID!,
|
|
29
26
|
},
|
|
30
27
|
});
|
|
31
28
|
|
|
@@ -52,6 +49,22 @@ const res = await fetch(`${API_BASE_URL}/v1/accounts/wallets`, {
|
|
|
52
49
|
`signature` is `base64(JSON.stringify({ body, stamp }))` — drop it into the
|
|
53
50
|
Tesser API request body as the documented `signature` field.
|
|
54
51
|
|
|
52
|
+
`apiBaseUrl` is optional; pass it to use only that URL (`https://api.tesser.xyz`
|
|
53
|
+
or `https://api-sandbox.tesser.xyz`; a trailing `/` or `/v1` is removed). Pass an
|
|
54
|
+
optional `fetch` implementation to configure a proxy.
|
|
55
|
+
|
|
56
|
+
`signCreateWallet` signs a wallet-creation request. For `stablecoin_ethereum`,
|
|
57
|
+
it first fetches the request to sign from the Tesser API and checks that it
|
|
58
|
+
matches your parameters. The private key never leaves your process. Tesser
|
|
59
|
+
currently supports managed wallets for `stablecoin_ethereum` only.
|
|
60
|
+
|
|
61
|
+
`LocalSigner({ signing })` works with production and sandbox signing keys.
|
|
62
|
+
Without `apiBaseUrl`, the first `stablecoin_ethereum` `signCreateWallet` tries
|
|
63
|
+
production and, if production does not accept your signing key, sandbox, and
|
|
64
|
+
remembers it for later calls. Set `apiBaseUrl` if your network allows only one
|
|
65
|
+
Tesser host, or if you create a new signer per request (each new signer detects
|
|
66
|
+
again). Send your own API requests to the environment of your signing key.
|
|
67
|
+
|
|
55
68
|
**Private-key handling.** `signer.signing` exposes only `{ publicKey, enclaveId }`.
|
|
56
69
|
The private key lives on the instance as an ECMA private field and is not
|
|
57
70
|
reachable via property access, `console.log`, or `JSON.stringify`. The SDK still
|
|
@@ -59,8 +72,7 @@ uses it internally to stamp activities — you just can't pull it back out.
|
|
|
59
72
|
|
|
60
73
|
## `signStep`
|
|
61
74
|
|
|
62
|
-
`signStep`
|
|
63
|
-
single rebalance step. Pass the unsigned transaction Tesser returned, the
|
|
75
|
+
`signStep` signs a single rebalance step. Pass the unsigned transaction Tesser returned, the
|
|
64
76
|
on-chain `signWith` address, and the network — that's all the SDK needs to
|
|
65
77
|
produce a signature:
|
|
66
78
|
|
|
@@ -71,9 +83,7 @@ const step: StepForSigning = {
|
|
|
71
83
|
unsignedTransaction: '0x02...', // hex-encoded raw tx Tesser returned
|
|
72
84
|
signWith: '0xabc...', // account.crypto_wallet_address from
|
|
73
85
|
// GET /v1/accounts/{from_account_id}
|
|
74
|
-
network: 'BASE_SEPOLIA',
|
|
75
|
-
// ETHEREUM_SEPOLIA | POLYGON |
|
|
76
|
-
// POLYGON_AMOY | SOLANA
|
|
86
|
+
network: 'BASE_SEPOLIA',
|
|
77
87
|
};
|
|
78
88
|
|
|
79
89
|
const signed = await signer.signStep(step);
|
|
@@ -92,29 +102,55 @@ await fetch(
|
|
|
92
102
|
```
|
|
93
103
|
|
|
94
104
|
`signStep` returns `{ signature, metadata }`. The `signature` field is what
|
|
95
|
-
the Tesser API needs; `metadata` carries the
|
|
96
|
-
|
|
105
|
+
the Tesser API needs; `metadata` carries the signed body and stamp header value
|
|
106
|
+
for debugging.
|
|
97
107
|
|
|
98
108
|
The `network` field is typed as `SupportedNetwork`, derived from the SDK's
|
|
99
|
-
network
|
|
109
|
+
supported-network table — so a typo like `'BASE-SEPOLIA'` (hyphen) is a
|
|
100
110
|
compile-time error. If you're pulling the value out of a webhook payload or
|
|
101
111
|
env var, cast it at that boundary.
|
|
102
112
|
|
|
113
|
+
Supported networks are `BASE`, `BASE_SEPOLIA`, `ETHEREUM`, `ETHEREUM_SEPOLIA`,
|
|
114
|
+
`POLYGON`, `POLYGON_AMOY`, `SOLANA`, `TEMPO`, and `TEMPO_MODERATO`.
|
|
115
|
+
|
|
116
|
+
### Tempo transfers
|
|
117
|
+
|
|
118
|
+
1. Pass the Tempo transaction Tesser provides (`0x76...`), your wallet
|
|
119
|
+
address, and `TEMPO` or `TEMPO_MODERATO` to `signStep`.
|
|
120
|
+
2. The SDK authorizes the transaction with your API key, without changing
|
|
121
|
+
it. Submit the returned `signature` to the Tesser signing endpoint.
|
|
122
|
+
3. Tesser completes the transfer, broadcasts it, and pays the network fee.
|
|
123
|
+
|
|
124
|
+
Unknown networks raise `TesserConfigError` before anything is signed.
|
|
125
|
+
|
|
103
126
|
## Error handling
|
|
104
127
|
|
|
105
128
|
The SDK mirrors Tesser's documented error envelope
|
|
106
129
|
(<https://docs.tesser.xyz/overviews/errors>) rather than inventing a custom
|
|
107
|
-
HTTP-status taxonomy
|
|
108
|
-
errors are exported:
|
|
130
|
+
HTTP-status taxonomy:
|
|
109
131
|
|
|
110
132
|
```
|
|
111
133
|
TesserError (base; .cause set on all)
|
|
112
134
|
├── TesserConfigError bad input at construction or call site
|
|
113
|
-
|
|
114
|
-
|
|
135
|
+
├── TesserAPIError Tesser answered with an error status or
|
|
136
|
+
│ an invalid response
|
|
137
|
+
├── TesserConnectionError the request to Tesser failed at the network level
|
|
138
|
+
├── TesserTimeoutError the request to Tesser timed out
|
|
139
|
+
└── TesserSigningError signing failure, or a fetched request that
|
|
140
|
+
│ does not match your parameters
|
|
141
|
+
└── StampError the signing key could not sign
|
|
115
142
|
```
|
|
116
143
|
|
|
117
|
-
|
|
144
|
+
`TesserAPIError`, `TesserConnectionError`, and `TesserTimeoutError` come from
|
|
145
|
+
the request `signCreateWallet` makes to Tesser for `stablecoin_ethereum`.
|
|
146
|
+
`TesserAPIError` carries `status`, `headers`, `requestId`, and the parsed
|
|
147
|
+
`errors[]` (with `errorCode` for the first one). `TesserConnectionError`,
|
|
148
|
+
`TesserTimeoutError`, and a `TesserAPIError` with status 429 or 5xx are safe to
|
|
149
|
+
retry; a `TesserAPIError` with any other 4xx status fails the same way on
|
|
150
|
+
retry. Without `apiBaseUrl`, if neither production nor sandbox accepts your
|
|
151
|
+
signing key, the `TesserAPIError` from production is thrown.
|
|
152
|
+
|
|
153
|
+
Each error carries the original failure as `.cause` and can be discriminated via
|
|
118
154
|
`instanceof` or the static `.is()` guard:
|
|
119
155
|
|
|
120
156
|
```typescript
|
|
@@ -124,7 +160,7 @@ try {
|
|
|
124
160
|
await signer.signStep(step);
|
|
125
161
|
} catch (err) {
|
|
126
162
|
if (StampError.is(err)) {
|
|
127
|
-
//
|
|
163
|
+
// The key could not sign — usually a key-format problem.
|
|
128
164
|
}
|
|
129
165
|
if (TesserError.is(err)) {
|
|
130
166
|
console.error('Signing failed', { name: err.name, cause: err.cause });
|
|
@@ -133,29 +169,19 @@ try {
|
|
|
133
169
|
}
|
|
134
170
|
```
|
|
135
171
|
|
|
136
|
-
HTTP-side errors (`TesserAPIError`, `TesserConnectionError`,
|
|
137
|
-
`TesserTimeoutError`) will return when the optional bundled client lands in a
|
|
138
|
-
future version. Until then, model HTTP failures with whatever your `fetch`
|
|
139
|
-
wrapper produces.
|
|
140
|
-
|
|
141
172
|
## Setup notes
|
|
142
173
|
|
|
143
|
-
`signing.publicKey` must be the **33-byte compressed** form
|
|
174
|
+
`signing.publicKey` must be the **33-byte compressed** form Tesser uses for
|
|
144
175
|
API-key auth lookups (66 hex chars, prefix `02` or `03`). Dashboards that
|
|
145
176
|
display the 65-byte uncompressed form need to be compressed before use:
|
|
146
177
|
|
|
147
178
|
```sh
|
|
148
|
-
|
|
149
|
-
'const c = require("@turnkey/crypto"); \
|
|
150
|
-
const u = process.argv[1].replace(/^0x/i,""); \
|
|
151
|
-
const b = Buffer.from(u, "hex"); \
|
|
152
|
-
console.log(Buffer.from(c.compressRawPublicKey(b)).toString("hex"))' \
|
|
179
|
+
node -e 'const u = process.argv[1].replace(/^0x/i, ""); console.log(require("node:crypto").ECDH.convertKey(u, "prime256v1", "hex", "hex", "compressed"))' \
|
|
153
180
|
04...your-uncompressed-key...
|
|
154
181
|
```
|
|
155
182
|
|
|
156
|
-
If a key is registered
|
|
157
|
-
|
|
158
|
-
correct — re-register the key in compressed form to fix.
|
|
183
|
+
If a key is registered in uncompressed form, signing is rejected even when the
|
|
184
|
+
keypair is logically correct — re-register the key in compressed form to fix.
|
|
159
185
|
|
|
160
186
|
## Examples
|
|
161
187
|
|
|
@@ -173,9 +199,7 @@ copy [`.env.example`](./.env.example) and fill in the values first.
|
|
|
173
199
|
## Out of scope
|
|
174
200
|
|
|
175
201
|
- **`TesserClient`** — the previous bundled HTTP client (`getAccountAddress`,
|
|
176
|
-
retry/timeout/logger plumbing) is intentionally not
|
|
177
|
-
re-add it later as an opt-in `LocalSignerOptions.client` field; that change
|
|
178
|
-
is purely additive and will not break v0.0.1 call sites.
|
|
202
|
+
retry/timeout/logger plumbing) is intentionally not included.
|
|
179
203
|
- **Webhook signature verification** — the header name and HMAC algorithm
|
|
180
204
|
aren't currently documented in public Tesser docs.
|
|
181
205
|
- **Solana / Stellar `signCreateWallet`** — exported as `@experimental` types;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export { LocalSigner } from './signer.js';
|
|
|
2
2
|
export type { PublicSigningInfo } from './signer.js';
|
|
3
3
|
export type { CreateWalletParams, LocalSignerOptions, SignedResult, SignedResultMetadata, SignedStepResult, SignedStepResultMetadata, SigningConfig, StepForSigning, WalletType, } from './internal/types.js';
|
|
4
4
|
export type { SupportedNetwork } from './signing/network-type.js';
|
|
5
|
-
export { StampError, TesserConfigError, TesserError, TesserSigningError, } from './internal/errors.js';
|
|
5
|
+
export { StampError, TesserAPIError, TesserConfigError, TesserConnectionError, TesserError, TesserSigningError, TesserTimeoutError, } from './internal/errors.js';
|
|
6
6
|
export type { TesserErrorDetail } from './internal/errors.js';
|
package/dist/index.js
CHANGED
|
@@ -18273,6 +18273,47 @@ class TesserConfigError extends TesserError {
|
|
|
18273
18273
|
return err instanceof TesserConfigError;
|
|
18274
18274
|
}
|
|
18275
18275
|
}
|
|
18276
|
+
|
|
18277
|
+
class TesserAPIError extends TesserError {
|
|
18278
|
+
name = "TesserAPIError";
|
|
18279
|
+
status;
|
|
18280
|
+
headers;
|
|
18281
|
+
requestId;
|
|
18282
|
+
errors;
|
|
18283
|
+
constructor(message, options) {
|
|
18284
|
+
super(message, { cause: options.cause });
|
|
18285
|
+
this.status = options.status;
|
|
18286
|
+
if (options.headers !== undefined)
|
|
18287
|
+
this.headers = options.headers;
|
|
18288
|
+
if (options.requestId !== undefined)
|
|
18289
|
+
this.requestId = options.requestId;
|
|
18290
|
+
this.errors = options.errors ?? [];
|
|
18291
|
+
}
|
|
18292
|
+
get errorCode() {
|
|
18293
|
+
return this.errors[0]?.errorCode;
|
|
18294
|
+
}
|
|
18295
|
+
hasCode(...codes) {
|
|
18296
|
+
return this.errors.some((e) => codes.includes(e.errorCode));
|
|
18297
|
+
}
|
|
18298
|
+
static is(err) {
|
|
18299
|
+
return err instanceof TesserAPIError;
|
|
18300
|
+
}
|
|
18301
|
+
}
|
|
18302
|
+
|
|
18303
|
+
class TesserConnectionError extends TesserError {
|
|
18304
|
+
name = "TesserConnectionError";
|
|
18305
|
+
static is(err) {
|
|
18306
|
+
return err instanceof TesserConnectionError;
|
|
18307
|
+
}
|
|
18308
|
+
}
|
|
18309
|
+
|
|
18310
|
+
class TesserTimeoutError extends TesserError {
|
|
18311
|
+
name = "TesserTimeoutError";
|
|
18312
|
+
static is(err) {
|
|
18313
|
+
return err instanceof TesserTimeoutError;
|
|
18314
|
+
}
|
|
18315
|
+
}
|
|
18316
|
+
|
|
18276
18317
|
class TesserSigningError extends TesserError {
|
|
18277
18318
|
name = "TesserSigningError";
|
|
18278
18319
|
static is(err) {
|
|
@@ -19814,7 +19855,7 @@ var DNS = "dns";
|
|
|
19814
19855
|
var DN = "dn";
|
|
19815
19856
|
var EMAIL = "email";
|
|
19816
19857
|
var IP = "ip";
|
|
19817
|
-
var
|
|
19858
|
+
var URL2 = "url";
|
|
19818
19859
|
var GUID = "guid";
|
|
19819
19860
|
var UPN = "upn";
|
|
19820
19861
|
var REGISTERED_ID = "id";
|
|
@@ -19870,7 +19911,7 @@ class GeneralName extends AsnData {
|
|
|
19870
19911
|
});
|
|
19871
19912
|
break;
|
|
19872
19913
|
}
|
|
19873
|
-
case
|
|
19914
|
+
case URL2:
|
|
19874
19915
|
name = new asn1X509__namespace.GeneralName({ uniformResourceIdentifier: args[1] });
|
|
19875
19916
|
break;
|
|
19876
19917
|
default:
|
|
@@ -19894,7 +19935,7 @@ class GeneralName extends AsnData {
|
|
|
19894
19935
|
this.type = IP;
|
|
19895
19936
|
this.value = asn.iPAddress;
|
|
19896
19937
|
} else if (asn.uniformResourceIdentifier != null) {
|
|
19897
|
-
this.type =
|
|
19938
|
+
this.type = URL2;
|
|
19898
19939
|
this.value = asn.uniformResourceIdentifier;
|
|
19899
19940
|
} else if (asn.registeredID != null) {
|
|
19900
19941
|
this.type = REGISTERED_ID;
|
|
@@ -19941,7 +19982,7 @@ class GeneralName extends AsnData {
|
|
|
19941
19982
|
case IP:
|
|
19942
19983
|
case REGISTERED_ID:
|
|
19943
19984
|
case UPN:
|
|
19944
|
-
case
|
|
19985
|
+
case URL2:
|
|
19945
19986
|
type = this.type.toUpperCase();
|
|
19946
19987
|
break;
|
|
19947
19988
|
case EMAIL:
|
|
@@ -21431,16 +21472,313 @@ async function stamp(keys, body) {
|
|
|
21431
21472
|
}
|
|
21432
21473
|
}
|
|
21433
21474
|
|
|
21475
|
+
// src/internal/version.ts
|
|
21476
|
+
var SDK_VERSION = "0.2.0";
|
|
21477
|
+
|
|
21478
|
+
// src/signing/unsigned-payload.ts
|
|
21479
|
+
var UNSIGNED_PAYLOAD_PATH = "/v1/accounts/wallets/unsigned-payload";
|
|
21480
|
+
var CREATE_WALLET_ACCOUNTS_ACTIVITY_TYPE = "ACTIVITY_TYPE_CREATE_WALLET_ACCOUNTS";
|
|
21481
|
+
var ETHEREUM_ADDRESS_FORMAT = "ADDRESS_FORMAT_ETHEREUM";
|
|
21482
|
+
var UNSIGNED_PAYLOAD_TIMEOUT_MS = 1e4;
|
|
21483
|
+
var LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
21484
|
+
var MAX_FAILURE_CHAIN_DEPTH = 10;
|
|
21485
|
+
var PRODUCTION_API_BASE_URL = "https://api.tesser.xyz";
|
|
21486
|
+
var SANDBOX_API_BASE_URL = "https://api-sandbox.tesser.xyz";
|
|
21487
|
+
var DEFAULT_API_BASE_URLS = [PRODUCTION_API_BASE_URL, SANDBOX_API_BASE_URL];
|
|
21488
|
+
var STAMP_REJECTED_ERROR_CODE = "signing-4010";
|
|
21489
|
+
function createTesserApiConnection({
|
|
21490
|
+
apiBaseUrl,
|
|
21491
|
+
fetch: injectedFetch,
|
|
21492
|
+
defaultBaseUrls = DEFAULT_API_BASE_URLS
|
|
21493
|
+
}) {
|
|
21494
|
+
return {
|
|
21495
|
+
fetch: resolveFetchImplementation(injectedFetch),
|
|
21496
|
+
defaultBaseUrls,
|
|
21497
|
+
resolvedBaseUrl: resolveExplicitApiBaseUrl(apiBaseUrl)
|
|
21498
|
+
};
|
|
21499
|
+
}
|
|
21500
|
+
function resolveExplicitApiBaseUrl(apiBaseUrl) {
|
|
21501
|
+
if (apiBaseUrl === undefined) {
|
|
21502
|
+
return;
|
|
21503
|
+
}
|
|
21504
|
+
return normalizeApiBaseUrl(apiBaseUrl);
|
|
21505
|
+
}
|
|
21506
|
+
function resolveFetchImplementation(injectedFetch) {
|
|
21507
|
+
if (injectedFetch !== undefined) {
|
|
21508
|
+
return injectedFetch;
|
|
21509
|
+
}
|
|
21510
|
+
return globalThis.fetch;
|
|
21511
|
+
}
|
|
21512
|
+
function normalizeApiBaseUrl(apiBaseUrl) {
|
|
21513
|
+
if (typeof apiBaseUrl !== "string") {
|
|
21514
|
+
throw invalidApiBaseUrlError();
|
|
21515
|
+
}
|
|
21516
|
+
const withoutTrailingSlash = removeSuffix(apiBaseUrl.trim(), "/");
|
|
21517
|
+
const normalized = removeSuffix(removeSuffix(withoutTrailingSlash, "/v1"), "/");
|
|
21518
|
+
if (!isHttpsOrLoopbackHttpUrl(normalized)) {
|
|
21519
|
+
throw invalidApiBaseUrlError();
|
|
21520
|
+
}
|
|
21521
|
+
return normalized;
|
|
21522
|
+
}
|
|
21523
|
+
function invalidApiBaseUrlError() {
|
|
21524
|
+
return new TesserConfigError("LocalSigner: apiBaseUrl must be an https URL, for example https://api.tesser.xyz");
|
|
21525
|
+
}
|
|
21526
|
+
function removeSuffix(value, suffix) {
|
|
21527
|
+
if (value.endsWith(suffix)) {
|
|
21528
|
+
return value.slice(0, value.length - suffix.length);
|
|
21529
|
+
}
|
|
21530
|
+
return value;
|
|
21531
|
+
}
|
|
21532
|
+
function isHttpsOrLoopbackHttpUrl(value) {
|
|
21533
|
+
const url = parseUrl(value);
|
|
21534
|
+
if (url === undefined || url.hostname === "") {
|
|
21535
|
+
return false;
|
|
21536
|
+
}
|
|
21537
|
+
if (url.protocol === "https:") {
|
|
21538
|
+
return true;
|
|
21539
|
+
}
|
|
21540
|
+
return url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname.toLowerCase());
|
|
21541
|
+
}
|
|
21542
|
+
function parseUrl(value) {
|
|
21543
|
+
try {
|
|
21544
|
+
return new URL(value);
|
|
21545
|
+
} catch {
|
|
21546
|
+
return;
|
|
21547
|
+
}
|
|
21548
|
+
}
|
|
21549
|
+
async function fetchCheckedCreateWalletAccountsBody(options) {
|
|
21550
|
+
const { api, signing, params } = options;
|
|
21551
|
+
if (api.resolvedBaseUrl !== undefined) {
|
|
21552
|
+
return fetchCheckedBodyFrom(api.resolvedBaseUrl, api.fetch, signing, params);
|
|
21553
|
+
}
|
|
21554
|
+
return detectEnvironmentAndFetchCheckedBody({ api, signing, params });
|
|
21555
|
+
}
|
|
21556
|
+
async function detectEnvironmentAndFetchCheckedBody(options) {
|
|
21557
|
+
const { api, signing, params } = options;
|
|
21558
|
+
let firstStampRejection;
|
|
21559
|
+
for (const baseUrl of api.defaultBaseUrls) {
|
|
21560
|
+
try {
|
|
21561
|
+
const body = await fetchCheckedBodyFrom(baseUrl, api.fetch, signing, params);
|
|
21562
|
+
api.resolvedBaseUrl = baseUrl;
|
|
21563
|
+
return body;
|
|
21564
|
+
} catch (error) {
|
|
21565
|
+
if (!isStampRejected(error)) {
|
|
21566
|
+
throw error;
|
|
21567
|
+
}
|
|
21568
|
+
firstStampRejection ??= error;
|
|
21569
|
+
}
|
|
21570
|
+
}
|
|
21571
|
+
throw firstStampRejection;
|
|
21572
|
+
}
|
|
21573
|
+
function isStampRejected(error) {
|
|
21574
|
+
return error instanceof TesserAPIError && error.status === 401 && error.errorCode === STAMP_REJECTED_ERROR_CODE;
|
|
21575
|
+
}
|
|
21576
|
+
async function fetchCheckedBodyFrom(baseUrl, fetchImplementation, signing, params) {
|
|
21577
|
+
const query = buildUnsignedPayloadQuery(signing, params);
|
|
21578
|
+
const stamped = await stamp({ publicKey: signing.publicKey, privateKey: signing.privateKey }, buildCanonicalUnsignedPayloadMessage(query));
|
|
21579
|
+
const response = await sendUnsignedPayloadRequest(fetchImplementation, buildUnsignedPayloadUrl(baseUrl, query), stamped);
|
|
21580
|
+
const body = parseUnsignedPayloadBody(response);
|
|
21581
|
+
assertUnsignedPayloadMatchesRequest(body, signing, params);
|
|
21582
|
+
return body;
|
|
21583
|
+
}
|
|
21584
|
+
function buildUnsignedPayloadQuery(signing, params) {
|
|
21585
|
+
return {
|
|
21586
|
+
organizationId: signing.enclaveId,
|
|
21587
|
+
name: params.name,
|
|
21588
|
+
type: params.type,
|
|
21589
|
+
timestampMs: String(Date.now())
|
|
21590
|
+
};
|
|
21591
|
+
}
|
|
21592
|
+
function buildCanonicalUnsignedPayloadMessage(query) {
|
|
21593
|
+
return JSON.stringify({
|
|
21594
|
+
organizationId: query.organizationId,
|
|
21595
|
+
name: query.name,
|
|
21596
|
+
type: query.type,
|
|
21597
|
+
timestampMs: query.timestampMs
|
|
21598
|
+
});
|
|
21599
|
+
}
|
|
21600
|
+
function buildUnsignedPayloadUrl(baseUrl, query) {
|
|
21601
|
+
const queryEntries = [
|
|
21602
|
+
["organizationId", query.organizationId],
|
|
21603
|
+
["name", query.name],
|
|
21604
|
+
["type", query.type],
|
|
21605
|
+
["timestampMs", query.timestampMs]
|
|
21606
|
+
];
|
|
21607
|
+
const queryString = queryEntries.map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("&");
|
|
21608
|
+
return `${baseUrl}${UNSIGNED_PAYLOAD_PATH}?${queryString}`;
|
|
21609
|
+
}
|
|
21610
|
+
async function sendUnsignedPayloadRequest(fetchImplementation, url, stamped) {
|
|
21611
|
+
try {
|
|
21612
|
+
const response = await fetchImplementation(url, {
|
|
21613
|
+
method: "GET",
|
|
21614
|
+
headers: {
|
|
21615
|
+
Accept: "application/json",
|
|
21616
|
+
[stamped.stampHeaderName]: stamped.stampHeaderValue,
|
|
21617
|
+
"User-Agent": `tesser-sdk-ts/${SDK_VERSION}`
|
|
21618
|
+
},
|
|
21619
|
+
signal: AbortSignal.timeout(UNSIGNED_PAYLOAD_TIMEOUT_MS)
|
|
21620
|
+
});
|
|
21621
|
+
const text = await response.text();
|
|
21622
|
+
return { status: response.status, headers: response.headers, text };
|
|
21623
|
+
} catch (cause) {
|
|
21624
|
+
throw toTransportError(cause);
|
|
21625
|
+
}
|
|
21626
|
+
}
|
|
21627
|
+
function toTransportError(cause) {
|
|
21628
|
+
if (isTimeoutFailure(cause)) {
|
|
21629
|
+
return new TesserTimeoutError(`Unsigned payload request timed out after ${UNSIGNED_PAYLOAD_TIMEOUT_MS} ms`, { cause });
|
|
21630
|
+
}
|
|
21631
|
+
return new TesserConnectionError(`Unsigned payload request failed: ${describeTransportFailure(cause)}`, { cause });
|
|
21632
|
+
}
|
|
21633
|
+
function isTimeoutFailure(cause) {
|
|
21634
|
+
return readErrorName(cause) === "TimeoutError";
|
|
21635
|
+
}
|
|
21636
|
+
function describeTransportFailure(cause) {
|
|
21637
|
+
const failureChain = collectFailureChain(cause);
|
|
21638
|
+
const messages = failureChain.map(readErrorMessage).filter(isNonBlank);
|
|
21639
|
+
if (messages.length > 0) {
|
|
21640
|
+
return [...new Set(messages)].join(": ");
|
|
21641
|
+
}
|
|
21642
|
+
const names2 = failureChain.map(readErrorName).filter(isNonBlank);
|
|
21643
|
+
return names2[names2.length - 1] ?? "Unknown transport failure";
|
|
21644
|
+
}
|
|
21645
|
+
function collectFailureChain(cause) {
|
|
21646
|
+
const failureChain = [];
|
|
21647
|
+
let current = cause;
|
|
21648
|
+
while (isObject(current) && failureChain.length < MAX_FAILURE_CHAIN_DEPTH) {
|
|
21649
|
+
if (failureChain.includes(current)) {
|
|
21650
|
+
break;
|
|
21651
|
+
}
|
|
21652
|
+
failureChain.push(current);
|
|
21653
|
+
current = current.cause;
|
|
21654
|
+
}
|
|
21655
|
+
return failureChain;
|
|
21656
|
+
}
|
|
21657
|
+
function readErrorMessage(failure) {
|
|
21658
|
+
return readStringField(failure, "message");
|
|
21659
|
+
}
|
|
21660
|
+
function readErrorName(failure) {
|
|
21661
|
+
return readStringField(failure, "name");
|
|
21662
|
+
}
|
|
21663
|
+
function readStringField(source, key) {
|
|
21664
|
+
if (!isObject(source)) {
|
|
21665
|
+
return;
|
|
21666
|
+
}
|
|
21667
|
+
const value = source[key];
|
|
21668
|
+
if (typeof value !== "string") {
|
|
21669
|
+
return;
|
|
21670
|
+
}
|
|
21671
|
+
return value;
|
|
21672
|
+
}
|
|
21673
|
+
function isNonBlank(value) {
|
|
21674
|
+
return value !== undefined && value.trim() !== "";
|
|
21675
|
+
}
|
|
21676
|
+
function isObject(value) {
|
|
21677
|
+
return typeof value === "object" && value !== null;
|
|
21678
|
+
}
|
|
21679
|
+
function readGatewayRequestId(headers) {
|
|
21680
|
+
return headers.get("zp-rid") ?? headers.get("x-request-id") ?? undefined;
|
|
21681
|
+
}
|
|
21682
|
+
function parseUnsignedPayloadBody(response) {
|
|
21683
|
+
if (response.status < 200 || response.status > 299) {
|
|
21684
|
+
throw buildUnsignedPayloadApiError(response);
|
|
21685
|
+
}
|
|
21686
|
+
const body = readResponseDataBody(response.text);
|
|
21687
|
+
if (body === undefined) {
|
|
21688
|
+
throw new TesserAPIError("Unsigned payload response is not valid: data.body is missing", buildApiErrorOptions(response, []));
|
|
21689
|
+
}
|
|
21690
|
+
return body;
|
|
21691
|
+
}
|
|
21692
|
+
function readResponseDataBody(text) {
|
|
21693
|
+
const envelope = parseJsonObject(text);
|
|
21694
|
+
if (envelope === undefined || !isObject(envelope.data)) {
|
|
21695
|
+
return;
|
|
21696
|
+
}
|
|
21697
|
+
return readStringField(envelope.data, "body");
|
|
21698
|
+
}
|
|
21699
|
+
function buildUnsignedPayloadApiError(response) {
|
|
21700
|
+
return new TesserAPIError(`Unsigned payload request returned HTTP ${response.status}: ${response.text}`, buildApiErrorOptions(response, parseErrorDetails(response.text)));
|
|
21701
|
+
}
|
|
21702
|
+
function buildApiErrorOptions(response, errors) {
|
|
21703
|
+
const requestId = readGatewayRequestId(response.headers);
|
|
21704
|
+
if (requestId === undefined) {
|
|
21705
|
+
return { status: response.status, headers: response.headers, errors };
|
|
21706
|
+
}
|
|
21707
|
+
return { status: response.status, headers: response.headers, requestId, errors };
|
|
21708
|
+
}
|
|
21709
|
+
function parseErrorDetails(text) {
|
|
21710
|
+
const envelope = parseJsonObject(text);
|
|
21711
|
+
if (envelope === undefined || !Array.isArray(envelope.errors)) {
|
|
21712
|
+
return [];
|
|
21713
|
+
}
|
|
21714
|
+
return envelope.errors.flatMap(toErrorDetail);
|
|
21715
|
+
}
|
|
21716
|
+
function toErrorDetail(entry) {
|
|
21717
|
+
const errorCode = readStringField(entry, "error_code");
|
|
21718
|
+
const errorMessage = readStringField(entry, "error_message");
|
|
21719
|
+
if (errorCode === undefined || errorMessage === undefined) {
|
|
21720
|
+
return [];
|
|
21721
|
+
}
|
|
21722
|
+
const uiMessage = readStringField(entry, "ui_message");
|
|
21723
|
+
if (uiMessage === undefined) {
|
|
21724
|
+
return [{ errorCode, errorMessage }];
|
|
21725
|
+
}
|
|
21726
|
+
return [{ errorCode, errorMessage, uiMessage }];
|
|
21727
|
+
}
|
|
21728
|
+
function parseJsonObject(text) {
|
|
21729
|
+
try {
|
|
21730
|
+
const parsed = JSON.parse(text);
|
|
21731
|
+
if (isObject(parsed) && !Array.isArray(parsed)) {
|
|
21732
|
+
return parsed;
|
|
21733
|
+
}
|
|
21734
|
+
return;
|
|
21735
|
+
} catch {
|
|
21736
|
+
return;
|
|
21737
|
+
}
|
|
21738
|
+
}
|
|
21739
|
+
function assertUnsignedPayloadMatchesRequest(body, signing, params) {
|
|
21740
|
+
const payload = parseJsonObject(body);
|
|
21741
|
+
if (payload === undefined) {
|
|
21742
|
+
throw rejectUnsignedPayload("body is not a JSON object");
|
|
21743
|
+
}
|
|
21744
|
+
if (readStringField(payload, "type") !== CREATE_WALLET_ACCOUNTS_ACTIVITY_TYPE) {
|
|
21745
|
+
throw rejectUnsignedPayload(`type is not ${CREATE_WALLET_ACCOUNTS_ACTIVITY_TYPE}`);
|
|
21746
|
+
}
|
|
21747
|
+
if (readStringField(payload, "organizationId") !== signing.enclaveId) {
|
|
21748
|
+
throw rejectUnsignedPayload("organizationId does not match the signing enclaveId");
|
|
21749
|
+
}
|
|
21750
|
+
const account = readSingleAccount(payload);
|
|
21751
|
+
if (readStringField(account, "name") !== params.name) {
|
|
21752
|
+
throw rejectUnsignedPayload("account name does not match the requested wallet name");
|
|
21753
|
+
}
|
|
21754
|
+
if (readStringField(account, "addressFormat") !== ETHEREUM_ADDRESS_FORMAT) {
|
|
21755
|
+
throw rejectUnsignedPayload(`account addressFormat is not ${ETHEREUM_ADDRESS_FORMAT}`);
|
|
21756
|
+
}
|
|
21757
|
+
}
|
|
21758
|
+
function readSingleAccount(payload) {
|
|
21759
|
+
const parameters = payload.parameters;
|
|
21760
|
+
if (!isObject(parameters)) {
|
|
21761
|
+
throw rejectUnsignedPayload("parameters is missing");
|
|
21762
|
+
}
|
|
21763
|
+
const accounts = parameters.accounts;
|
|
21764
|
+
if (!Array.isArray(accounts)) {
|
|
21765
|
+
throw rejectUnsignedPayload("parameters.accounts is missing");
|
|
21766
|
+
}
|
|
21767
|
+
if (accounts.length !== 1) {
|
|
21768
|
+
throw rejectUnsignedPayload(`expected exactly one account, got ${accounts.length}`);
|
|
21769
|
+
}
|
|
21770
|
+
const account = accounts[0];
|
|
21771
|
+
if (!isObject(account) || Array.isArray(account)) {
|
|
21772
|
+
throw rejectUnsignedPayload("account is not a JSON object");
|
|
21773
|
+
}
|
|
21774
|
+
return account;
|
|
21775
|
+
}
|
|
21776
|
+
function rejectUnsignedPayload(reason) {
|
|
21777
|
+
return new TesserSigningError(`Unsigned payload rejected: ${reason}`);
|
|
21778
|
+
}
|
|
21779
|
+
|
|
21434
21780
|
// src/signing/create-wallet.ts
|
|
21435
21781
|
var WALLET_TYPE_ACCOUNTS = {
|
|
21436
|
-
stablecoin_ethereum: [
|
|
21437
|
-
{
|
|
21438
|
-
curve: "CURVE_SECP256K1",
|
|
21439
|
-
pathFormat: "PATH_FORMAT_BIP32",
|
|
21440
|
-
path: "m/44'/60'/0'/0/0",
|
|
21441
|
-
addressFormat: "ADDRESS_FORMAT_ETHEREUM"
|
|
21442
|
-
}
|
|
21443
|
-
],
|
|
21444
21782
|
stablecoin_solana: [
|
|
21445
21783
|
{
|
|
21446
21784
|
curve: "CURVE_ED25519",
|
|
@@ -21458,21 +21796,16 @@ var WALLET_TYPE_ACCOUNTS = {
|
|
|
21458
21796
|
}
|
|
21459
21797
|
]
|
|
21460
21798
|
};
|
|
21461
|
-
var VALID_WALLET_TYPES =
|
|
21462
|
-
|
|
21799
|
+
var VALID_WALLET_TYPES = [
|
|
21800
|
+
"stablecoin_ethereum",
|
|
21801
|
+
"stablecoin_solana",
|
|
21802
|
+
"stablecoin_stellar"
|
|
21803
|
+
];
|
|
21804
|
+
async function signCreateWallet(signing, params, api) {
|
|
21463
21805
|
if (!VALID_WALLET_TYPES.includes(params.type)) {
|
|
21464
21806
|
throw new TesserConfigError(`signCreateWallet: type '${String(params.type)}' is not a valid WalletType. ` + `Valid values: ${VALID_WALLET_TYPES.join(", ")}.`);
|
|
21465
21807
|
}
|
|
21466
|
-
const
|
|
21467
|
-
const body = JSON.stringify({
|
|
21468
|
-
type: "ACTIVITY_TYPE_CREATE_WALLET",
|
|
21469
|
-
timestampMs: String(Date.now()),
|
|
21470
|
-
organizationId: signing.enclaveId,
|
|
21471
|
-
parameters: {
|
|
21472
|
-
walletName: params.name,
|
|
21473
|
-
accounts
|
|
21474
|
-
}
|
|
21475
|
-
});
|
|
21808
|
+
const body = await resolveCreateWalletBody({ signing, params, api });
|
|
21476
21809
|
const result = await stamp({ publicKey: signing.publicKey, privateKey: signing.privateKey }, body);
|
|
21477
21810
|
const signature = Buffer.from(JSON.stringify({ body, stamp: result.stampHeaderValue })).toString("base64");
|
|
21478
21811
|
return {
|
|
@@ -21484,6 +21817,28 @@ async function signCreateWallet(signing, params) {
|
|
|
21484
21817
|
}
|
|
21485
21818
|
};
|
|
21486
21819
|
}
|
|
21820
|
+
async function resolveCreateWalletBody(options) {
|
|
21821
|
+
const { signing, params, api } = options;
|
|
21822
|
+
if (params.type === "stablecoin_ethereum") {
|
|
21823
|
+
return fetchCheckedCreateWalletAccountsBody({ api, signing, params });
|
|
21824
|
+
}
|
|
21825
|
+
return buildCreateWalletBody({
|
|
21826
|
+
enclaveId: signing.enclaveId,
|
|
21827
|
+
name: params.name,
|
|
21828
|
+
type: params.type
|
|
21829
|
+
});
|
|
21830
|
+
}
|
|
21831
|
+
function buildCreateWalletBody(options) {
|
|
21832
|
+
return JSON.stringify({
|
|
21833
|
+
type: "ACTIVITY_TYPE_CREATE_WALLET",
|
|
21834
|
+
timestampMs: String(Date.now()),
|
|
21835
|
+
organizationId: options.enclaveId,
|
|
21836
|
+
parameters: {
|
|
21837
|
+
walletName: options.name,
|
|
21838
|
+
accounts: WALLET_TYPE_ACCOUNTS[options.type]
|
|
21839
|
+
}
|
|
21840
|
+
});
|
|
21841
|
+
}
|
|
21487
21842
|
|
|
21488
21843
|
// src/signing/network-type.ts
|
|
21489
21844
|
var NETWORK_TO_TURNKEY_TYPE = {
|
|
@@ -21493,7 +21848,9 @@ var NETWORK_TO_TURNKEY_TYPE = {
|
|
|
21493
21848
|
ETHEREUM_SEPOLIA: "TRANSACTION_TYPE_ETHEREUM",
|
|
21494
21849
|
POLYGON: "TRANSACTION_TYPE_ETHEREUM",
|
|
21495
21850
|
POLYGON_AMOY: "TRANSACTION_TYPE_ETHEREUM",
|
|
21496
|
-
SOLANA: "TRANSACTION_TYPE_SOLANA"
|
|
21851
|
+
SOLANA: "TRANSACTION_TYPE_SOLANA",
|
|
21852
|
+
TEMPO: "TRANSACTION_TYPE_TEMPO",
|
|
21853
|
+
TEMPO_MODERATO: "TRANSACTION_TYPE_TEMPO"
|
|
21497
21854
|
};
|
|
21498
21855
|
function networkToTurnkeyType(network) {
|
|
21499
21856
|
const turnkeyType = NETWORK_TO_TURNKEY_TYPE[network];
|
|
@@ -21534,11 +21891,13 @@ async function signStep(signing, step) {
|
|
|
21534
21891
|
class LocalSigner {
|
|
21535
21892
|
signing;
|
|
21536
21893
|
#privateKey;
|
|
21894
|
+
#api;
|
|
21537
21895
|
constructor(options) {
|
|
21538
21896
|
const s = options.signing;
|
|
21539
21897
|
if (!s || !s.publicKey || !s.privateKey || !s.enclaveId) {
|
|
21540
21898
|
throw new TesserConfigError("LocalSigner: signing.publicKey, signing.privateKey, and signing.enclaveId are required");
|
|
21541
21899
|
}
|
|
21900
|
+
this.#api = createTesserApiConnection({ apiBaseUrl: options.apiBaseUrl, fetch: options.fetch });
|
|
21542
21901
|
this.signing = { publicKey: s.publicKey, enclaveId: s.enclaveId };
|
|
21543
21902
|
this.#privateKey = s.privateKey;
|
|
21544
21903
|
}
|
|
@@ -21547,7 +21906,7 @@ class LocalSigner {
|
|
|
21547
21906
|
publicKey: this.signing.publicKey,
|
|
21548
21907
|
privateKey: this.#privateKey,
|
|
21549
21908
|
enclaveId: this.signing.enclaveId
|
|
21550
|
-
}, params);
|
|
21909
|
+
}, params, this.#api);
|
|
21551
21910
|
}
|
|
21552
21911
|
signStep(step) {
|
|
21553
21912
|
return signStep({
|
|
@@ -21558,9 +21917,12 @@ class LocalSigner {
|
|
|
21558
21917
|
}
|
|
21559
21918
|
}
|
|
21560
21919
|
export {
|
|
21920
|
+
TesserTimeoutError,
|
|
21561
21921
|
TesserSigningError,
|
|
21562
21922
|
TesserError,
|
|
21923
|
+
TesserConnectionError,
|
|
21563
21924
|
TesserConfigError,
|
|
21925
|
+
TesserAPIError,
|
|
21564
21926
|
StampError,
|
|
21565
21927
|
LocalSigner
|
|
21566
21928
|
};
|
|
@@ -28,11 +28,11 @@ export interface TesserAPIErrorOptions {
|
|
|
28
28
|
errors?: readonly TesserErrorDetail[];
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Thrown
|
|
32
|
-
* `errors[]` envelope so consumers can branch on Tesser's
|
|
33
|
-
* codes (e.g. `accounts-3005`, `idempotency-1001`) rather
|
|
34
|
-
* alone. For responses that are not Tesser-shaped (e.g., a
|
|
35
|
-
* page), `errors` is the empty array.
|
|
31
|
+
* Thrown when Tesser answers with an error status or an invalid response.
|
|
32
|
+
* Carries the parsed `errors[]` envelope so consumers can branch on Tesser's
|
|
33
|
+
* documented error codes (e.g. `accounts-3005`, `idempotency-1001`) rather
|
|
34
|
+
* than HTTP status alone. For responses that are not Tesser-shaped (e.g., a
|
|
35
|
+
* Cloudflare 502 page), `errors` is the empty array.
|
|
36
36
|
*/
|
|
37
37
|
export declare class TesserAPIError extends TesserError {
|
|
38
38
|
readonly name: string;
|
package/dist/internal/types.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface StepForSigning {
|
|
|
34
34
|
unsignedTransaction: string;
|
|
35
35
|
/** On-chain address from GET /v1/accounts/{id} → crypto_wallet_address */
|
|
36
36
|
signWith: string;
|
|
37
|
-
/** Network identifier; one of the supported
|
|
37
|
+
/** Network identifier; one of the supported networks. */
|
|
38
38
|
network: SupportedNetwork;
|
|
39
39
|
}
|
|
40
40
|
export interface SignedStepResultMetadata {
|
|
@@ -49,4 +49,16 @@ export interface SignedStepResult {
|
|
|
49
49
|
}
|
|
50
50
|
export interface LocalSignerOptions {
|
|
51
51
|
signing: SigningConfig;
|
|
52
|
+
/**
|
|
53
|
+
* Optional Tesser API base URL. Leave it unset and the signer finds the environment
|
|
54
|
+
* your signing key belongs to, production or sandbox. When set, only this URL is used:
|
|
55
|
+
* `https://api.tesser.xyz` in production, `https://api-sandbox.tesser.xyz` in sandbox.
|
|
56
|
+
* A trailing `/` or `/v1` is removed.
|
|
57
|
+
*/
|
|
58
|
+
apiBaseUrl?: string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* `fetch` implementation used for requests to Tesser. Inject one to configure a
|
|
61
|
+
* proxy. Defaults to the global `fetch`.
|
|
62
|
+
*/
|
|
63
|
+
fetch?: typeof fetch;
|
|
52
64
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SDK_VERSION = "0.2.0";
|
package/dist/signer.d.ts
CHANGED
|
@@ -11,24 +11,41 @@ export interface PublicSigningInfo {
|
|
|
11
11
|
/**
|
|
12
12
|
* Produces locally-signed activity payloads for Tesser API operations.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* the addition is purely additive and does not break existing call sites.
|
|
14
|
+
* Supports `signCreateWallet` and `signStep`. Construction validates that the
|
|
15
|
+
* signing keys are non-blank and that `apiBaseUrl`, if set, is an https URL.
|
|
17
16
|
*
|
|
18
17
|
* Key handling: the private key is stored in an ECMA private field (`#privateKey`)
|
|
19
18
|
* and is not reachable via `signer.signing.privateKey` or any other public
|
|
20
19
|
* surface. `signer.signing` exposes only `{ publicKey, enclaveId }`, so logging
|
|
21
20
|
* `signer` or its `signing` property will not leak the private key.
|
|
22
21
|
*
|
|
23
|
-
* Thread-safety:
|
|
24
|
-
*
|
|
22
|
+
* Thread-safety: the only state is the environment remembered after the first
|
|
23
|
+
* Ethereum wallet request; a single LocalSigner may be shared across concurrent calls.
|
|
25
24
|
*/
|
|
26
25
|
export declare class LocalSigner {
|
|
27
26
|
#private;
|
|
28
27
|
readonly signing: PublicSigningInfo;
|
|
29
28
|
constructor(options: LocalSignerOptions);
|
|
30
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Signs a wallet-creation request.
|
|
31
|
+
*
|
|
32
|
+
* For `stablecoin_ethereum` the SDK first fetches the request to sign from the
|
|
33
|
+
* Tesser API and checks that it matches your parameters. Other wallet types are
|
|
34
|
+
* signed locally without a request to Tesser. The private key never leaves the
|
|
35
|
+
* process.
|
|
36
|
+
*
|
|
37
|
+
* @throws {TesserConfigError} if the wallet type is not valid.
|
|
38
|
+
* @throws {TesserSigningError} if signing fails or the request fetched from
|
|
39
|
+
* Tesser does not match your parameters.
|
|
40
|
+
* @throws {TesserAPIError} if Tesser answers with an error status or an
|
|
41
|
+
* invalid response. Safe to retry for status 429 or 5xx; other 4xx statuses
|
|
42
|
+
* fail the same way on retry. Without `apiBaseUrl`, if neither production nor
|
|
43
|
+
* sandbox accepts your signing key, the error from production is thrown.
|
|
44
|
+
* @throws {TesserConnectionError} if the request to Tesser fails at the
|
|
45
|
+
* network level. Safe to retry.
|
|
46
|
+
* @throws {TesserTimeoutError} if the request to Tesser times out. Safe to retry.
|
|
47
|
+
*/
|
|
31
48
|
signCreateWallet(params: CreateWalletParams): Promise<SignedResult>;
|
|
32
|
-
/**
|
|
49
|
+
/** Signs a single transfer step. The private key never leaves the process. */
|
|
33
50
|
signStep(step: StepForSigning): Promise<SignedStepResult>;
|
|
34
51
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { CreateWalletParams, SignedResult, SigningConfig } from '../internal/types.js';
|
|
2
|
-
|
|
2
|
+
import { type TesserApiConnection } from './unsigned-payload.js';
|
|
3
|
+
export declare function signCreateWallet(signing: SigningConfig, params: CreateWalletParams, api: TesserApiConnection): Promise<SignedResult>;
|
|
@@ -6,6 +6,8 @@ declare const NETWORK_TO_TURNKEY_TYPE: {
|
|
|
6
6
|
readonly POLYGON: "TRANSACTION_TYPE_ETHEREUM";
|
|
7
7
|
readonly POLYGON_AMOY: "TRANSACTION_TYPE_ETHEREUM";
|
|
8
8
|
readonly SOLANA: "TRANSACTION_TYPE_SOLANA";
|
|
9
|
+
readonly TEMPO: "TRANSACTION_TYPE_TEMPO";
|
|
10
|
+
readonly TEMPO_MODERATO: "TRANSACTION_TYPE_TEMPO";
|
|
9
11
|
};
|
|
10
12
|
export type SupportedNetwork = keyof typeof NETWORK_TO_TURNKEY_TYPE;
|
|
11
13
|
export declare function networkToTurnkeyType(network: string): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CreateWalletParams, SigningConfig } from '../internal/types.js';
|
|
2
|
+
export interface TesserApiConnection {
|
|
3
|
+
fetch: typeof fetch;
|
|
4
|
+
defaultBaseUrls: readonly string[];
|
|
5
|
+
resolvedBaseUrl: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare function createTesserApiConnection({ apiBaseUrl, fetch: injectedFetch, defaultBaseUrls, }: {
|
|
8
|
+
apiBaseUrl: unknown;
|
|
9
|
+
fetch: typeof fetch | undefined;
|
|
10
|
+
defaultBaseUrls?: readonly string[];
|
|
11
|
+
}): TesserApiConnection;
|
|
12
|
+
export declare function normalizeApiBaseUrl(apiBaseUrl: unknown): string;
|
|
13
|
+
export declare function fetchCheckedCreateWalletAccountsBody(options: {
|
|
14
|
+
api: TesserApiConnection;
|
|
15
|
+
signing: SigningConfig;
|
|
16
|
+
params: CreateWalletParams;
|
|
17
|
+
}): Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tesser-payments/sdk-ts",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "TypeScript SDK for Tesser API: sign signCreateWallet and signStep requests locally. No OAuth — bring your own client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -41,14 +41,17 @@
|
|
|
41
41
|
},
|
|
42
42
|
"overrides": {
|
|
43
43
|
"ws": "^8.21.0",
|
|
44
|
-
"vite": "^6.4.
|
|
44
|
+
"vite": "^6.4.5",
|
|
45
|
+
"postcss": "^8.5.18",
|
|
46
|
+
"source-map-js": "^1.2.2",
|
|
47
|
+
"zod": "^4.6.6"
|
|
45
48
|
},
|
|
46
49
|
"devDependencies": {
|
|
47
50
|
"@biomejs/biome": "^1.9",
|
|
48
51
|
"@tesser-payments/types": "*",
|
|
49
52
|
"@types/node": "^20",
|
|
50
53
|
"typescript": "^5.4",
|
|
51
|
-
"vitest": "^
|
|
54
|
+
"vitest": "^4.1.12"
|
|
52
55
|
},
|
|
53
56
|
"trustedDependencies": []
|
|
54
57
|
}
|