@btc-vision/btc-runtime 1.10.8 → 1.10.11
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/LICENSE +190 -0
- package/README.md +258 -137
- package/SECURITY.md +226 -0
- package/docs/README.md +614 -0
- package/docs/advanced/bitcoin-scripts.md +939 -0
- package/docs/advanced/cross-contract-calls.md +579 -0
- package/docs/advanced/plugins.md +1006 -0
- package/docs/advanced/quantum-resistance.md +660 -0
- package/docs/advanced/signature-verification.md +715 -0
- package/docs/api-reference/blockchain.md +729 -0
- package/docs/api-reference/events.md +642 -0
- package/docs/api-reference/op20.md +902 -0
- package/docs/api-reference/op721.md +819 -0
- package/docs/api-reference/safe-math.md +510 -0
- package/docs/api-reference/storage.md +840 -0
- package/docs/contracts/op-net-base.md +786 -0
- package/docs/contracts/op20-token.md +687 -0
- package/docs/contracts/op20s-signatures.md +614 -0
- package/docs/contracts/op721-nft.md +785 -0
- package/docs/contracts/reentrancy-guard.md +787 -0
- package/docs/core-concepts/blockchain-environment.md +724 -0
- package/docs/core-concepts/decorators.md +466 -0
- package/docs/core-concepts/events.md +652 -0
- package/docs/core-concepts/pointers.md +391 -0
- package/docs/core-concepts/security.md +473 -0
- package/docs/core-concepts/storage-system.md +969 -0
- package/docs/examples/basic-token.md +745 -0
- package/docs/examples/nft-with-reservations.md +1440 -0
- package/docs/examples/oracle-integration.md +1212 -0
- package/docs/examples/stablecoin.md +1180 -0
- package/docs/getting-started/first-contract.md +575 -0
- package/docs/getting-started/installation.md +384 -0
- package/docs/getting-started/project-structure.md +630 -0
- package/docs/storage/memory-maps.md +764 -0
- package/docs/storage/stored-arrays.md +778 -0
- package/docs/storage/stored-maps.md +758 -0
- package/docs/storage/stored-primitives.md +655 -0
- package/docs/types/address.md +773 -0
- package/docs/types/bytes-writer-reader.md +938 -0
- package/docs/types/calldata.md +744 -0
- package/docs/types/safe-math.md +446 -0
- package/package.json +52 -27
- package/runtime/memory/MapOfMap.ts +1 -0
- package/LICENSE.md +0 -21
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
# OP20S - Signature-Based Approvals
|
|
2
|
+
|
|
3
|
+
OP20S extends OP20 with signature-based approval mechanisms, enabling off-chain approvals and enhanced security through cryptographic signatures.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
OP20S adds:
|
|
8
|
+
- **Permit-style approvals** - Approve via signature instead of transaction
|
|
9
|
+
- **Nonce management** - Replay attack protection
|
|
10
|
+
- **Deadline enforcement** - Time-limited signatures
|
|
11
|
+
- **ML-DSA support** - Quantum-resistant signatures
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import {
|
|
15
|
+
OP20S,
|
|
16
|
+
OP20InitParameters,
|
|
17
|
+
Calldata,
|
|
18
|
+
BytesWriter,
|
|
19
|
+
ABIDataTypes,
|
|
20
|
+
} from '@btc-vision/btc-runtime/runtime';
|
|
21
|
+
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
22
|
+
|
|
23
|
+
@final
|
|
24
|
+
export class MyToken extends OP20S {
|
|
25
|
+
public constructor() {
|
|
26
|
+
super();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public override onDeployment(_calldata: Calldata): void {
|
|
30
|
+
this.instantiate(new OP20InitParameters(
|
|
31
|
+
u256.fromString('1000000000000000000000000'),
|
|
32
|
+
18,
|
|
33
|
+
'MyToken',
|
|
34
|
+
'MTK'
|
|
35
|
+
));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## ERC20Permit vs OP20S Comparison
|
|
41
|
+
|
|
42
|
+
| Feature | ERC20Permit (EIP-2612) | OP20S (OPNet) |
|
|
43
|
+
|---------|------------------------|---------------|
|
|
44
|
+
| Language | Solidity | AssemblyScript |
|
|
45
|
+
| Signature Type | ECDSA (v, r, s) | Schnorr or ML-DSA |
|
|
46
|
+
| Domain Separator | EIP-712 | EIP-712 style |
|
|
47
|
+
| Quantum Resistance | No | Yes (ML-DSA option) |
|
|
48
|
+
| Signature Parameter | Three params (v, r, s) | Single bytes param |
|
|
49
|
+
| Nonce Type | `uint256` | `u256` |
|
|
50
|
+
|
|
51
|
+
## Why Signature-Based Approvals?
|
|
52
|
+
|
|
53
|
+
### Traditional Approval Flow
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
1. User signs APPROVE transaction
|
|
57
|
+
2. User submits APPROVE TX to blockchain
|
|
58
|
+
3. Contract updates allowance
|
|
59
|
+
4. User signs TRANSFER_FROM transaction (or protocol does)
|
|
60
|
+
5. Transfer executes
|
|
61
|
+
|
|
62
|
+
Total: 2 transactions required from user
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Signature-Based Flow
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
1. User signs approval MESSAGE (off-chain, no TX needed)
|
|
69
|
+
2. Protocol submits signature with action
|
|
70
|
+
3. Contract verifies signature and executes
|
|
71
|
+
|
|
72
|
+
Total: 1 transaction, user signs off-chain only
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Permit Flow
|
|
76
|
+
|
|
77
|
+
The following diagram shows the high-level permit flow:
|
|
78
|
+
|
|
79
|
+
```mermaid
|
|
80
|
+
---
|
|
81
|
+
config:
|
|
82
|
+
theme: dark
|
|
83
|
+
---
|
|
84
|
+
flowchart LR
|
|
85
|
+
A[👤 User signs off-chain] --> B[Send signature to relayer]
|
|
86
|
+
B --> C[Relayer submits permit TX]
|
|
87
|
+
C --> D{Deadline valid?}
|
|
88
|
+
D -->|No| E[Revert]
|
|
89
|
+
D -->|Yes| F{Nonce correct?}
|
|
90
|
+
F -->|No| G[Revert]
|
|
91
|
+
F -->|Yes| H{Signature valid?}
|
|
92
|
+
H -->|No| I[Revert]
|
|
93
|
+
H -->|Yes| J[Increment nonce]
|
|
94
|
+
J --> K[Set allowance]
|
|
95
|
+
K --> L[Emit ApprovalEvent]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Detailed Permit Sequence
|
|
99
|
+
|
|
100
|
+
The following sequence diagram shows the complete permit process from off-chain signing to on-chain execution:
|
|
101
|
+
|
|
102
|
+
```mermaid
|
|
103
|
+
sequenceDiagram
|
|
104
|
+
participant User as 👤 User Wallet<br/>(Has Private Key)
|
|
105
|
+
participant OffChain as Off-Chain Signer<br/>(Browser/App)
|
|
106
|
+
participant Relayer as Relayer/Protocol<br/>(Submits TX)
|
|
107
|
+
participant Blockchain as Bitcoin L1
|
|
108
|
+
participant VM as WASM Runtime
|
|
109
|
+
participant OP20S as OP20S Contract
|
|
110
|
+
participant Storage as Storage Pointers
|
|
111
|
+
participant NonceMap as nonces Map<br/>(Prevents Replay)
|
|
112
|
+
participant AllowanceMap as allowance Map<br/>(Pointer 6)
|
|
113
|
+
participant DomainSep as Domain Separator<br/>(EIP-712)
|
|
114
|
+
participant EventLog as Event Log
|
|
115
|
+
|
|
116
|
+
Note over User,OffChain: Phase 1: Off-Chain Signing (No TX needed)
|
|
117
|
+
|
|
118
|
+
User->>OffChain: Request permit for spender
|
|
119
|
+
OffChain->>OP20S: Query nonce(owner) view call
|
|
120
|
+
Note over OffChain,OP20S: Read-only view call
|
|
121
|
+
|
|
122
|
+
OP20S->>NonceMap: get(owner)
|
|
123
|
+
NonceMap->>Storage: Read current nonce
|
|
124
|
+
Storage-->>NonceMap: nonce value
|
|
125
|
+
NonceMap-->>OP20S: currentNonce
|
|
126
|
+
OP20S-->>OffChain: Return nonce
|
|
127
|
+
|
|
128
|
+
OffChain->>DomainSep: Get DOMAIN_SEPARATOR()
|
|
129
|
+
DomainSep-->>OffChain: Domain hash
|
|
130
|
+
Note over DomainSep: Includes: name, version,<br/>chainId, contract address
|
|
131
|
+
|
|
132
|
+
OffChain->>OffChain: Build permit struct
|
|
133
|
+
Note over OffChain: owner, spender, value,<br/>nonce, deadline
|
|
134
|
+
|
|
135
|
+
OffChain->>OffChain: Hash permit data
|
|
136
|
+
Note over OffChain: permitHash = keccak256(<br/>abi.encode(typeHash, permit))
|
|
137
|
+
|
|
138
|
+
OffChain->>OffChain: Build EIP-712 message
|
|
139
|
+
Note over OffChain: message = keccak256(<br/>0x1901 + domainSep + permitHash)
|
|
140
|
+
|
|
141
|
+
User->>OffChain: Sign message with private key
|
|
142
|
+
Note over User: Schnorr or ML-DSA signature
|
|
143
|
+
|
|
144
|
+
OffChain-->>User: signature bytes
|
|
145
|
+
|
|
146
|
+
Note over User,Blockchain: Phase 2: On-Chain Submission (Relayer Pays)
|
|
147
|
+
|
|
148
|
+
User->>Relayer: Send signature + permit params
|
|
149
|
+
Note over User,Relayer: User sends NO on-chain TX
|
|
150
|
+
|
|
151
|
+
Relayer->>Blockchain: Submit permit() transaction
|
|
152
|
+
Note over Relayer: Relayer submits TX to chain
|
|
153
|
+
|
|
154
|
+
Blockchain->>VM: Execute transaction
|
|
155
|
+
VM->>OP20S: permit(owner, spender, value, deadline, sig)
|
|
156
|
+
activate OP20S
|
|
157
|
+
|
|
158
|
+
OP20S->>OP20S: Read calldata parameters
|
|
159
|
+
|
|
160
|
+
OP20S->>OP20S: Check Blockchain.block.medianTime
|
|
161
|
+
|
|
162
|
+
alt Deadline passed
|
|
163
|
+
OP20S->>VM: Revert('Permit expired')
|
|
164
|
+
VM->>Relayer: Transaction failed
|
|
165
|
+
Relayer->>User: Permit expired
|
|
166
|
+
else Valid deadline
|
|
167
|
+
OP20S->>NonceMap: get(owner)
|
|
168
|
+
NonceMap->>Storage: Read expected nonce
|
|
169
|
+
Storage-->>NonceMap: expectedNonce
|
|
170
|
+
NonceMap-->>OP20S: nonce value
|
|
171
|
+
|
|
172
|
+
OP20S->>DomainSep: getDomainSeparator()
|
|
173
|
+
DomainSep-->>OP20S: domain hash
|
|
174
|
+
|
|
175
|
+
OP20S->>OP20S: buildPermitMessage()
|
|
176
|
+
Note over OP20S: Reconstruct same message<br/>user signed off-chain
|
|
177
|
+
|
|
178
|
+
OP20S->>OP20S: verifySignature(owner, message, sig)
|
|
179
|
+
Note over OP20S: Recover signer from signature
|
|
180
|
+
|
|
181
|
+
alt Signature invalid OR signer != owner
|
|
182
|
+
OP20S->>VM: Revert('Invalid signature')
|
|
183
|
+
VM->>Relayer: Transaction failed
|
|
184
|
+
else Signature valid
|
|
185
|
+
OP20S->>OP20S: SafeMath.add(nonce, 1)
|
|
186
|
+
OP20S->>NonceMap: set(owner, nonce + 1)
|
|
187
|
+
NonceMap->>Storage: Write incremented nonce
|
|
188
|
+
Note over Storage: Replay protection:<br/>Old signatures now invalid
|
|
189
|
+
|
|
190
|
+
OP20S->>OP20S: _approve(owner, spender, value)
|
|
191
|
+
|
|
192
|
+
OP20S->>AllowanceMap: set(owner->spender, value)
|
|
193
|
+
AllowanceMap->>Storage: Write allowance
|
|
194
|
+
Note over Storage: Approval stored
|
|
195
|
+
|
|
196
|
+
OP20S->>OP20S: Create ApprovalEvent
|
|
197
|
+
OP20S->>EventLog: emit ApprovalEvent(owner, spender, value)
|
|
198
|
+
|
|
199
|
+
OP20S->>VM: Return success
|
|
200
|
+
deactivate OP20S
|
|
201
|
+
|
|
202
|
+
VM->>Blockchain: Commit state changes
|
|
203
|
+
Blockchain->>Relayer: Transaction success
|
|
204
|
+
Relayer->>User: Permit approved!
|
|
205
|
+
Note over User: Approved without<br/>submitting any TX!
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Message Construction
|
|
211
|
+
|
|
212
|
+
The following diagram shows how the permit message is constructed for signing:
|
|
213
|
+
|
|
214
|
+
```mermaid
|
|
215
|
+
---
|
|
216
|
+
config:
|
|
217
|
+
theme: dark
|
|
218
|
+
---
|
|
219
|
+
flowchart LR
|
|
220
|
+
A[Domain separator] --> B[Permit struct]
|
|
221
|
+
B --> C[Combine with EIP-712 prefix]
|
|
222
|
+
C --> D[Hash message]
|
|
223
|
+
D --> E[👤 User signs with private key]
|
|
224
|
+
E --> F[Submit signature on-chain]
|
|
225
|
+
F --> G[Verify signature]
|
|
226
|
+
G --> H{Valid?}
|
|
227
|
+
H -->|No| I[Revert]
|
|
228
|
+
H -->|Yes| J[Process permit]
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Nonce Management
|
|
232
|
+
|
|
233
|
+
Each address has a nonce that increments with each signature use to prevent replay attacks:
|
|
234
|
+
|
|
235
|
+
```mermaid
|
|
236
|
+
stateDiagram-v2
|
|
237
|
+
[*] --> NeverUsed: 👤 User account created
|
|
238
|
+
|
|
239
|
+
state NeverUsed {
|
|
240
|
+
[*] --> Nonce0
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
NeverUsed --> FirstPermit: permit() with nonce=0
|
|
244
|
+
|
|
245
|
+
state FirstPermit {
|
|
246
|
+
Nonce0 --> Nonce1: Signature verified<br/>Nonce incremented
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
FirstPermit --> SubsequentPermits
|
|
250
|
+
|
|
251
|
+
state SubsequentPermits {
|
|
252
|
+
Nonce1 --> Nonce2: permit() nonce=1
|
|
253
|
+
Nonce2 --> Nonce3: permit() nonce=2
|
|
254
|
+
Nonce3 --> NoncePlus: permit() nonce=3
|
|
255
|
+
NoncePlus --> NoncePlus: More permits
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
state "Replay Attack Blocked" as Blocked {
|
|
259
|
+
[*] --> AttemptOldNonce
|
|
260
|
+
AttemptOldNonce --> Reverted: Old signature submitted
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
SubsequentPermits --> Blocked: Try to reuse old signature
|
|
264
|
+
Blocked --> SubsequentPermits: Continue with correct nonce
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Permit Method
|
|
268
|
+
|
|
269
|
+
### Usage
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// Off-chain: User signs permit data
|
|
273
|
+
const permitData = {
|
|
274
|
+
owner: userAddress,
|
|
275
|
+
spender: protocolAddress,
|
|
276
|
+
value: amount,
|
|
277
|
+
nonce: await contract.nonces(userAddress),
|
|
278
|
+
deadline: Math.floor(Date.now() / 1000) + 3600 // 1 hour
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const signature = signPermit(permitData, userPrivateKey);
|
|
282
|
+
|
|
283
|
+
// On-chain: Protocol submits permit
|
|
284
|
+
contract.permit(owner, spender, value, deadline, signature);
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Parameters
|
|
288
|
+
|
|
289
|
+
| Parameter | Type | Description |
|
|
290
|
+
|-----------|------|-------------|
|
|
291
|
+
| `owner` | `Address` | Token owner granting approval |
|
|
292
|
+
| `spender` | `Address` | Address being approved |
|
|
293
|
+
| `value` | `u256` | Amount to approve |
|
|
294
|
+
| `deadline` | `u64` | Signature expiration timestamp |
|
|
295
|
+
| `signature` | `bytes` | Cryptographic signature |
|
|
296
|
+
|
|
297
|
+
### Signature Verification
|
|
298
|
+
|
|
299
|
+
The permit verifies:
|
|
300
|
+
1. **Signature validity** - Matches owner's public key
|
|
301
|
+
2. **Deadline** - Current time <= deadline
|
|
302
|
+
3. **Nonce** - Matches expected nonce for owner
|
|
303
|
+
4. **Domain** - Correct contract and chain
|
|
304
|
+
|
|
305
|
+
## Nonces
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
// Get current nonce for address
|
|
309
|
+
const nonce: u256 = contract.nonces(address);
|
|
310
|
+
|
|
311
|
+
// Nonce auto-increments after successful permit
|
|
312
|
+
// This prevents signature replay
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Replay Protection
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// Signature for nonce 0 used
|
|
319
|
+
contract.permit(..., nonce=0, signature0); // Success, nonce now 1
|
|
320
|
+
|
|
321
|
+
// Same signature replayed
|
|
322
|
+
contract.permit(..., nonce=0, signature0); // FAILS - nonce mismatch
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Solidity Comparison (EIP-2612)
|
|
326
|
+
|
|
327
|
+
<table>
|
|
328
|
+
<tr>
|
|
329
|
+
<th>ERC20Permit (Solidity)</th>
|
|
330
|
+
<th>OP20S (OPNet)</th>
|
|
331
|
+
</tr>
|
|
332
|
+
<tr>
|
|
333
|
+
<td>
|
|
334
|
+
|
|
335
|
+
```solidity
|
|
336
|
+
contract MyToken is ERC20Permit {
|
|
337
|
+
constructor()
|
|
338
|
+
ERC20("MyToken", "MTK")
|
|
339
|
+
ERC20Permit("MyToken")
|
|
340
|
+
{ }
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Usage
|
|
344
|
+
token.permit(
|
|
345
|
+
owner,
|
|
346
|
+
spender,
|
|
347
|
+
value,
|
|
348
|
+
deadline,
|
|
349
|
+
v, r, s // ECDSA signature components
|
|
350
|
+
);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
</td>
|
|
354
|
+
<td>
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
@final
|
|
358
|
+
export class MyToken extends OP20S {
|
|
359
|
+
constructor() {
|
|
360
|
+
super();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
public override onDeployment(_: Calldata): void {
|
|
364
|
+
this.instantiate(new OP20InitParameters(
|
|
365
|
+
maxSupply, 18, 'MyToken', 'MTK'
|
|
366
|
+
));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Usage
|
|
371
|
+
token.permit(
|
|
372
|
+
owner,
|
|
373
|
+
spender,
|
|
374
|
+
value,
|
|
375
|
+
deadline,
|
|
376
|
+
signature // Schnorr or ML-DSA
|
|
377
|
+
);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
</td>
|
|
381
|
+
</tr>
|
|
382
|
+
</table>
|
|
383
|
+
|
|
384
|
+
## Domain Separator
|
|
385
|
+
|
|
386
|
+
OP20S uses EIP-712 style domain separation:
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// Domain includes:
|
|
390
|
+
// - Contract name
|
|
391
|
+
// - Contract version
|
|
392
|
+
// - Chain ID
|
|
393
|
+
// - Contract address
|
|
394
|
+
|
|
395
|
+
// This prevents cross-chain and cross-contract replay
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Quantum Resistance
|
|
399
|
+
|
|
400
|
+
OP20S supports ML-DSA (Dilithium) signatures for quantum resistance:
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
// Traditional Schnorr signature
|
|
404
|
+
contract.permit(owner, spender, value, deadline, schnorrSignature);
|
|
405
|
+
|
|
406
|
+
// ML-DSA quantum-resistant signature
|
|
407
|
+
contract.permitQuantum(owner, spender, value, deadline, mldsaSignature);
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### Extended Address
|
|
411
|
+
|
|
412
|
+
For quantum-safe operations, use `ExtendedAddress`:
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
import { ExtendedAddress } from '@btc-vision/btc-runtime/runtime';
|
|
416
|
+
|
|
417
|
+
// Extended address with both key types
|
|
418
|
+
const extAddress = new ExtendedAddress(
|
|
419
|
+
traditionalPubKey, // Schnorr
|
|
420
|
+
mldsaPubKey // ML-DSA
|
|
421
|
+
);
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
See [Quantum Resistance](../advanced/quantum-resistance.md) for details.
|
|
425
|
+
|
|
426
|
+
## Implementation Details
|
|
427
|
+
|
|
428
|
+
### Permit Verification
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
@method(
|
|
432
|
+
{ name: 'owner', type: ABIDataTypes.ADDRESS },
|
|
433
|
+
{ name: 'spender', type: ABIDataTypes.ADDRESS },
|
|
434
|
+
{ name: 'value', type: ABIDataTypes.UINT256 },
|
|
435
|
+
{ name: 'deadline', type: ABIDataTypes.UINT64 },
|
|
436
|
+
{ name: 'signature', type: ABIDataTypes.BYTES },
|
|
437
|
+
)
|
|
438
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
439
|
+
@emit('Approval')
|
|
440
|
+
public permit(calldata: Calldata): BytesWriter {
|
|
441
|
+
const owner = calldata.readAddress();
|
|
442
|
+
const spender = calldata.readAddress();
|
|
443
|
+
const value = calldata.readU256();
|
|
444
|
+
const deadline = calldata.readU64();
|
|
445
|
+
const signature = calldata.readBytes();
|
|
446
|
+
|
|
447
|
+
// Check deadline
|
|
448
|
+
if (Blockchain.block.medianTime > deadline) {
|
|
449
|
+
throw new Revert('Permit expired');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Get and increment nonce
|
|
453
|
+
const nonce = this.nonces.get(owner);
|
|
454
|
+
this.nonces.set(owner, SafeMath.add(nonce, u256.One));
|
|
455
|
+
|
|
456
|
+
// Verify signature
|
|
457
|
+
const message = this.buildPermitMessage(owner, spender, value, nonce, deadline);
|
|
458
|
+
if (!this.verifySignature(owner, message, signature)) {
|
|
459
|
+
throw new Revert('Invalid signature');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Set approval
|
|
463
|
+
this._approve(owner, spender, value);
|
|
464
|
+
|
|
465
|
+
return new BytesWriter(0);
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Message Format
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
// Permit message structure
|
|
473
|
+
struct Permit {
|
|
474
|
+
address owner;
|
|
475
|
+
address spender;
|
|
476
|
+
uint256 value;
|
|
477
|
+
uint256 nonce;
|
|
478
|
+
uint256 deadline;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Hashed with domain separator
|
|
482
|
+
messageHash = SHA256(domainSeparator || SHA256(permitTypeHash || encode(permit)))
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
## Additional Methods
|
|
486
|
+
|
|
487
|
+
OP20S adds these methods to OP20:
|
|
488
|
+
|
|
489
|
+
| Method | Description |
|
|
490
|
+
|--------|-------------|
|
|
491
|
+
| `permit(...)` | Approve via signature |
|
|
492
|
+
| `nonces(address)` | Get nonce for address |
|
|
493
|
+
| `DOMAIN_SEPARATOR()` | Get domain separator |
|
|
494
|
+
|
|
495
|
+
## Use Cases
|
|
496
|
+
|
|
497
|
+
### 1. Off-Chain Approvals
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
// User signs permit off-chain
|
|
501
|
+
const sig = await user.signPermit(spender, amount, deadline);
|
|
502
|
+
|
|
503
|
+
// Protocol submits on user's behalf
|
|
504
|
+
await contract.permit(user, spender, amount, deadline, sig);
|
|
505
|
+
await contract.transferFrom(user, recipient, amount);
|
|
506
|
+
|
|
507
|
+
// User submitted no on-chain TX!
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### 2. Single-Transaction Approve+Transfer
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
// Protocol contract
|
|
514
|
+
@method(
|
|
515
|
+
{ name: 'owner', type: ABIDataTypes.ADDRESS },
|
|
516
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
517
|
+
{ name: 'deadline', type: ABIDataTypes.UINT64 },
|
|
518
|
+
{ name: 'signature', type: ABIDataTypes.BYTES },
|
|
519
|
+
)
|
|
520
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
521
|
+
@emit('Deposit')
|
|
522
|
+
public depositWithPermit(calldata: Calldata): BytesWriter {
|
|
523
|
+
// Read permit params
|
|
524
|
+
const owner = calldata.readAddress();
|
|
525
|
+
const amount = calldata.readU256();
|
|
526
|
+
const deadline = calldata.readU64();
|
|
527
|
+
const signature = calldata.readBytes();
|
|
528
|
+
|
|
529
|
+
// Execute permit
|
|
530
|
+
token.permit(owner, this.address, amount, deadline, signature);
|
|
531
|
+
|
|
532
|
+
// Now transfer in same transaction
|
|
533
|
+
token.transferFrom(owner, this.address, amount);
|
|
534
|
+
|
|
535
|
+
return new BytesWriter(0);
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
### 3. Meta-Transactions
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
// Relayer submits on behalf of user
|
|
543
|
+
@method(
|
|
544
|
+
{ name: 'user', type: ABIDataTypes.ADDRESS },
|
|
545
|
+
{ name: 'permitSig', type: ABIDataTypes.BYTES },
|
|
546
|
+
{ name: 'actionSig', type: ABIDataTypes.BYTES },
|
|
547
|
+
)
|
|
548
|
+
@returns({ name: 'success', type: ABIDataTypes.BOOL })
|
|
549
|
+
public executeMetaTx(calldata: Calldata): BytesWriter {
|
|
550
|
+
const user = calldata.readAddress();
|
|
551
|
+
const permitSig = calldata.readBytes();
|
|
552
|
+
const actionSig = calldata.readBytes();
|
|
553
|
+
|
|
554
|
+
// Verify permit
|
|
555
|
+
this.permit(user, ...permitSig);
|
|
556
|
+
|
|
557
|
+
// Execute action
|
|
558
|
+
this.executeAction(user, actionSig);
|
|
559
|
+
|
|
560
|
+
return new BytesWriter(0);
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
## Security Considerations
|
|
565
|
+
|
|
566
|
+
### Deadline Selection
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
// Too short: User might not complete in time
|
|
570
|
+
const deadline = now + 60; // 1 minute - risky
|
|
571
|
+
|
|
572
|
+
// Reasonable: Enough time, limited exposure
|
|
573
|
+
const deadline = now + 3600; // 1 hour - good
|
|
574
|
+
|
|
575
|
+
// Too long: Extended attack window
|
|
576
|
+
const deadline = now + 86400 * 365; // 1 year - bad
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### Signature Storage
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
// NEVER store signatures on-chain unnecessarily
|
|
583
|
+
// They become public and could be analyzed
|
|
584
|
+
|
|
585
|
+
// DO process and discard
|
|
586
|
+
const sig = calldata.readBytes();
|
|
587
|
+
verifyAndProcess(sig);
|
|
588
|
+
// sig is gone after transaction
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Front-Running Protection
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
// Permit followed by transfer in same tx = safe
|
|
595
|
+
permit(owner, spender, value, deadline, sig);
|
|
596
|
+
transferFrom(owner, recipient, value); // Same tx
|
|
597
|
+
|
|
598
|
+
// Separate transactions = front-running risk
|
|
599
|
+
// Attacker could see permit and race to use allowance
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
## Best Practices
|
|
603
|
+
|
|
604
|
+
1. **Set reasonable deadlines** - Not too short, not too long
|
|
605
|
+
2. **Process permits atomically** - Permit + action in same transaction
|
|
606
|
+
3. **Monitor nonces** - Track expected nonces off-chain
|
|
607
|
+
4. **Verify domains** - Ensure signatures are for correct contract
|
|
608
|
+
5. **Consider quantum safety** - Use ML-DSA for high-value applications
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
**Navigation:**
|
|
613
|
+
- Previous: [OP20 Token](./op20-token.md)
|
|
614
|
+
- Next: [OP721 NFT](./op721-nft.md)
|