Supply Chain & Regulatory Compliance (EUDR) with Earth Observation

This guide demonstrates how to build a supply chain compliance system for the EU Deforestation Regulation (EUDR) using TrustWeave and Earth Observation data. You’ll learn how to create verifiable credentials that prove geospatial non-deforestation for every shipment.

What You’ll Build

By the end of this tutorial, you’ll have:

  • ✅ Created DIDs for importers, exporters, and verifiers
  • ✅ Issued verifiable credentials for geospatial non-deforestation proof
  • ✅ Built Digital Product Passport (DPP) using VCs
  • ✅ Implemented automated compliance verification
  • ✅ Created EO data evidence for deforestation monitoring
  • ✅ Anchored compliance credentials to blockchain for audit trails

Big Picture & Significance

The EU Deforestation Regulation (EUDR)

By 2025, importers must prove geospatial non-deforestation for every shipment. Current systems rely on self-declared PDFs, creating a trust gap. Verifiable Credentials provide cryptographic proof of compliance.

Industry Context:

  • Regulatory Requirement: EUDR mandatory by 2025
  • Scope: All imports of commodities (coffee, cocoa, palm oil, etc.)
  • Current Gap: Self-declared PDFs are not verifiable
  • Solution: W3C Verifiable Credentials to model farm identity and compliance status
  • EO Data: Earth Observation data provides verifiable evidence of non-deforestation

Why This Matters:

  1. Regulatory Compliance: Meet EUDR requirements with verifiable proof
  2. Geospatial Proof: EO data provides verifiable evidence of non-deforestation
  3. Digital Product Passport: VCs are leading candidate for DPP implementation
  4. Trust: Cryptographic proof prevents fraud and false declarations
  5. Automation: Enable automated compliance verification
  6. Audit Trails: Complete compliance history for regulators

Real-World Examples

EU Deforestation Regulation (EUDR):

  • Requirement: By 2025, importers must prove geospatial non-deforestation
  • Current Gap: Self-declared PDFs
  • Solution: AgrospAI and similar Agri-food data spaces testing W3C Verifiable Credentials

Climate TRACE:

  • Independent tracking of GHG emissions using satellites and AI
  • Acts as “Global Verifier”
  • Emitter could issue VC claiming “Low Emissions”
  • Verifier checks against Climate TRACE data to validate/refute automatically

Value Proposition

Problems Solved

  1. Regulatory Compliance: Meet EUDR requirements with verifiable proof
  2. Geospatial Proof: EO data provides verifiable evidence
  3. Digital Product Passport: Standard format for DPP implementation
  4. Automated Verification: Enable automated compliance checks
  5. Fraud Prevention: Cryptographic proof prevents false declarations
  6. Audit Trails: Complete compliance history
  7. Trust: Build trust through verifiable credentials

Business Benefits

For Importers:

  • Compliance: Meet EUDR requirements automatically
  • Risk Reduction: Reduce risk of non-compliance penalties
  • Efficiency: Automated verification reduces costs
  • Trust: Build trust with regulators and consumers

For Exporters:

  • Market Access: Access EU market with verifiable compliance
  • Differentiation: Stand out with verifiable credentials
  • Efficiency: Reduce compliance documentation costs
  • Trust: Build trust with importers

For Regulators:

  • Verification: Automated compliance verification
  • Audit Trails: Complete compliance history
  • Transparency: Verifiable data lineage
  • Efficiency: Reduce manual verification costs

Understanding the Problem

EUDR compliance needs:

  1. Geospatial Proof: Prove non-deforestation for specific locations
  2. Digital Product Passport: Standard format for product information
  3. Automated Verification: Enable automated compliance checks
  4. Audit Trails: Complete compliance history
  5. EO Data Evidence: Use Earth Observation data as proof
  6. Trust: Cryptographic proof prevents fraud

Prerequisites

  • Java 21+
  • Kotlin 2.2.0+
  • Gradle 8.5+
  • Basic understanding of Kotlin and coroutines
  • Understanding of EUDR requirements

Step 1: Add Dependencies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dependencies {
    // Core TrustWeave modules
    implementation("com.trustweave:trustweave-all:1.0.0-SNAPSHOT")

    // Test kit for in-memory implementations
    testImplementation("com.trustweave:trustweave-testkit:1.0.0-SNAPSHOT")

    // Optional: Algorand adapter for real blockchain anchoring
    implementation("com.trustweave.chains:algorand:1.0.0-SNAPSHOT")

    // Kotlinx Serialization
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")

    // Coroutines
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}

Step 2: Complete Runnable Example

Here’s a complete EUDR compliance workflow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
package com.example.eudr.compliance

import com.trustweave.TrustWeave
import com.trustweave.core.*
import com.trustweave.json.DigestUtils
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.*
import java.time.Instant

fun main() = runBlocking {
    println("=".repeat(70))
    println("EUDR Compliance with EO Data - Complete Example")
    println("=".repeat(70))

    // Step 1: Create TrustWeave instance
    val trustWeave = TrustWeave.build {
        factories(
            kmsFactory = TestkitKmsFactory(),
            didMethodFactory = TestkitDidMethodFactory()
        )
        keys { provider("inMemory"); algorithm("Ed25519") }
        did { method("key") { algorithm("Ed25519") } }
    }
    println("\n✅ TrustWeave initialized")

    // Step 2: Create DIDs for exporter, importer, and verifier
    import com.trustweave.trust.types.DidCreationResult
    
    val exporterDidResult = trustWeave.createDid { method("key") }
    val exporterDid = when (exporterDidResult) {
        is DidCreationResult.Success -> {
            println("✅ Exporter DID: ${exporterDidResult.did.value}")
            exporterDidResult.did
        }
        else -> {
            println("Failed to create exporter DID: ${exporterDidResult.reason}")
            return@runBlocking
        }
    }
    
    val importerDidResult = trustWeave.createDid { method("key") }
    val importerDid = when (importerDidResult) {
        is DidCreationResult.Success -> {
            println("✅ Importer DID: ${importerDidResult.did.value}")
            importerDidResult.did
        }
        else -> {
            println("Failed to create importer DID: ${importerDidResult.reason}")
            return@runBlocking
        }
    }
    
    val verifierDidResult = trustWeave.createDid { method("key") }
    val verifierDid = when (verifierDidResult) {
        is DidCreationResult.Success -> {
            println("✅ Verifier DID: ${verifierDidResult.did.value}")
            verifierDidResult.did
        }
        else -> {
            println("Failed to create verifier DID: ${verifierDidResult.reason}")
            return@runBlocking
        }
    }

    // Step 3: Create farm/production site DID
    val farmDidResult = trustWeave.createDid { method("key") }
    val farmDid = when (farmDidResult) {
        is DidCreationResult.Success -> {
            println("✅ Farm DID: ${farmDidResult.did.value}")
            farmDidResult.did
        }
        else -> {
            println("Failed to create farm DID: ${farmDidResult.reason}")
            return@runBlocking
        }
    }

    // Step 4: Create EO data evidence (non-deforestation proof)
    val eoDeforestationProof = buildJsonObject {
        put("id", "eo-deforestation-proof-2024")
        put("type", "NonDeforestationProof")
        put("location", buildJsonObject {
            put("latitude", -3.4653)
            put("longitude", -62.2159)
            put("region", "Amazon Rainforest, Brazil")
            put("farmId", farmDid.id)
            put("polygon", buildJsonArray {
                // Farm boundary coordinates
                add(buildJsonArray { add(-62.22); add(-3.47) })
                add(buildJsonArray { add(-62.21); add(-3.47) })
                add(buildJsonArray { add(-62.21); add(-3.46) })
                add(buildJsonArray { add(-62.22); add(-3.46) })
                add(buildJsonArray { add(-62.22); add(-3.47) })
            })
        })
        put("analysis", buildJsonObject {
            put("method", "Sentinel-2 L2A Time Series Analysis")
            put("analysisPeriod", buildJsonObject {
                put("startDate", "2020-01-01")
                put("endDate", "2024-12-31")
            })
            put("deforestationDetected", false)
            put("forestCoverChange", 0.02)  // 2% increase (reforestation)
            put("confidence", 0.95)
            put("verificationDate", Instant.now().toString())
        })
        put("timestamp", Instant.now().toString())
    }

    val eoProofDigest = DigestUtils.sha256DigestMultibase(eoDeforestationProof)

    // Step 5: Verifier issues compliance credential
    val verifierResolution = trustWeave.resolveDid(verifierDid)
    val verifierDoc = when (verifierResolution) {
        is DidResolutionResult.Success -> verifierResolution.document
        else -> throw IllegalStateException("Failed to resolve verifier DID")
    }
    val verifierKeyId = verifierDoc.verificationMethod.firstOrNull()?.id?.substringAfter("#")
        ?: throw IllegalStateException("No verification method found")

    val complianceCredential = TrustWeave.issueCredential(
        issuerDid = verifierDid.value,
        issuerKeyId = verifierKeyId,
        credentialSubject = buildJsonObject {
            put("id", "eudr-compliance-2024-001")
            put("complianceType", "EUDR")
            put("farm", buildJsonObject {
                put("id", farmDid.value)
                put("name", "Sustainable Coffee Farm")
                put("location", buildJsonObject {
                    put("latitude", -3.4653)
                    put("longitude", -62.2159)
                    put("country", "Brazil")
                })
            })
            put("eoEvidence", eoDeforestationProof)
            put("eoEvidenceDigest", eoProofDigest)
            put("complianceStatus", "compliant")
            put("verificationDate", Instant.now().toString())
            put("validUntil", Instant.now().plus(365, java.time.temporal.ChronoUnit.DAYS).toString())
            put("verifier", verifierDid.value)
        },
        types = listOf("VerifiableCredential", "EUDRComplianceCredential")
    ).fold(
        onSuccess = { it },
        onFailure = { error ->
            println("❌ Failed to issue compliance credential: ${error.message}")
            return@runBlocking
        }
    )

    println("✅ Compliance Credential issued: ${complianceCredential.id}")
    println("   Status: compliant")
    println("   Farm: ${farmDid.id}")

    // Step 6: Create Digital Product Passport (DPP)
    val exporterResolution = trustWeave.resolveDid(exporterDid)
    val exporterDoc = when (exporterResolution) {
        is DidResolutionResult.Success -> exporterResolution.document
        else -> throw IllegalStateException("Failed to resolve exporter DID")
    }
    val exporterKeyId = exporterDoc.verificationMethod.firstOrNull()?.id?.substringAfter("#")
        ?: throw IllegalStateException("No verification method found")

    val dppCredential = TrustWeave.issueCredential(
        issuerDid = exporterDid.value,
        issuerKeyId = exporterKeyId,
        credentialSubject = buildJsonObject {
            put("id", "dpp-coffee-shipment-2024-001")
            put("productType", "Coffee")
            put("commodity", "Coffee Beans")
            put("quantity", 10000.0)  // kg
            put("unit", "kg")
            put("farm", farmDid.value)
            put("complianceCredentialId", complianceCredential.id)
            put("eoEvidenceDigest", eoProofDigest)
            put("harvestDate", "2024-06-15")
            put("exportDate", Instant.now().toString())
            put("exporter", exporterDid.value)
            put("destination", "EU")
        },
        types = listOf("VerifiableCredential", "DigitalProductPassport", "EUDRProductCredential")
    ).fold(
        onSuccess = { it },
        onFailure = { error ->
            println("❌ Failed to issue DPP: ${error.message}")
            return@runBlocking
        }
    )

    println("✅ Digital Product Passport issued: ${dppCredential.id}")
    println("   Product: Coffee Beans")
    println("   Quantity: 10,000 kg")

    // Step 7: Importer verifies compliance before import
    val dppVerification = TrustWeave.verifyCredential(dppCredential).fold(
        onSuccess = { it },
        onFailure = { error ->
            println("❌ DPP verification failed: ${error.message}")
            return@runBlocking
        }
    )

    if (!dppVerification.valid) {
        println("❌ DPP verification failed: ${dppVerification.errors}")
        return@runBlocking
    }

    println("✅ DPP verified")

    // Step 8: Verify compliance credential
    val complianceVerification = TrustWeave.verifyCredential(complianceCredential).fold(
        onSuccess = { it },
        onFailure = { error ->
            println("❌ Compliance verification failed: ${error.message}")
            return@runBlocking
        }
    )

    if (!complianceVerification.valid) {
        println("❌ Compliance verification failed: ${complianceVerification.errors}")
        return@runBlocking
    }

    println("✅ Compliance verified")

    // Step 9: Verify EO evidence integrity
    val currentProofDigest = DigestUtils.sha256DigestMultibase(eoDeforestationProof)
    val credentialProofDigest = complianceCredential.credentialSubject
        .jsonObject["eoEvidenceDigest"]?.jsonPrimitive?.content ?: ""

    if (currentProofDigest == credentialProofDigest) {
        println("✅ EO Evidence integrity verified")
        println("   No tampering detected")
    } else {
        println("❌ EO Evidence integrity FAILED")
        println("   Evidence may have been tampered with")
        return@runBlocking
    }

    // Step 10: Check against Climate TRACE (global verifier)
    val climateTraceVerification = verifyAgainstClimateTrace(
        location = buildJsonObject {
            put("latitude", -3.4653)
            put("longitude", -62.2159)
        },
        eoEvidence = eoDeforestationProof
    )

    if (climateTraceVerification) {
        println("✅ Verified against Climate TRACE")
        println("   Global verification confirms compliance")
    } else {
        println("⚠️ Climate TRACE verification inconclusive")
    }

    // Step 11: Anchor to blockchain for audit trail
    val anchorResult = trustWeave.blockchains.anchor(
        data = dppCredential,
        serializer = VerifiableCredential.serializer(),
        chainId = "algorand:testnet"
    ).fold(
        onSuccess = { anchor ->
            println("✅ DPP anchored: ${anchor.ref.txHash}")
            anchor
        },
        onFailure = { error ->
            println("❌ Anchoring failed: ${error.message}")
            null
        }
    )

    println("\n📊 EUDR Compliance Summary:")
    println("   Farm: ${farmDid.id}")
    println("   Compliance Status: compliant")
    println("   EO Evidence: verified")
    println("   DPP: issued and verified")
    println("   Blockchain Anchor: ${anchorResult?.ref?.txHash}")
    println("   ✅ Ready for EU import")

    println("\n" + "=".repeat(70))
    println("✅ EUDR Compliance Scenario Complete!")
    println("=".repeat(70))
}

// Helper function to verify against Climate TRACE
suspend fun verifyAgainstClimateTrace(
    location: JsonObject,
    eoEvidence: JsonObject
): Boolean {
    // In production, query Climate TRACE API
    // Compare EO evidence with Climate TRACE data
    // Return true if evidence matches Climate TRACE data
    return true  // Placeholder
}

Expected Output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
======================================================================
EUDR Compliance with EO Data - Complete Example
======================================================================

✅ TrustWeave initialized
✅ Exporter DID: did:key:z6Mk...
✅ Importer DID: did:key:z6Mk...
✅ Verifier DID: did:key:z6Mk...
✅ Farm DID: did:key:z6Mk...
✅ Compliance Credential issued: urn:uuid:...
   Status: compliant
   Farm: did:key:z6Mk...
✅ Digital Product Passport issued: urn:uuid:...
   Product: Coffee Beans
   Quantity: 10,000 kg
✅ DPP verified
✅ Compliance verified
✅ EO Evidence integrity verified
   No tampering detected
✅ Verified against Climate TRACE
   Global verification confirms compliance
✅ DPP anchored: tx_...

📊 EUDR Compliance Summary:
   Farm: did:key:z6Mk...
   Compliance Status: compliant
   EO Evidence: verified
   DPP: issued and verified
   Blockchain Anchor: tx_...
   ✅ Ready for EU import

======================================================================
✅ EUDR Compliance Scenario Complete!
======================================================================

Step 3: Automated Compliance Verification

Enable automated verification:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
suspend fun automatedEUDRVerification(
    dppCredential: VerifiableCredential
): ComplianceResult {
    // Verify DPP credential
    val dppVerification = TrustWeave.verifyCredential(dppCredential).getOrThrow()
    if (!dppVerification.valid) {
        return ComplianceResult.NonCompliant("DPP verification failed")
    }

    // Extract compliance credential ID
    val complianceCredentialId = extractComplianceCredentialId(dppCredential)

    // Verify compliance credential
    val complianceCredential = fetchCredential(complianceCredentialId)
    val complianceVerification = TrustWeave.verifyCredential(complianceCredential).getOrThrow()
    if (!complianceVerification.valid) {
        return ComplianceResult.NonCompliant("Compliance verification failed")
    }

    // Verify EO evidence
    val eoEvidence = extractEOEvidence(complianceCredential)
    val eoVerification = verifyEOEvidence(eoEvidence)
    if (!eoVerification.valid) {
        return ComplianceResult.NonCompliant("EO evidence verification failed")
    }

    // Check against Climate TRACE
    val climateTraceCheck = verifyAgainstClimateTrace(eoEvidence)
    if (!climateTraceCheck) {
        return ComplianceResult.NonCompliant("Climate TRACE verification failed")
    }

    return ComplianceResult.Compliant("All checks passed")
}

sealed class ComplianceResult {
    data class Compliant(val message: String) : ComplianceResult()
    data class NonCompliant(val reason: String) : ComplianceResult()
}

Step 4: Digital Product Passport (DPP) Structure

DPP using VCs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
val dpp = buildJsonObject {
    put("id", "dpp-product-001")
    put("product", buildJsonObject {
        put("type", "Coffee")
        put("quantity", 10000.0)
        put("unit", "kg")
    })
    put("compliance", buildJsonObject {
        put("eudrCompliant", true)
        put("complianceCredentialId", complianceCredential.id)
        put("verificationDate", Instant.now().toString())
    })
    put("provenance", buildJsonObject {
        put("farm", farmDid.id)
        put("harvestDate", "2024-06-15")
        put("exportDate", Instant.now().toString())
    })
    put("eoEvidence", buildJsonObject {
        put("digest", eoProofDigest)
        put("verificationStatus", "verified")
    })
}

Step 5: Climate TRACE Integration

Verify against Climate TRACE as global verifier:

1
2
3
4
5
6
7
8
9
10
11
12
suspend fun verifyAgainstClimateTrace(
    location: JsonObject,
    eoEvidence: JsonObject
): Boolean {
    // Query Climate TRACE API
    val climateTraceData = queryClimateTraceAPI(location)

    // Compare with EO evidence
    val matches = compareWithClimateTrace(eoEvidence, climateTraceData)

    return matches
}

Next Steps