@1money/protocol-ts-sdk 2.1.0 → 2.1.1
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/AGENTS.md +115 -28
- package/CLAUDE.md +1 -77
- package/es/api/constants.d.ts +1 -1
- package/es/api/index.js +1 -1
- package/es/index.js +1 -1
- package/lib/api/constants.d.ts +1 -1
- package/lib/api/index.js +1 -1
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/skills/1money-protocol-sdk/SKILL.md +205 -0
- package/skills/1money-protocol-sdk/references/api-reference.md +188 -0
- package/skills/1money-protocol-sdk/references/client-and-errors.md +176 -0
- package/skills/1money-protocol-sdk/references/transactions.md +325 -0
- package/umd/1money-protocol-ts-sdk.min.js +1 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# API reference — read endpoints & response shapes
|
|
2
|
+
|
|
3
|
+
Every method below returns the SDK promise wrapper (see
|
|
4
|
+
`client-and-errors.md`). The value handed to `.success(cb)` / resolved by
|
|
5
|
+
`await` is the **decoded response body directly** — the shapes shown here are
|
|
6
|
+
exactly what you get.
|
|
7
|
+
|
|
8
|
+
All addresses, hashes, and large numbers are hex/decimal **strings**. `U256`
|
|
9
|
+
amounts are decimal strings (base units); `B256`/address values are `0x…` hex.
|
|
10
|
+
|
|
11
|
+
## Table of contents
|
|
12
|
+
- [Client construction](#client-construction)
|
|
13
|
+
- [chain](#chain)
|
|
14
|
+
- [accounts](#accounts)
|
|
15
|
+
- [tokens (read)](#tokens-read)
|
|
16
|
+
- [transactions (read)](#transactions-read)
|
|
17
|
+
- [checkpoints](#checkpoints)
|
|
18
|
+
- [Write endpoints (index)](#write-endpoints-index)
|
|
19
|
+
- [Constants](#constants)
|
|
20
|
+
|
|
21
|
+
## Client construction
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { api } from '@1money/protocol-ts-sdk';
|
|
25
|
+
|
|
26
|
+
const client = api(options?: {
|
|
27
|
+
network?: 'testnet' | 'mainnet' | 'local'; // default 'mainnet'
|
|
28
|
+
timeout?: number; // ms, default 10000
|
|
29
|
+
});
|
|
30
|
+
// → { accounts, checkpoints, tokens, transactions, chain }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Base URLs by network: mainnet `https://api.1money.network`, testnet
|
|
34
|
+
`https://api.testnet.1money.network`, local `http://localhost:18555`.
|
|
35
|
+
|
|
36
|
+
## chain
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
client.chain.getChainId() // → { chain_id: number }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## accounts
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
client.accounts.getNonce(address: string)
|
|
46
|
+
// → { nonce: number } next nonce to use for this sender
|
|
47
|
+
|
|
48
|
+
client.accounts.getBbNonce(address: string)
|
|
49
|
+
// → { bbnonce: number }
|
|
50
|
+
|
|
51
|
+
client.accounts.getTokenAccount(address: string, token: string)
|
|
52
|
+
// → { balance: string /* U256 base units */, nonce: number }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`getTokenAccount` is how you read a wallet's balance of a specific token. To
|
|
56
|
+
compute the associated token-account address offline, use `deriveTokenAddress`
|
|
57
|
+
(see `client-and-errors.md` → Utilities).
|
|
58
|
+
|
|
59
|
+
## tokens (read)
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
client.tokens.getTokenMetadata(token: string) // → MintInfo
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`MintInfo` (token-wide config and authorities):
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
interface MintInfo {
|
|
69
|
+
symbol: string;
|
|
70
|
+
master_authority: string;
|
|
71
|
+
master_mint_burn_authority: string;
|
|
72
|
+
mint_burn_authorities: { minter: string; allowance: string }[];
|
|
73
|
+
pause_authorities: string[];
|
|
74
|
+
list_authorities: string[];
|
|
75
|
+
black_list: string[];
|
|
76
|
+
white_list: string[];
|
|
77
|
+
metadata_update_authorities: string[];
|
|
78
|
+
bridge_mint_authorities: string[];
|
|
79
|
+
supply: string; // U256 base units
|
|
80
|
+
decimals: number;
|
|
81
|
+
is_paused: boolean;
|
|
82
|
+
is_private: boolean;
|
|
83
|
+
clawback_enabled: boolean;
|
|
84
|
+
meta: {
|
|
85
|
+
name: string;
|
|
86
|
+
uri: string;
|
|
87
|
+
additional_metadata: { key: string; value: string }[];
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## transactions (read)
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
client.transactions.getByHash(hash: string) // → Transaction
|
|
96
|
+
client.transactions.getReceiptByHash(hash: string) // → TransactionReceipt
|
|
97
|
+
client.transactions.getFinalizedByHash(hash: string) // → FinalizedTransactionReceipt
|
|
98
|
+
client.transactions.estimateFee(from, to, value, token) // → { fee: string }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`estimateFee(from: string, to: string, value: string, token: string)` — note the
|
|
102
|
+
argument order is **from, to, value, token** (value before to in the URL, but the
|
|
103
|
+
function signature is from/to/value/token).
|
|
104
|
+
|
|
105
|
+
`TransactionReceipt`:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
interface TransactionReceipt {
|
|
109
|
+
success: boolean; // did the tx succeed on-chain
|
|
110
|
+
transaction_hash: string;
|
|
111
|
+
fee_used: number;
|
|
112
|
+
from: string;
|
|
113
|
+
checkpoint_hash?: string;
|
|
114
|
+
checkpoint_number?: number;
|
|
115
|
+
to?: string;
|
|
116
|
+
token_address?: string;
|
|
117
|
+
}
|
|
118
|
+
// FinalizedTransactionReceipt extends it with:
|
|
119
|
+
// epoch: number; counter_signatures: { r; s; v }[]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`Transaction` is a **discriminated union** keyed by `transaction_type`
|
|
123
|
+
(`'TokenCreate' | 'TokenTransfer' | 'TokenMint' | 'TokenGrantAuthority' |
|
|
124
|
+
'TokenRevokeAuthority' | 'TokenBlacklistAccount' | 'TokenWhitelistAccount' |
|
|
125
|
+
'TokenBridgeAndMint' | 'TokenBurn' | 'TokenBurnAndBridge' | 'TokenClawback' |
|
|
126
|
+
'TokenCloseAccount' | 'TokenPause' | 'TokenUnpause' | 'TokenUpdateMetadata' |
|
|
127
|
+
'Raw'`). All variants share `hash`, `chain_id`, `from`, `nonce`, `signature`,
|
|
128
|
+
plus optional `checkpoint_*`/`transaction_index` and an optional `memo` (present
|
|
129
|
+
only for V2/memo-bearing txs); each carries a `data` object specific to its type.
|
|
130
|
+
Narrow on `transaction_type` before reading `data`.
|
|
131
|
+
|
|
132
|
+
## checkpoints
|
|
133
|
+
|
|
134
|
+
(1Money's term for blocks.)
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
client.checkpoints.getNumber() // → { number: number }
|
|
138
|
+
client.checkpoints.getByHash(hash, full = false) // → Checkpoint
|
|
139
|
+
client.checkpoints.getByNumber(number, full = false) // number: number | string → Checkpoint
|
|
140
|
+
client.checkpoints.getReceiptsByNumber(number: number | string) // → TransactionReceipt[]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`full` controls whether `transactions` comes back as full `Transaction[]`
|
|
144
|
+
(`true`) or just an array of hashes (`false`, default).
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
interface Checkpoint {
|
|
148
|
+
hash: string; parent_hash: string;
|
|
149
|
+
state_root: string; transactions_root: string; receipts_root: string;
|
|
150
|
+
number: number; timestamp: number;
|
|
151
|
+
size?: number;
|
|
152
|
+
transactions: Transaction[] | string[]; // hashes unless full=true
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Write endpoints (index)
|
|
157
|
+
|
|
158
|
+
These take a **signed payload** built via `TransactionBuilder` — see
|
|
159
|
+
`transactions.md`. Listed here only so you pick the right one.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
client.transactions.payment(payload) // → { hash }
|
|
163
|
+
client.tokens.issueToken(payload) // → { hash, token }
|
|
164
|
+
client.tokens.mintToken(payload) // → { hash }
|
|
165
|
+
client.tokens.burnToken(payload) // → { hash }
|
|
166
|
+
client.tokens.grantAuthority(payload) // → { hash }
|
|
167
|
+
client.tokens.manageBlacklist(payload) // → { hash }
|
|
168
|
+
client.tokens.manageWhitelist(payload) // → { hash }
|
|
169
|
+
client.tokens.pauseToken(payload) // → { hash }
|
|
170
|
+
client.tokens.updateMetadata(payload) // → { hash }
|
|
171
|
+
client.tokens.bridgeAndMint(payload) // → { hash }
|
|
172
|
+
client.tokens.burnAndBridge(payload) // → { hash }
|
|
173
|
+
client.tokens.clawbackToken(payload) // → { hash }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Constants
|
|
177
|
+
|
|
178
|
+
The chain ids are defined internally as `CHAIN_IDS` (mainnet `21210`, testnet
|
|
179
|
+
`1212101`, local `1212101`) but are **not re-exported** from any public entry
|
|
180
|
+
point (`.`, `/api`, `/client`, `/utils`). Don't try to import them — fetch the
|
|
181
|
+
live value at runtime:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
const { chain_id } = await client.chain.getChainId();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
If you genuinely need a hardcoded id for an offline/test path, define your own
|
|
188
|
+
constant rather than relying on an unexported one.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Client, error handling, config & utilities
|
|
2
|
+
|
|
3
|
+
## The promise wrapper in depth
|
|
4
|
+
|
|
5
|
+
Every API method returns an object that is **both** a `Promise` and a builder of
|
|
6
|
+
handler chains. You consume it one of two ways.
|
|
7
|
+
|
|
8
|
+
### Await style
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
try {
|
|
12
|
+
const res = await client.accounts.getNonce(addr); // resolves with the body
|
|
13
|
+
console.log(res.nonce);
|
|
14
|
+
} catch (err) {
|
|
15
|
+
// err is a ParsedError (see below) — thrown only when NO error handler chained
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Chain style
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
client.accounts.getNonce(addr)
|
|
23
|
+
.success(res => res.nonce) // res is the decoded body, not wrapped
|
|
24
|
+
.timeout(err => { /* fired specifically on configured timeout */ })
|
|
25
|
+
.error(err => { /* any other error */ });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Handlers and what they fire on:
|
|
29
|
+
|
|
30
|
+
| Handler | Fires when |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| `.success(cb)` | request succeeded (HTTP 200 by `api()`'s rule) |
|
|
33
|
+
| `.failure(cb)` | a 2xx response that fails the success rule (rare — see note) |
|
|
34
|
+
| `.error(cb)` | network error, non-2xx HTTP status, or a throw in another handler |
|
|
35
|
+
| `.timeout(cb)` | the configured `timeout` elapsed (request is aborted) |
|
|
36
|
+
| `.login(cb)` | auth-required responses (HTTP 401 / `code === 401`) |
|
|
37
|
+
| `.rest(cb, scope?)` | catch-all for whichever cases you didn't handle |
|
|
38
|
+
|
|
39
|
+
> `api()` sets the success rule to `status === 200`. Since axios rejects non-2xx
|
|
40
|
+
> responses by default, most server-side failures (4xx/5xx) arrive at `.error`,
|
|
41
|
+
> not `.failure`. `.failure` only fires for a resolved response that isn't 200
|
|
42
|
+
> and isn't a login — uncommon in practice. For consumer code, handling
|
|
43
|
+
> `.success` + `.error` (or `try/catch`) covers the real cases.
|
|
44
|
+
|
|
45
|
+
Important behavioral notes:
|
|
46
|
+
|
|
47
|
+
- **Return value of the chain.** Awaiting with no handlers returns the raw body
|
|
48
|
+
(e.g. `await client.chain.getChainId()` → `{ chain_id }`) — this is the cleanest
|
|
49
|
+
idiom for the common case. Attaching `.success(cb)` instead makes the awaited
|
|
50
|
+
value equal to `cb`'s return, which is useful when you want to transform or map
|
|
51
|
+
the body inline.
|
|
52
|
+
- **Errors resolve vs. throw.** If you attach `.error()` (or `.rest()` covering
|
|
53
|
+
errors), the awaited chain **resolves** with the handler's return instead of
|
|
54
|
+
throwing. With no error handler, `await` **throws**. Don't half-mix: either
|
|
55
|
+
handle errors in the chain, or `try/catch` the await — not an awkward both.
|
|
56
|
+
- **`.rest(cb, scope)`** lets you target a subset, e.g. handle only timeout +
|
|
57
|
+
error with one callback: `.rest(cb, ['timeout', 'error'])`. An empty scope, or
|
|
58
|
+
a scope whose cases were all already handled, warns and never fires.
|
|
59
|
+
|
|
60
|
+
### ParsedError shape
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
interface ParsedError<T extends string = string> {
|
|
64
|
+
name: T; // underlying error name (see timeout caveat below)
|
|
65
|
+
message: string;
|
|
66
|
+
stack: string;
|
|
67
|
+
status: number; // HTTP status, or 500 if none
|
|
68
|
+
data?: any; // server error body when present
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
> **Timeout discriminator caveat.** A timeout is typed `ParsedError<'timeout'>`,
|
|
73
|
+
> but that `'timeout'` is only the *type-level* tag — at runtime the object is a
|
|
74
|
+
> plain `Error('timeout')`, so `name === 'Error'` and **`message === 'timeout'`**.
|
|
75
|
+
> To detect a timeout in a `catch`, check `err.message === 'timeout'` (or just use
|
|
76
|
+
> the `.timeout()` handler), **not** `err.name === 'timeout'`.
|
|
77
|
+
|
|
78
|
+
## Configuration & custom headers
|
|
79
|
+
|
|
80
|
+
`api()` sets base URL, timeout, and the success rule globally. To add headers
|
|
81
|
+
(auth tokens, API keys) or override the base URL, use `setInitConfig` — headers
|
|
82
|
+
merge with the SDK's defaults rather than replacing them:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Import from the /client subpath — setInitConfig is NOT a named export of the
|
|
86
|
+
// package root (the README's root import is inaccurate). The default `client`
|
|
87
|
+
// object from the root also carries it as `client.setInitConfig(...)`.
|
|
88
|
+
import { setInitConfig } from '@1money/protocol-ts-sdk/client';
|
|
89
|
+
|
|
90
|
+
setInitConfig({
|
|
91
|
+
headers: { Authorization: 'Bearer <token>', 'X-API-Key': '<key>' },
|
|
92
|
+
// optional:
|
|
93
|
+
baseURL: 'https://api.custom-domain.com',
|
|
94
|
+
timeout: 10000,
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
> Config is **global/singleton** — it applies to all module references obtained
|
|
99
|
+
> from `api()`. Call `setInitConfig` once at startup (after `api()`), not
|
|
100
|
+
> per-request. There is no per-call header argument in the public methods.
|
|
101
|
+
|
|
102
|
+
## Timeouts & cancellation
|
|
103
|
+
|
|
104
|
+
Default timeout is 10s; override per client via `api({ timeout })` or globally
|
|
105
|
+
via `setInitConfig({ timeout })`. On timeout the request is **aborted** (via
|
|
106
|
+
`AbortController`) and `.timeout()` fires (or the awaited call throws a
|
|
107
|
+
`ParsedError<'timeout'>` if unhandled — runtime `message === 'timeout'`, see the
|
|
108
|
+
caveat under ParsedError shape).
|
|
109
|
+
|
|
110
|
+
The high-level module methods (`accounts.getNonce`, `transactions.payment`, …)
|
|
111
|
+
do **not** accept a per-call options/`signal` argument — they use fixed request
|
|
112
|
+
options internally. Caller-supplied cancellation is only available through the
|
|
113
|
+
low-level `get`/`post` exports from `@1money/protocol-ts-sdk/client`, which do
|
|
114
|
+
accept an options object with a `signal`; that signal is merged with the SDK's
|
|
115
|
+
internal timeout abort signal.
|
|
116
|
+
|
|
117
|
+
## Utilities
|
|
118
|
+
|
|
119
|
+
Imported from the package root (`@1money/protocol-ts-sdk`).
|
|
120
|
+
|
|
121
|
+
### deriveTokenAddress(walletAddress, mintAddress)
|
|
122
|
+
Compute a wallet's associated token-account address offline (keccak-based
|
|
123
|
+
derivation) — no network call.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { deriveTokenAddress } from '@1money/protocol-ts-sdk';
|
|
127
|
+
const tokenAccount = deriveTokenAddress(
|
|
128
|
+
'0xA634dfba8c7550550817898bC4820cD10888Aac5', // wallet
|
|
129
|
+
'0x8E9d1b45293e30EF38564582979195DD16A16E13', // mint
|
|
130
|
+
); // → '0x…'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### toHex(value)
|
|
134
|
+
Convert booleans, numbers/bigints, integer strings, byte arrays, or arbitrary
|
|
135
|
+
values to a `0x…` hex string.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { toHex } from '@1money/protocol-ts-sdk';
|
|
139
|
+
toHex(true); // '0x1' (minimal hex, not zero-padded — README's '0x01' is wrong)
|
|
140
|
+
toHex(123); // '0x7b'
|
|
141
|
+
toHex('hello'); // '0x68656c6c6f'
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### calcTxHash(payload, signature)
|
|
145
|
+
Compute a transaction hash from a raw payload array + `{ r, s, v }`. Rarely
|
|
146
|
+
needed directly — prefer `SignedTx.txHash` from the builder flow, which is
|
|
147
|
+
computed for you. Use `calcTxHash` only for low-level/verification work.
|
|
148
|
+
|
|
149
|
+
### validateMemo / MemoValidationError / Memo
|
|
150
|
+
For attaching a memo to a transaction (see `transactions.md` → Memo). `Memo` is
|
|
151
|
+
`{ type?, format?, data? }`. The builders call `validateMemo` for you when a memo
|
|
152
|
+
is present; call it yourself to pre-validate user input. It throws
|
|
153
|
+
`MemoValidationError` (with a `.code`) on oversize or illegal-character subfields.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { validateMemo, MemoValidationError } from '@1money/protocol-ts-sdk';
|
|
157
|
+
try {
|
|
158
|
+
validateMemo({ type: 'invoice', data: userInput });
|
|
159
|
+
} catch (e) {
|
|
160
|
+
if (e instanceof MemoValidationError) console.error(e.code, e.message);
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Deprecated: signMessage / encodePayload
|
|
165
|
+
`signMessage(payload, privateKey)` and `encodePayload(payload)` are legacy
|
|
166
|
+
`@deprecated` helpers retained for backward compatibility. **Do not use them for
|
|
167
|
+
new code** — `TransactionBuilder` + `createPrivateKeySigner` (see
|
|
168
|
+
`transactions.md`) is the supported, validated, malleability-safe path.
|
|
169
|
+
|
|
170
|
+
## Security checklist
|
|
171
|
+
|
|
172
|
+
- Load private keys from environment/secret managers; never hardcode or commit
|
|
173
|
+
them. Example value strings in docs are placeholders.
|
|
174
|
+
- Use `testnet` (or `local`) while developing; switch the network deliberately.
|
|
175
|
+
- Keep peer deps (`axios`, `viem`, `@ethereumjs/rlp`) within the package's
|
|
176
|
+
declared ranges; mismatches can break signing/encoding subtly.
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Building, signing & submitting transactions
|
|
2
|
+
|
|
3
|
+
Every write follows the same pipeline. Internalize this shape; the rest is just
|
|
4
|
+
filling in builder-specific fields.
|
|
5
|
+
|
|
6
|
+
```typescript
|
|
7
|
+
import { api, TransactionBuilder, createPrivateKeySigner } from '@1money/protocol-ts-sdk';
|
|
8
|
+
|
|
9
|
+
const client = api({ network: 'testnet' });
|
|
10
|
+
|
|
11
|
+
// 1. Inputs every tx needs. Awaiting directly returns the body; it throws on
|
|
12
|
+
// error (or attach .success/.error handlers — see client-and-errors.md).
|
|
13
|
+
const { chain_id } = await client.chain.getChainId();
|
|
14
|
+
const { nonce } = await client.accounts.getNonce(sender);
|
|
15
|
+
|
|
16
|
+
// 2. Build (validates fields, RLP-encodes, prepares the digest).
|
|
17
|
+
const prepared = TransactionBuilder.<type>({ chain_id, nonce, /* ...fields */ });
|
|
18
|
+
|
|
19
|
+
// 3. Sign → request body → submit to the matching endpoint.
|
|
20
|
+
const signed = await prepared.sign(createPrivateKeySigner(privateKey));
|
|
21
|
+
const res = await client.<module>.<method>(signed.toRequest());
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What the objects give you
|
|
25
|
+
|
|
26
|
+
`TransactionBuilder.<type>(...)` returns a **PreparedTx**:
|
|
27
|
+
- `prepared.signatureHash` — the 32-byte digest a signer must sign.
|
|
28
|
+
- `prepared.rlpBytes`, `prepared.unsigned`, `prepared.kind`.
|
|
29
|
+
- `await prepared.sign(signer)` → **SignedTx**.
|
|
30
|
+
- `prepared.attachSignature(signature)` → **SignedTx** (when you signed the
|
|
31
|
+
`signatureHash` elsewhere and just want to attach r/s/v).
|
|
32
|
+
|
|
33
|
+
`SignedTx`:
|
|
34
|
+
- `signed.toRequest()` — the request body (unsigned fields + `signature`) you
|
|
35
|
+
pass to the submit method.
|
|
36
|
+
- `signed.txHash` — the on-chain transaction hash (available before submit).
|
|
37
|
+
- `signed.signature` (`{ r, s, v }`), `signed.signatureHash`, `signed.unsigned`.
|
|
38
|
+
|
|
39
|
+
`createPrivateKeySigner(privateKey: \`0x${string}\`)` produces a `SignerAdapter`
|
|
40
|
+
that signs with low-S (required — high-S signatures are rejected on attach).
|
|
41
|
+
|
|
42
|
+
## Builder ↔ endpoint map
|
|
43
|
+
|
|
44
|
+
| `TransactionBuilder.…` | Submit `client.…` | Returns |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `payment` | `transactions.payment` | `{ hash }` |
|
|
47
|
+
| `tokenIssue` | `tokens.issueToken` | `{ hash, token }` |
|
|
48
|
+
| `tokenMint` | `tokens.mintToken` | `{ hash }` |
|
|
49
|
+
| `tokenBurn` | `tokens.burnToken` | `{ hash }` |
|
|
50
|
+
| `tokenAuthority` | `tokens.grantAuthority` | `{ hash }` |
|
|
51
|
+
| `tokenManageList` | `tokens.manageBlacklist` / `tokens.manageWhitelist` | `{ hash }` |
|
|
52
|
+
| `tokenPause` | `tokens.pauseToken` | `{ hash }` |
|
|
53
|
+
| `tokenMetadata` | `tokens.updateMetadata` | `{ hash }` |
|
|
54
|
+
| `tokenBridgeAndMint` | `tokens.bridgeAndMint` | `{ hash }` |
|
|
55
|
+
| `tokenBurnAndBridge` | `tokens.burnAndBridge` | `{ hash }` |
|
|
56
|
+
| `tokenClawback` | `tokens.clawbackToken` | `{ hash }` |
|
|
57
|
+
|
|
58
|
+
## Builder fields
|
|
59
|
+
|
|
60
|
+
All builders take `chain_id: number` and `nonce: number`. Fields below are the
|
|
61
|
+
*additional* ones. `value`/amount fields are **decimal strings in base units**.
|
|
62
|
+
Addresses are EIP-55 `0x…` strings.
|
|
63
|
+
|
|
64
|
+
**Every builder also accepts an optional `memo?: Memo`** (see [Memo](#memo-optional--v1-vs-v2-envelope) below). Omit it for the common case; the fields listed per builder are the required ones.
|
|
65
|
+
|
|
66
|
+
### payment → transactions.payment
|
|
67
|
+
```typescript
|
|
68
|
+
{ recipient: string; value: string; token: string }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### tokenIssue → tokens.issueToken
|
|
72
|
+
```typescript
|
|
73
|
+
{
|
|
74
|
+
symbol: string;
|
|
75
|
+
name: string;
|
|
76
|
+
decimals: number;
|
|
77
|
+
master_authority: string;
|
|
78
|
+
is_private: boolean;
|
|
79
|
+
clawback_enabled?: boolean; // default true
|
|
80
|
+
}
|
|
81
|
+
// response includes the new token's address: { hash, token }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### tokenMint → tokens.mintToken
|
|
85
|
+
```typescript
|
|
86
|
+
{ recipient: string; value: string; token: string }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### tokenBurn → tokens.burnToken
|
|
90
|
+
```typescript
|
|
91
|
+
{ value: string; token: string }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### tokenAuthority → tokens.grantAuthority
|
|
95
|
+
```typescript
|
|
96
|
+
import { AuthorityAction, AuthorityType } from '@1money/protocol-ts-sdk/api';
|
|
97
|
+
{
|
|
98
|
+
action: AuthorityAction; // Grant | Revoke
|
|
99
|
+
authority_type: AuthorityType; // see enum below
|
|
100
|
+
authority_address: string;
|
|
101
|
+
token: string;
|
|
102
|
+
value?: string; // e.g. mint/burn allowance
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### tokenManageList → tokens.manageBlacklist / tokens.manageWhitelist
|
|
107
|
+
```typescript
|
|
108
|
+
import { ManageListAction } from '@1money/protocol-ts-sdk/api';
|
|
109
|
+
{ action: ManageListAction; address: string; token: string } // Add | Remove
|
|
110
|
+
```
|
|
111
|
+
Build once, then submit to `manageBlacklist` *or* `manageWhitelist` — the payload
|
|
112
|
+
shape is identical; the endpoint decides which list.
|
|
113
|
+
|
|
114
|
+
### tokenPause → tokens.pauseToken
|
|
115
|
+
```typescript
|
|
116
|
+
import { PauseAction } from '@1money/protocol-ts-sdk/api';
|
|
117
|
+
{ action: PauseAction; token: string } // Pause | Unpause
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### tokenMetadata → tokens.updateMetadata
|
|
121
|
+
```typescript
|
|
122
|
+
{
|
|
123
|
+
name: string;
|
|
124
|
+
uri: string;
|
|
125
|
+
token: string;
|
|
126
|
+
additional_metadata: { key: string; value: string }[];
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### tokenBridgeAndMint → tokens.bridgeAndMint
|
|
131
|
+
```typescript
|
|
132
|
+
{
|
|
133
|
+
recipient: string;
|
|
134
|
+
value: string;
|
|
135
|
+
token: string;
|
|
136
|
+
source_chain_id: number;
|
|
137
|
+
source_tx_hash: string;
|
|
138
|
+
bridge_metadata: string;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### tokenBurnAndBridge → tokens.burnAndBridge
|
|
143
|
+
```typescript
|
|
144
|
+
{
|
|
145
|
+
sender: string;
|
|
146
|
+
value: string;
|
|
147
|
+
token: string;
|
|
148
|
+
destination_chain_id: number;
|
|
149
|
+
destination_address: string;
|
|
150
|
+
escrow_fee: string;
|
|
151
|
+
bridge_metadata: string;
|
|
152
|
+
bridge_param: string; // bytes as 0x… hex ('0x' for empty)
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### tokenClawback → tokens.clawbackToken
|
|
157
|
+
```typescript
|
|
158
|
+
{ token: string; from: string; recipient: string; value: string }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Memo (optional — V1 vs V2 envelope)
|
|
162
|
+
|
|
163
|
+
Any builder accepts an optional `memo` to attach a structured note. `Memo` is
|
|
164
|
+
imported from the package root (or `/utils`); all three subfields are optional:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import type { Memo } from '@1money/protocol-ts-sdk';
|
|
168
|
+
|
|
169
|
+
const prepared = TransactionBuilder.payment({
|
|
170
|
+
chain_id, nonce, recipient, value, token,
|
|
171
|
+
memo: { type: 'invoice', format: 'text', data: 'order-12345' }, // all subfields optional
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
What it changes — this is **not cosmetic**:
|
|
176
|
+
|
|
177
|
+
- **Absent memo (`undefined`/`null`) → V1 path.** RLP bytes are byte-identical to
|
|
178
|
+
the pre-memo SDK; `prepared.kind` is e.g. `'payment'`.
|
|
179
|
+
- **Present memo (even `{}` with empty subfields) → V2 envelope.** The builder
|
|
180
|
+
RLP-encodes `[innerList, [type, format, data]]`, `prepared.kind` becomes
|
|
181
|
+
`'<kind>_v2'`, and the **signature hash and tx hash live in a different domain**
|
|
182
|
+
— a V1 and a V2 tx with otherwise identical fields produce different hashes.
|
|
183
|
+
So passing `memo: {}` is a meaningful choice, not a no-op.
|
|
184
|
+
|
|
185
|
+
Validation runs at build time (mirrors the server's Rust rules) and throws
|
|
186
|
+
`MemoValidationError` (carries a `.code`) on violation:
|
|
187
|
+
|
|
188
|
+
| Field | Cap | Allowed chars |
|
|
189
|
+
| --- | --- | --- |
|
|
190
|
+
| `type` | 128 bytes (UTF-8) | URL-safe (RFC 3986 unreserved + gen/sub-delims + `%`) |
|
|
191
|
+
| `format` | 64 bytes (UTF-8) | URL-safe (same set) |
|
|
192
|
+
| `data` | 256 bytes (UTF-8) | any non-control: rejects NUL, C0/C1 controls, surrogates |
|
|
193
|
+
|
|
194
|
+
Error codes: `MEMO_TYPE_TOO_LONG`, `MEMO_TYPE_INVALID_CHARS`,
|
|
195
|
+
`MEMO_FORMAT_TOO_LONG`, `MEMO_FORMAT_INVALID_CHARS`, `MEMO_DATA_TOO_LONG`,
|
|
196
|
+
`MEMO_DATA_CONTROL_CHARS`, `MEMO_TOO_LARGE`. On the read side, the `Transaction`
|
|
197
|
+
union carries `memo?` back (populated only for V2 transactions); the receipt
|
|
198
|
+
types do not include it.
|
|
199
|
+
|
|
200
|
+
## Enums (import from `@1money/protocol-ts-sdk/api`)
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
enum AuthorityType {
|
|
204
|
+
MasterMint = 'MasterMintBurn',
|
|
205
|
+
MintBurnTokens = 'MintBurnTokens',
|
|
206
|
+
Pause = 'Pause',
|
|
207
|
+
ManageList = 'ManageList',
|
|
208
|
+
UpdateMetadata = 'UpdateMetadata',
|
|
209
|
+
Bridge = 'Bridge',
|
|
210
|
+
Clawback = 'Clawback',
|
|
211
|
+
}
|
|
212
|
+
enum AuthorityAction { Grant = 'Grant', Revoke = 'Revoke' }
|
|
213
|
+
enum ManageListAction { Add = 'Add', Remove = 'Remove' }
|
|
214
|
+
enum PauseAction { Pause = 'Pause', Unpause = 'Unpause' }
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Worked example: issue a token, then read its address
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { api, TransactionBuilder, createPrivateKeySigner } from '@1money/protocol-ts-sdk';
|
|
221
|
+
|
|
222
|
+
const client = api({ network: 'testnet' });
|
|
223
|
+
const master = '0x9E1E9688A44D058fF181Ed64ddFAFbBE5CC74ff3';
|
|
224
|
+
const privateKey = process.env.ONE_MONEY_PRIVATE_KEY as `0x${string}`;
|
|
225
|
+
|
|
226
|
+
const { chain_id } = await client.chain.getChainId();
|
|
227
|
+
const { nonce } = await client.accounts.getNonce(master);
|
|
228
|
+
|
|
229
|
+
const prepared = TransactionBuilder.tokenIssue({
|
|
230
|
+
chain_id, nonce,
|
|
231
|
+
symbol: 'MTK', name: 'My Token', decimals: 18,
|
|
232
|
+
master_authority: master,
|
|
233
|
+
is_private: true,
|
|
234
|
+
clawback_enabled: true,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const signed = await prepared.sign(createPrivateKeySigner(privateKey));
|
|
238
|
+
const { hash, token } = await client.tokens.issueToken(signed.toRequest());
|
|
239
|
+
console.log('issued token', token, 'in tx', hash);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Custom signer (wallet / HSM / no raw key in process)
|
|
243
|
+
|
|
244
|
+
When the private key lives in a browser wallet, KMS, or HSM, implement the
|
|
245
|
+
`SignerAdapter` interface instead of `createPrivateKeySigner`. You only need to
|
|
246
|
+
sign `prepared.signatureHash` and return `{ r, s, v }` (low-S):
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import type { SignerAdapter, Signature } from '@1money/protocol-ts-sdk';
|
|
250
|
+
|
|
251
|
+
const walletSigner: SignerAdapter = {
|
|
252
|
+
async signDigest(digest): Promise<Signature> {
|
|
253
|
+
// digest === prepared.signatureHash, a 0x-prefixed 32-byte hex string.
|
|
254
|
+
// Produce a low-S secp256k1 signature however your key custody allows.
|
|
255
|
+
return { r: '0x…', s: '0x…', v: 0 };
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const signed = await prepared.sign(walletSigner);
|
|
260
|
+
// or, if you already have the signature object:
|
|
261
|
+
const signed2 = prepared.attachSignature({ r: '0x…', s: '0x…', v: 0 });
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
`v` may be `number` (recovery id, e.g. `0`/`1`) or `boolean`. `attachSignature`
|
|
265
|
+
throws on high-S signatures (malleability guard), so ensure your signer enforces
|
|
266
|
+
low-S.
|
|
267
|
+
|
|
268
|
+
## Alternate path: EIP-712 typed-data payment
|
|
269
|
+
|
|
270
|
+
Besides the RLP `TransactionBuilder` flow above, the SDK exports a **separate
|
|
271
|
+
EIP-712 typed-data path for payments** (root-level export, via the signing
|
|
272
|
+
layer). Use it when the signer is a browser wallet doing
|
|
273
|
+
`eth_signTypedData_v4` and you submit on-chain calldata to a `submitTypedData`
|
|
274
|
+
contract entrypoint — *not* the REST `transactions.payment` endpoint. Today only
|
|
275
|
+
payment is implemented.
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import { preparePaymentTypedTx, parseSig } from '@1money/protocol-ts-sdk';
|
|
279
|
+
|
|
280
|
+
const prepared = preparePaymentTypedTx({
|
|
281
|
+
chain_id, nonce, recipient, value, token,
|
|
282
|
+
memo: { data: 'note' }, // optional, same Memo rules
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// 1. Hand prepared.typedData to the wallet for eth_signTypedData_v4.
|
|
286
|
+
const sigHex = await wallet.request({
|
|
287
|
+
method: 'eth_signTypedData_v4',
|
|
288
|
+
params: [account, JSON.stringify(prepared.typedData)],
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// 2. Parse the 65-byte sig (normalizes v to 27/28) and build the calldata.
|
|
292
|
+
const tx = prepared.encodeCalldata(parseSig(sigHex));
|
|
293
|
+
// → { to, data, value: 0n, gas, gasPrice, type: 'legacy' } — send via your wallet.
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
`encodeCalldata` requires `v ∈ {27, 28}` (recovery ids `0`/`1` are normalized by
|
|
297
|
+
`parseSig`). The EIP-712 domain is `{ name: '1Money Network', version: '1',
|
|
298
|
+
chainId, verifyingContract: 0xff…fe }`; `buildPaymentEip712TypedData` is exported
|
|
299
|
+
if you need the typed data without the calldata helper. This path is independent
|
|
300
|
+
of the `api()` REST client.
|
|
301
|
+
|
|
302
|
+
## Verifying the result
|
|
303
|
+
|
|
304
|
+
After submit you get a `{ hash }` (and `{ token }` for issue). Confirm it landed:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
const receipt = await client.transactions.getReceiptByHash(hash);
|
|
308
|
+
console.log(receipt.success ? 'confirmed' : 'failed', 'fee:', receipt.fee_used);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Receipts may not be available immediately — poll `getReceiptByHash` (or
|
|
312
|
+
`getFinalizedByHash` for finality) with a short backoff rather than a single
|
|
313
|
+
call.
|
|
314
|
+
|
|
315
|
+
## Common mistakes
|
|
316
|
+
|
|
317
|
+
- **Wrong builder/endpoint pairing** — e.g. building `tokenMint` but calling
|
|
318
|
+
`tokens.issueToken`. Use the map above.
|
|
319
|
+
- **Numeric amounts** — `value: 1` or `value: 1.5` is wrong; use a base-unit
|
|
320
|
+
string like `'1000000000000000000'`.
|
|
321
|
+
- **Stale nonce across multiple txs** — re-fetch or locally increment `nonce`
|
|
322
|
+
between sequential sends from the same sender.
|
|
323
|
+
- **Forgetting `.toRequest()`** — you submit the SignedTx's request body, not the
|
|
324
|
+
PreparedTx or the SignedTx object itself.
|
|
325
|
+
- **Reaching for `signMessage`/`encodePayload`** — deprecated; use the builder.
|